packages feed

downhill (empty) → 0.1.0.0

raw patch · 24 files changed

+3115/−0 lines, 24 filesdep +basedep +containersdep +downhill

Dependencies added: base, containers, downhill, reflection, tasty, tasty-hunit, template-haskell, th-abstraction, transformers, unordered-containers, vector-space

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for downhill++## 0.1.0.0 -- 2021-12-12++* First version
+ LICENSE view
@@ -0,0 +1,14 @@+Copyright 2021 Andrius Stankevičius++Permission is hereby granted, free of charge, to any person obtaining a copy of this+software and associated documentation files (the "Software"), to deal in the Software+without restriction, including without limitation the rights to use, copy, modify,+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ downhill.cabal view
@@ -0,0 +1,58 @@+cabal-version:       2.4++name:                downhill+version:             0.1.0.0+synopsis:            Reverse mode automatic differentiation+homepage:            https://andriusstank.github.io/downhill/+description:+  Simple and well typed implementation of reverse mode automatic differentiation.+  See home page <https://andriusstank.github.io/downhill/> for more detailed+  description.+bug-reports:         https://github.com/andriusstank/downhill/issues+license:             MIT+license-file:        LICENSE+author:              Andrius Stankevičius+maintainer:          floppycat@gmail.com+-- copyright:+category:            Math+extra-source-files:  CHANGELOG.md++library+  exposed-modules:     Downhill.Linear.Expr,+                       Downhill.Linear.BackGrad,+                       Downhill.Linear.Backprop,+                       Downhill.Linear.Lift,+                       Downhill.Linear.Prelude,+                       Downhill.Internal.Graph.Types,+                       Downhill.Internal.Graph.OpenMap,+                       Downhill.Internal.Graph.NodeMap,+                       Downhill.Internal.Graph.OpenGraph,+                       Downhill.Internal.Graph.Graph,+                       Downhill.Grad,+                       Downhill.BVar,+                       Downhill.BVar.Num+                       Downhill.BVar.Prelude,+                       Downhill.BVar.Traversable,+                       Downhill.TH+  -- other-modules:+  -- other-extensions:+  build-depends:       base                  >= 4.12.0.0 && <4.17,+                       containers            >= 0.6.5 && < 0.7,+                       reflection            >= 2.1.6 && < 2.2,+                       template-haskell      >= 2.16.0 && < 2.19,+                       transformers          >= 0.5.6 && < 0.6,+                       th-abstraction        >= 0.4.3 && < 0.5,+                       unordered-containers  >= 0.2.14 && < 0.3,+                       vector-space          >= 0.16 && < 0.17,+  hs-source-dirs:      src+  other-modules:+  default-language:    Haskell2010+  ghc-options:         -Wall++test-suite downhill-test+  type:                exitcode-stdio-1.0+  main-is:             Main.hs+  other-modules:       DownhillTest.Point, DownhillTest.Traversable, DownhillTest.TH, DownhillTest.TestTHOptions+  build-depends:       base, downhill, tasty, tasty-hunit, vector-space+  hs-source-dirs:      test+  default-language:    Haskell2010
+ src/Downhill/BVar.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.BVar+  ( BVar (..),+    var,+    constant,+    backprop,+  )+where++import Data.AdditiveGroup (AdditiveGroup)+import Data.AffineSpace (AffineSpace ((.+^), (.-.)))+import qualified Data.AffineSpace as AffineSpace+import Data.VectorSpace+  ( AdditiveGroup (..),+    VectorSpace ((*^)),+  )+import qualified Data.VectorSpace as VectorSpace+import Downhill.Grad+  ( Dual (evalGrad),+    HasFullGrad,+    HasGrad (Grad, MScalar, Tang),+    HasGradAffine,+  )+import Downhill.Linear.BackGrad+  ( BackGrad (..),+    realNode,+  )+import qualified Downhill.Linear.Backprop as BP+import Downhill.Linear.Expr (BasicVector, Expr (ExprVar), FullVector)+import Downhill.Linear.Lift (lift2_dense)+import Prelude hiding (id, (.))++-- | Variable is a value paired with derivative.+data BVar r a = BVar+  { bvarValue :: a,+    bvarGrad :: BackGrad r (Grad a)+  }++instance (AdditiveGroup b, HasFullGrad b) => AdditiveGroup (BVar r b) where+  zeroV = BVar zeroV zeroV+  negateV (BVar y0 dy) = BVar (negateV y0) (negateV dy)+  BVar y0 dy ^-^ BVar z0 dz = BVar (y0 ^-^ z0) (dy ^-^ dz)+  BVar y0 dy ^+^ BVar z0 dz = BVar (y0 ^+^ z0) (dy ^+^ dz)++instance (Num b, HasFullGrad b, MScalar b ~ b) => Num (BVar r b) where+  (BVar f0 df) + (BVar g0 dg) = BVar (f0 + g0) (df ^+^ dg)+  (BVar f0 df) - (BVar g0 dg) = BVar (f0 - g0) (df ^-^ dg)+  (BVar f0 df) * (BVar g0 dg) = BVar (f0 * g0) (f0 *^ dg ^+^ g0 *^ df)+  negate (BVar f0 df) = BVar (negate f0) (negateV df)+  abs (BVar f0 df) = BVar (abs f0) (signum f0 *^ df) -- TODO: ineffiency: multiplication by 1+  signum (BVar f0 _) = BVar (signum f0) zeroV+  fromInteger x = BVar (fromInteger x) zeroV++sqr :: Num a => a -> a+sqr x = x * x++rsqrt :: Floating a => a -> a+rsqrt x = recip (sqrt x)++instance (Fractional b, HasFullGrad b, MScalar b ~ b) => Fractional (BVar r b) where+  fromRational x = BVar (fromRational x) zeroV+  recip (BVar x dx) = BVar (recip x) (df *^ dx)+    where+      df = negate (recip (sqr x))+  BVar x dx / BVar y dy = BVar (x / y) ((recip y *^ dx) ^-^ ((x / sqr y) *^ dy))++instance (Floating b, HasFullGrad b, MScalar b ~ b) => Floating (BVar r b) where+  pi = BVar pi zeroV+  exp (BVar x dx) = BVar (exp x) (exp x *^ dx)+  log (BVar x dx) = BVar (log x) (recip x *^ dx)+  sin (BVar x dx) = BVar (sin x) (cos x *^ dx)+  cos (BVar x dx) = BVar (cos x) (negate (sin x) *^ dx)+  asin (BVar x dx) = BVar (asin x) (rsqrt (1 - sqr x) *^ dx)+  acos (BVar x dx) = BVar (acos x) (negate (rsqrt (1 - sqr x)) *^ dx)+  atan (BVar x dx) = BVar (atan x) (recip (1 + sqr x) *^ dx)+  sinh (BVar x dx) = BVar (sinh x) (cosh x *^ dx)+  cosh (BVar x dx) = BVar (cosh x) (sinh x *^ dx)+  asinh (BVar x dx) = BVar (asinh x) (rsqrt (1 + sqr x) *^ dx)+  acosh (BVar x dx) = BVar (acosh x) (rsqrt (sqr x - 1) *^ dx)+  atanh (BVar x dx) = BVar (atanh x) (recip (1 - sqr x) *^ dx)++instance+  ( VectorSpace v,+    HasFullGrad v,+    Tang v ~ v,+    FullVector (MScalar v),+    Grad (MScalar v) ~ MScalar v+  ) =>+  VectorSpace (BVar r v)+  where+  type Scalar (BVar r v) = BVar r (MScalar v)+  BVar a da *^ BVar v dv = BVar (a *^ v) (lift2_dense bpA bpV da dv)+    where+      bpA :: Grad v -> MScalar v+      bpA dz = evalGrad dz v+      bpV :: Grad v -> Grad v+      bpV dz = a *^ dz++instance (HasFullGrad p, HasGradAffine p) => AffineSpace (BVar r p) where+  type Diff (BVar r p) = BVar r (Tang p)+  BVar y0 dy .+^ BVar z0 dz = BVar (y0 .+^ z0) (dy ^+^ dz)+  BVar y0 dy .-. BVar z0 dz = BVar (y0 .-. z0) (dy ^-^ dz)++-- | A variable with derivative of zero.+constant :: forall r a. FullVector (Grad a) => a -> BVar r a+constant x = BVar x zeroV++-- | A variable with identity derivative.+var :: a -> BVar (Grad a) a+var x = BVar x (realNode ExprVar)++--backprop :: forall a p. (HasGrad p, BasicVector a) => BVar a p -> GradBuilder p -> a+--backprop (BVar _y0 x) = BP.backprop x++-- | Reverse mode differentiation.+--+-- +backprop :: forall r a. (HasGrad a, FullVector (Grad a), BasicVector r) => BVar r a -> Grad a -> r+backprop (BVar _y0 x) = BP.backprop x
+ src/Downhill/BVar/Num.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.BVar.Num+  ( -- | Automatic differentiation for @Num@ hierarchy.+    --+    -- Polymorphic functions of type such as @Num a => a -> a@+    -- can't be differentiated directly, because 'backprop' needs some additional instances.+    -- 'AsNum' wrapper provides those instances.+    --+    -- @+    -- derivative :: (forall b. Floating b => b -> b) -> (forall a. Floating a => a -> a)+    -- derivative fun x0 = backpropNum (fun (var (AsNum x0)))+    -- @++    AsNum (..),+    NumBVar,+    numbvarValue,+    var,+    constant,+    backpropNum+  )+where++import Data.AffineSpace (AffineSpace (..))+import Data.Semigroup (Sum (Sum, getSum))+import Data.VectorSpace (AdditiveGroup (..), VectorSpace (..), zeroV)+import Downhill.BVar (BVar (bvarValue), backprop)+import qualified Downhill.BVar as BVar+import Downhill.Grad+  ( Dual (evalGrad),+    HasGrad (Grad, Metric, MScalar, Tang),+    MetricTensor (MtCovector, MtVector, evalMetric),+  )+import Downhill.Linear.Expr (BasicVector (..), FullVector (identityBuilder, negateBuilder, scaleBuilder))++-- | @AsNum a@ implements many instances in terms of @Num a@ instance.+newtype AsNum a = AsNum {unAsNum :: a}+  deriving (Show)+  deriving (Num) via a+  deriving (Fractional) via a+  deriving (Floating) via a++instance Num a => Dual (AsNum a) (AsNum a) (AsNum a) where+  evalGrad = (*)++instance Num a => HasGrad (AsNum a) where+  type MScalar (AsNum a) = AsNum a+  type Grad (AsNum a) = AsNum a+  type Tang (AsNum a) = AsNum a+  type Metric (AsNum a) = AsNum a++instance Num a => MetricTensor (AsNum a) where+  type MtVector (AsNum a) = AsNum a+  type MtCovector (AsNum a) = AsNum a+  evalMetric (AsNum m) (AsNum x) = AsNum (m * x)++instance Num a => AdditiveGroup (AsNum a) where+  zeroV = 0+  (^+^) = (+)+  (^-^) = (-)+  negateV = negate++instance Num a => VectorSpace (AsNum a) where+  type Scalar (AsNum a) = AsNum a+  (*^) = (*)++instance Num a => BasicVector (AsNum a) where+  type VecBuilder (AsNum a) = Sum a+  sumBuilder = AsNum . getSum++instance Num a => FullVector (AsNum a) where+  identityBuilder = Sum . unAsNum+  negateBuilder = Sum . negate . unAsNum+  scaleBuilder (AsNum x) (AsNum y) = Sum $ x * y++instance Num a => AffineSpace (AsNum a) where+  type Diff (AsNum a) = AsNum a+  AsNum x .-. AsNum y = AsNum (x - y)+  AsNum x .+^ AsNum y = AsNum (x + y)++type NumBVar a = BVar (AsNum a) (AsNum a)++constant :: forall a. Num a => a -> NumBVar a+constant = BVar.constant @(AsNum a) @(AsNum a) . AsNum++var :: Num a => a -> NumBVar a+var = BVar.var . AsNum++backpropNum :: forall a. Num a => NumBVar a -> a+backpropNum x = unAsNum $ backprop @(AsNum a) @(AsNum a) x (AsNum 1)++numbvarValue :: NumBVar a -> a+numbvarValue = unAsNum . bvarValue
+ src/Downhill/BVar/Prelude.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}++module Downhill.BVar.Prelude+  ( -- * Tuples++    -- | Pattern synonyms @T2@, @T3@ pack and unpack tuples:+    --+    -- @+    -- fstBVar :: (HasGrad a, HasGrad b) => BVar r (a, b) -> BVar r a+    -- fstBVar (T2 a _b) = a+    --+    -- tieBVar :: (HasGrad a, HasGrad b) => BVar r a -> BVar r b -> BVar r (a, b)+    -- tieBVar a b = T2 a b+    -- @+    pattern T2,+    pattern T3,+  )+where++import Downhill.BVar (BVar (BVar))+import Downhill.Grad (HasGrad)+import qualified Downhill.Linear.Prelude as Linear+import Prelude ()++toPair :: (HasGrad a, HasGrad b) => BVar r (a, b) -> (BVar r a, BVar r b)+toPair (BVar (x, y) (Linear.T2 dx dy)) = (BVar x dx, BVar y dy)++{-# COMPLETE T2 #-}++pattern T2 :: (HasGrad a, HasGrad b) => BVar r a -> BVar r b -> BVar r (a, b)+pattern T2 a b <-+  (toPair -> (a, b))+  where+    T2 (BVar a da) (BVar b db) = BVar (a, b) (Linear.T2 da db)++toTriple :: (HasGrad a, HasGrad b, HasGrad c) => BVar r (a, b, c) -> (BVar r a, BVar r b, BVar r c)+toTriple (BVar (x, y, z) (Linear.T3 dx dy dz)) = (BVar x dx, BVar y dy, BVar z dz)++{-# COMPLETE T3 #-}++pattern T3 :: (HasGrad a, HasGrad b, HasGrad c) => BVar r a -> BVar r b -> BVar r c -> BVar r (a, b, c)+pattern T3 a b c <-+  (toTriple -> (a, b, c))+  where+    T3 (BVar a da) (BVar b db) (BVar c dc) = BVar (a, b, c) (Linear.T3 da db dc)
+ src/Downhill/BVar/Traversable.hs view
@@ -0,0 +1,284 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Easy backpropagation when all variables have the same type.+--+-- @+-- data MyRecord a = ...+--   deriving (Functor, Foldable, Traversable)+--+-- deriving via (TraversableVar MyRecord a) instance HasGrad a => HasGrad (MyRecord a)+-- @+-- +-- = Gradient type+-- One might excect gradient type to be @type Grad (MyRecord a) = MyRecord (Grad a)@, but it's not+-- the case, because record could contain additional members apart from @a@s, for example:+--+-- @+-- data MyPoint a = MyPoint+-- {+-- ,  pointLabel :: String+-- ,  pointX :: a+-- ,  pointY :: a+-- }+-- @+--+-- and @MyPoint (Grad a)@ can't be made @VectorSpace@. Gradient type @Grad (MyRecord a)@+-- is a newtype wrapper over @IntMap@+-- that is not exported.+--++module Downhill.BVar.Traversable+  ( -- * Backpropagate+    backpropTraversable,+    backpropTraversable_GradOnly,+    backpropTraversable_ValueAndGrad,++     -- * Split+    splitTraversable,++    -- * TraversableVar+    TraversableVar (..),+  )+where++import Control.Monad.Trans.State.Strict (State, evalState, get, put)+import Data.AdditiveGroup (AdditiveGroup, sumV)+import Data.Foldable (toList)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.Maybe (fromMaybe)+import Data.VectorSpace (AdditiveGroup (negateV, zeroV, (^+^), (^-^)), VectorSpace (Scalar, (*^)))+import qualified Data.VectorSpace as VectorSpace+import Downhill.BVar (BVar (BVar, bvarGrad, bvarValue), backprop, var)+import Downhill.Grad+  ( Dual (evalGrad),+    HasGrad (Grad, MScalar, Metric, Tang),+    MetricTensor+      ( MtCovector,+        MtVector,+        evalMetric+      ),+  )+import Downhill.Linear.BackGrad (BackGrad (BackGrad), castBackGrad, realNode)+import Downhill.Linear.Expr+  ( BasicVector (VecBuilder, sumBuilder),+    Expr (ExprSum),+    FullVector,+    SparseVector (unSparseVector),+    Term,+  )+import Downhill.Linear.Lift (lift1_sparse)+import GHC.Generics (Generic)++-- | Provides HasGrad instance for use in deriving via+newtype TraversableVar f a = TraversableVar {unTraversableVar :: f a}+  deriving stock (Functor, Foldable, Traversable)++newtype TraversableMetric f a = TraversableMetric (Metric a)+  deriving (Generic)++instance AdditiveGroup (Metric a) => AdditiveGroup (TraversableMetric f a)++instance VectorSpace (Metric a) => VectorSpace (TraversableMetric f a) where+  type Scalar (TraversableMetric f a) = Scalar (Metric a)++instance+  ( MetricTensor (Metric a),+    MtVector (Metric a) ~ Tang a,+    MtCovector (Metric a) ~ Grad a,+    Dual s (Tang a) (Grad a)+  ) =>+  MetricTensor (TraversableMetric f a)+  where+  type MtVector (TraversableMetric f a) = IntmapVector f (Tang a)+  type MtCovector (TraversableMetric f a) = IntmapVector f (Grad a)+  evalMetric (TraversableMetric m) (IntmapVector da) = IntmapVector (IntMap.map (evalMetric m) da)++instance HasGrad a => HasGrad (TraversableVar f a) where+  type MScalar (TraversableVar f a) = MScalar a+  type Tang (TraversableVar f a) = IntmapVector f (Tang a)+  type Grad (TraversableVar f a) = IntmapVector f (Grad a)+  type Metric (TraversableVar f a) = TraversableMetric f a++-- | @IntmapVector@ serves as a gradient of 'TraversableVar'.+newtype IntmapVector f v = IntmapVector {unIntmapVector :: IntMap v}+  deriving (Show)++instance AdditiveGroup a => AdditiveGroup (IntmapVector f a) where+  zeroV = IntmapVector IntMap.empty+  negateV (IntmapVector v) = IntmapVector (negateV <$> v)+  IntmapVector u ^+^ IntmapVector v = IntmapVector (IntMap.unionWith (^+^) u v)+  IntmapVector u ^-^ IntmapVector v = IntmapVector (IntMap.mergeWithKey combine only1 only2 u v)+    where+      combine _key x y = Just (x ^-^ y)+      only1 = id+      only2 = fmap negateV++instance VectorSpace v => VectorSpace (IntmapVector f v) where+  type Scalar (IntmapVector f v) = VectorSpace.Scalar v+  a *^ (IntmapVector v) = IntmapVector (fmap (a *^) v)++instance Dual s dv v => Dual s (IntmapVector f dv) (IntmapVector f v) where+  evalGrad (IntmapVector dv) (IntmapVector v) = sumV $ IntMap.intersectionWith evalGrad dv v++deriving via (IntMap v) instance Semigroup v => Semigroup (IntmapVector f v)++deriving via (IntMap v) instance Monoid v => Monoid (IntmapVector f v)++instance BasicVector v => BasicVector (IntmapVector f v) where+  type VecBuilder (IntmapVector f v) = IntmapVector f (VecBuilder v)+  sumBuilder (IntmapVector v) = IntmapVector (fmap sumBuilder v)++imap ::+  forall t a b.+  Traversable t =>+  (Int -> a -> b) ->+  t a ->+  t b+imap mkBVar' xs' = evalState (traverse getmkvar xs') 0+  where+    getmkvar :: a -> State Int b+    getmkvar x = do+      index <- get+      put (index + 1)+      return (mkBVar' index x)++-- | Note that @splitTraversable@ won't be useful+-- for top level @BVar@, because the type @Grad (f a)@ is not exposed. +splitTraversable ::+  forall f r a.+  ( Traversable f,+    Grad (f a) ~ Grad (TraversableVar f a),+    HasGrad a+  ) =>+  BVar r (f a) ->+  f (BVar r a)+splitTraversable (BVar xs dxs) = vars+  where+    vars :: f (BVar r a)+    vars = imap mkBVar xs+    mkBVar :: Int -> a -> BVar r a+    mkBVar index x =+      let mkBuilder :: VecBuilder (Grad a) -> IntmapVector f (VecBuilder (Grad a))+          mkBuilder dx = IntmapVector (IntMap.singleton index dx)+       in BVar x (lift1_sparse mkBuilder dxs)++lift1_sparseT ::+  forall r a z.+  BasicVector z =>+  (VecBuilder z -> VecBuilder a) ->+  BackGrad r a ->+  Term r (SparseVector z)+lift1_sparseT fa (BackGrad f) = f (fa . unSparseVector)++-- Not exported, because it is untested and hardly useful.+_joinTraversable ::+  forall f r a.+  ( Traversable f,+    Grad (f a) ~ Grad (TraversableVar f a),+    HasGrad a,+    FullVector (Grad a)+  ) =>+  f (BVar r a) ->+  BVar r (f a)+_joinTraversable x = BVar values (castBackGrad node)+  where+    values :: f a+    values = bvarValue <$> x+    grads :: f (BackGrad r (Grad a))+    grads = bvarGrad <$> x+    terms :: [Term r (SparseVector (IntmapVector f (Grad a)))]+    terms = toList (imap mkTerm grads)+    mkTerm :: Int -> BackGrad r (Grad a) -> Term r (SparseVector (IntmapVector f (Grad a)))+    mkTerm index = lift1_sparseT (lookupIntMap index)+    lookupIntMap :: Int -> IntmapVector f x -> x+    lookupIntMap key (IntmapVector intmap) = case IntMap.lookup key intmap of+      Nothing -> error "Downhill BUG: Bad index in joinTraversable"+      Just value -> value+    node :: BackGrad r (SparseVector (IntmapVector f (Grad a)))+    node = realNode (ExprSum terms)++-- | @backpropTraversable one combine fun@+--+-- @one@ is a value to be backpropagated. In case of @p@ being scalar, set @one@+-- to 1 to compute unscaled gradient.+--+-- @combine@ is given value of a parameter and its gradient to construct result,+-- just like @zipWith@.+--+-- @fun@ is the function to be differentiated.+backpropTraversable ::+  forall f a b p.+  ( Traversable f,+    Grad (f a) ~ Grad (TraversableVar f a),+    HasGrad a,+    HasGrad p,+    FullVector (Grad p)+  ) =>+  Grad p ->+  (a -> Grad a -> b) ->+  (forall r. f (BVar r a) -> BVar r p) ->+  f a ->+  f b+backpropTraversable one combine fun x = imap makeResult x+  where+    splitX :: f (BVar (Grad (f a)) a)+    splitX = splitTraversable (var x)++    y :: BVar (Grad (f a)) p+    y = fun splitX++    grad :: IntMap (Grad a)+    IntmapVector grad = backprop y one++    lookupGrad i = fromMaybe zeroV (IntMap.lookup i grad)++    makeResult :: Int -> a -> b+    makeResult i x' = combine x' (lookupGrad i)++{-# ANN backpropTraversable_GradOnly "HLint: ignore Use camelCase" #-}++-- | Like 'backpropTraversable', but returns gradient only.+backpropTraversable_GradOnly ::+  forall f a p.+  ( Traversable f,+    Grad (f a) ~ Grad (TraversableVar f a),+    HasGrad a,+    HasGrad p,+    FullVector (Grad p)+  ) =>+  Grad p ->+  (forall r. f (BVar r a) -> BVar r p) ->+  f a ->+  f (Grad a)+backpropTraversable_GradOnly one = backpropTraversable one gradOnly+  where+    gradOnly _value grad = grad++-- | 'backpropTraversable' specialized to return a pair of value and gradient.+{-# ANN backpropTraversable_ValueAndGrad "HLint: ignore Use camelCase" #-}+backpropTraversable_ValueAndGrad ::+  forall f a p.+  ( Traversable f,+    Grad (f a) ~ Grad (TraversableVar f a),+    HasGrad a,+    HasGrad p,+    FullVector (Grad p)+  ) =>+  Grad p ->+  (forall r. f (BVar r a) -> BVar r p) ->+  f a ->+  f (a, Grad a)+backpropTraversable_ValueAndGrad one = backpropTraversable one (,)
+ src/Downhill/Grad.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.Grad+  ( Dual (..),+    MetricTensor (..),+    HasGrad (..),+    GradBuilder,+    HasFullGrad,+    HasGradAffine,+  )+where++import Data.AffineSpace (AffineSpace (Diff))+import Data.Kind (Type)+import Data.VectorSpace (AdditiveGroup ((^+^)), VectorSpace (Scalar, (*^)))+import qualified Data.VectorSpace as VectorSpace+import Downhill.Linear.Expr (BasicVector (VecBuilder), FullVector)+import GHC.Generics (Generic)++-- | Dual of a vector @v@ is a linear map @v -> Scalar v@.+class+  ( AdditiveGroup s,+    VectorSpace v,+    VectorSpace dv,+    VectorSpace.Scalar v ~ s,+    VectorSpace.Scalar dv ~ s+  ) =>+  Dual s v dv+  where+  -- if evalGrad goes to HasGrad class, parameter p is ambiguous+  evalGrad :: dv -> v -> s++-- | @MetricTensor@ converts gradients to vectors.+--+-- It is really inverse of a metric tensor, because it maps cotangent+-- space into tangent space. Gradient descent doesn't need metric tensor,+-- it needs inverse.++class+  ( Dual (Scalar g) (MtVector g) (MtCovector g),+    VectorSpace g+  ) =>+  MetricTensor g+  where+  type MtVector g :: Type+  type MtCovector g :: Type++  -- | @m@ must be symmetric:+  --+  -- @evalGrad x (evalMetric m y) = evalGrad y (evalMetric m x)@+  evalMetric :: g -> MtCovector g -> MtVector g++  -- | @innerProduct m x y = evalGrad x (evalMetric m y)@+  innerProduct :: g -> MtCovector g -> MtCovector g -> Scalar g+  innerProduct g x y = evalGrad x (evalMetric g y)++  -- | @sqrNorm m x = innerProduct m x x@+  sqrNorm :: g -> MtCovector g -> Scalar g+  sqrNorm g x = innerProduct g x x++-- | @HasGrad@ is a collection of types and constraints that are useful+-- in many places. It helps to keep type signatures short.++-- TODO: FullVector or not?+-- TODO: Metric or not?+class+  ( Dual (MScalar p) (Tang p) (Grad p),+    MetricTensor (Metric p),+    MtVector (Metric p) ~ Tang p,+    MtCovector (Metric p) ~ Grad p,+    BasicVector (Tang p),+    BasicVector (Grad p)+  ) =>+  HasGrad p+  where+  -- | Scalar of @Tang p@ and @Grad p@.+  type MScalar p :: Type++  -- | Tangent vector of manifold @p@. If p is 'AffineSpace', @Tang p@ should+  -- be @'Diff' p@. If @p@ is 'VectorSpace', @Tang p@ might be the same as @p@ itself.+  type Tang p :: Type++  -- | Dual of tangent space of @p@.+  type Grad p :: Type++  -- | A 'MetricTensor'.+  type Metric p :: Type++type GradBuilder v = VecBuilder (Grad v)++type HasFullGrad p = (HasGrad p, FullVector (Grad p))++type HasGradAffine p =+  ( AffineSpace p,+    HasGrad p,+    HasGrad (Tang p),+    Tang p ~ Diff p,+    Tang (Tang p) ~ Tang p,+    Grad (Tang p) ~ Grad p+  )++instance Dual Integer Integer Integer where+  evalGrad = (*)++instance MetricTensor Integer where+  type MtVector Integer = Integer+  type MtCovector Integer = Integer+  evalMetric m x = m * x++instance HasGrad Integer where+  type MScalar Integer = Integer+  type Tang Integer = Integer+  type Grad Integer = Integer+  type Metric Integer = Integer++instance (Dual s a da, Dual s b db) => Dual s (a, b) (da, db) where+  evalGrad (a, b) (x, y) = evalGrad a x ^+^ evalGrad b y++instance (Dual s a da, Dual s b db, Dual s c dc) => Dual s (a, b, c) (da, db, dc) where+  evalGrad (a, b, c) (x, y, z) = evalGrad a x ^+^ evalGrad b y ^+^ evalGrad c z++instance (MetricTensor ma, MetricTensor mb, Scalar ma ~ Scalar mb) => MetricTensor (ma, mb) where+  type MtVector (ma, mb) = (MtVector ma, MtVector mb)+  type MtCovector (ma, mb) = (MtCovector ma, MtCovector mb)+  evalMetric (ma, mb) (a, b) = (evalMetric ma a, evalMetric mb b)+  sqrNorm (ma, mb) (a, b) = sqrNorm ma a ^+^ sqrNorm mb b++instance+  ( HasGrad a,+    HasGrad b,+    MScalar b ~ MScalar a+  ) =>+  HasGrad (a, b)+  where+  type MScalar (a, b) = MScalar a+  type Grad (a, b) = (Grad a, Grad b)+  type Tang (a, b) = (Tang a, Tang b)+  type Metric (a, b) = (Metric a, Metric b)++instance+  ( MetricTensor ma,+    MetricTensor mb,+    MetricTensor mc,+    Scalar ma ~ Scalar mb,+    Scalar ma ~ Scalar mc+  ) =>+  MetricTensor (ma, mb, mc)+  where+  type MtVector (ma, mb, mc) = (MtVector ma, MtVector mb, MtVector mc)+  type MtCovector (ma, mb, mc) = (MtCovector ma, MtCovector mb, MtCovector mc)+  evalMetric (ma, mb, mc) (a, b, c) = (evalMetric ma a, evalMetric mb b, evalMetric mc c)+  sqrNorm (ma, mb, mc) (a, b, c) = sqrNorm ma a ^+^ sqrNorm mb b ^+^ sqrNorm mc c++instance+  ( HasGrad a,+    HasGrad b,+    HasGrad c,+    MScalar b ~ MScalar a,+    MScalar c ~ MScalar a+  ) =>+  HasGrad (a, b, c)+  where+  type MScalar (a, b, c) = MScalar a+  type Grad (a, b, c) = (Grad a, Grad b, Grad c)+  type Tang (a, b, c) = (Tang a, Tang b, Tang c)+  type Metric (a, b, c) = (Metric a, Metric b, Metric c)++instance Dual Float Float Float where+  evalGrad = (*)++instance MetricTensor Float where+  type MtVector Float = Float+  type MtCovector Float = Float+  evalMetric m dv = m * dv++instance HasGrad Float where+  type MScalar Float = Float+  type Grad Float = Float+  type Tang Float = Float+  type Metric Float = Float++instance Dual Double Double Double where+  evalGrad = (*)++instance MetricTensor Double where+  type MtVector Double = Double+  type MtCovector Double = Double+  evalMetric m dv = m * dv++instance HasGrad Double where+  type MScalar Double = Double+  type Grad Double = Double+  type Tang Double = Double+  type Metric Double = Double++newtype L2 v = L2 (Scalar v)+  deriving (Generic)++instance AdditiveGroup (Scalar v) => AdditiveGroup (L2 v)++instance (AdditiveGroup (Scalar v), Num (Scalar v)) => VectorSpace (L2 v) where+  type Scalar (L2 v) = Scalar v+  x *^ L2 y = L2 (x * y)++instance (AdditiveGroup a, Num a, a ~ Scalar v, Dual a v v) => MetricTensor (L2 v) where+  type MtVector (L2 v) = v+  type MtCovector (L2 v) = v+  evalMetric (L2 a) u = a *^ u+  innerProduct (L2 a) x y = a * evalGrad x y+  sqrNorm g x = innerProduct g x x
+ src/Downhill/Internal/Graph/Graph.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.Internal.Graph.Graph+  (  -- * Graph type+    Graph (..), Node(..),+    SomeGraph (..),+    -- * Evaluate+    evalGraph,+    -- * Transpose+    transposeGraph,+    --transposeFwdGraph,+    --transposeBackGraph,+    -- * Construct+    unsafeFromOpenGraph,+  )+where++import Data.Either (partitionEithers)+import Data.Functor.Identity (Identity (Identity, runIdentity))+import Downhill.Internal.Graph.NodeMap+  ( IsNodeSet,+    NodeKey,+    NodeMap,+    KeyAndValue (KeyAndValue),+    SomeNodeMap (SomeNodeMap),+  )+import qualified Downhill.Internal.Graph.NodeMap as NodeMap+import Downhill.Internal.Graph.OpenGraph (OpenGraph (OpenGraph), OpenNode (OpenNode), OpenEdge (OpenEdge), OpenEndpoint (OpenSourceNode, OpenInnerNode))+import Downhill.Internal.Graph.Types (FwdFun (FwdFun), BackFun)+import Downhill.Linear.Expr (BasicVector (VecBuilder, sumBuilder))+import Prelude hiding (head, tail)+import GHC.Stack (callStack, prettyCallStack, HasCallStack)++data Endpoint s a v where+    SourceNode :: Endpoint s a a+    InnerNode :: NodeKey s v -> Endpoint s a v++data Edge s e a v where+    Edge :: e u v -> Endpoint s a u -> Edge s e a v++{-| Inner node. This does not include initial node. Contains a list+of ingoing edges. -}+data Node s e a v = BasicVector v => Node [Edge s e a v]++data Graph s e a z = BasicVector a =>+  Graph+  { graphInnerNodes :: NodeMap s (Node s e a),+    graphFinalNode :: Node s e a z+  }++data SomeGraph e a z where+  SomeGraph :: IsNodeSet s => Graph s e a z -> SomeGraph e a z++{- `Edge` stores head endpoint only. `AnyEdge` stores both endpoints. -}+data AnyEdge s e a z = forall u v.+  AnyEdge+  { _edgeTail :: Endpoint s z v,+    _edgeLabel :: e u v,+    _edgeHead :: Endpoint s a u+  }++-- | Forward mode evaluation+evalGraph :: forall s x z. Graph s FwdFun z x -> z -> x+evalGraph (Graph nodes finalNode) dz = evalNode finalNode+  where+    evalParent :: forall v. Endpoint s z v -> v+    evalParent = \case+      SourceNode -> dz+      InnerNode nodeName -> runIdentity (NodeMap.lookup innerValues nodeName)+    evalEdge :: Edge s FwdFun z v -> VecBuilder v+    evalEdge (Edge (FwdFun f) tail) = f $ evalParent tail+    evalNode :: Node s FwdFun z v -> v+    evalNode (Node xs) = sumBuilder (mconcat [evalEdge x | x <- xs])+    innerValues :: NodeMap s Identity+    innerValues = NodeMap.map (Identity . evalNode) nodes++nodeEdges :: forall s f a z x. NodeKey s x -> Node s f a x -> [AnyEdge s f a z]+nodeEdges name (Node xs) = go <$> xs+  where+    go :: Edge s f a x -> AnyEdge s f a z+    go (Edge f head) = AnyEdge (InnerNode name) f head++allGraphEdges :: forall s f a z. Graph s f a z -> [AnyEdge s f a z]+allGraphEdges (Graph innerNodes (Node es)) = finalEdges ++ innerEdges+  where+    innerEdges :: [AnyEdge s f a z]+    innerEdges = concat (NodeMap.toListWith nodeEdges innerNodes)+    finalEdges :: [AnyEdge s f a z]+    finalEdges = wrapFinalEdge <$> es+      where+        wrapFinalEdge :: Edge s f a z -> AnyEdge s f a z+        wrapFinalEdge (Edge f head) = AnyEdge SourceNode f head++sortByTail ::+  forall s f da dz.+  AnyEdge s f da dz ->+  Either (Edge s f da dz) (KeyAndValue s (Edge s f da))+sortByTail (AnyEdge tail f head) = case tail of+  SourceNode -> Left (Edge f head)+  InnerNode x -> Right (KeyAndValue x (Edge f head))++flipAnyEdge :: (forall u v. f u v -> g v u) -> AnyEdge s f a z -> AnyEdge s g z a+flipAnyEdge flipF (AnyEdge tail f head) = AnyEdge head (flipF f) tail++{- BasicVector constraint is needed to construct a node.+   `NodeMap s NodeDict` is a list of all nodes.+-}+data NodeDict x = BasicVector x => NodeDict++emptyNodeMap :: forall s e z. NodeMap s NodeDict -> NodeMap s (Node s e z)+emptyNodeMap = NodeMap.map emptyNode+  where+    emptyNode :: forall x. NodeDict x -> Node s e z x+    emptyNode = \case+      NodeDict -> Node []++edgeListToGraph ::+  forall s e a z.+  (IsNodeSet s, BasicVector a, BasicVector z) =>+  NodeMap s NodeDict ->+  [AnyEdge s e z a] ->+  Graph s e z a+edgeListToGraph nodes flippedEdges = Graph innerNodes (Node initialEdges)+  where+    initialEdges :: [Edge s e z a]+    innerEdges :: [KeyAndValue s (Edge s e z)]+    (initialEdges, innerEdges) = partitionEithers (sortByTail <$> flippedEdges)+    prependToMap :: KeyAndValue s (Edge s e z) -> NodeMap s (Node s e z) -> NodeMap s (Node s e z)+    prependToMap (KeyAndValue key edge) = NodeMap.adjust prependToNode key+      where+        prependToNode (Node edges) = Node (edge : edges)+    innerNodes = foldr prependToMap (emptyNodeMap nodes) innerEdges+  +graphNodes :: Graph s f da dz -> NodeMap s NodeDict+graphNodes (Graph env _) = NodeMap.map go env+  where+    go :: Node s f da dv -> NodeDict dv+    go = \case+      Node _ -> NodeDict++-- | Reverse edges. Turns reverse mode evaluation into forward mode.+transposeGraph :: forall s f g a z. IsNodeSet s => (forall u v. f u v -> g v u) -> Graph s f a z -> Graph s g z a+transposeGraph flipEdge g@(Graph _ (Node _)) = edgeListToGraph (graphNodes g) flippedEdges+  where edges :: [AnyEdge s f a z]+        edges = allGraphEdges g+        flippedEdges :: [AnyEdge s g z a]+        flippedEdges = flipAnyEdge flipEdge <$> edges++_mapEdges :: forall s f g a z. (forall u v. f u v -> g u v) -> Graph s f a z -> Graph s g a z+_mapEdges f (Graph inner final) = Graph (NodeMap.map go inner) (go final)+  where+    go :: Node s f a v -> Node s g a v+    go (Node xs) = Node [goEdge x | x <- xs]+    goEdge :: Edge p f a x -> Edge p g a x+    goEdge (Edge e x) = Edge (f e) x++unsafeConstructGraph :: forall s a v. (IsNodeSet s, BasicVector a, HasCallStack) => NodeMap s (OpenNode a) -> OpenNode a v -> Graph s BackFun a v+unsafeConstructGraph m x = Graph (NodeMap.map mkExpr m) (mkExpr x)+  where+    mkExpr :: forall x. OpenNode a x -> Node s BackFun a x+    mkExpr = \case+      OpenNode terms -> Node (mkTerm <$> terms)+    mkTerm :: forall x. OpenEdge a x -> Edge s BackFun a x+    mkTerm = \case+      OpenEdge f x' -> Edge f (mkArg x')+    mkArg :: forall u. OpenEndpoint a u -> Endpoint s a u+    mkArg = \case+      OpenSourceNode -> SourceNode+      OpenInnerNode key -> case NodeMap.tryLookup m key of+        Just (key', _value) -> InnerNode key'+        Nothing -> error ("Downhill: invalid key in constructGraph\n" ++ prettyCallStack callStack)++-- | Will crash if graph has invalid keys+unsafeFromOpenGraph :: (BasicVector a, HasCallStack) => OpenGraph a v -> SomeGraph BackFun a v+unsafeFromOpenGraph (OpenGraph x m) =+  case NodeMap.fromOpenMap m of+    SomeNodeMap m' -> SomeGraph (unsafeConstructGraph m' x)
+ src/Downhill/Internal/Graph/NodeMap.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.Internal.Graph.NodeMap+  ( -- * NodeMap+    NodeMap,+    NodeKey,+    -- * Construction+    fromOpenMap,+    generate,+    -- * Query+    lookup,+    tryLookup,+    toList,+    toListWith,+    elems,+    -- * Modify+    map,+    mapWithKey,+    adjust,+    zipWith,+    -- * Node Set+    IsNodeSet,+    SomeNodeMap (..),+    KeyAndValue (..),+  )+where++import Control.Applicative (Const)+import Data.Data (Proxy (Proxy))+import Data.Reflection (Reifies (reflect), reify)+import Downhill.Internal.Graph.OpenMap (OpenKey, OpenMap, SomeOpenItem (SomeOpenItem))+import qualified Downhill.Internal.Graph.OpenMap as OpenMap+import Prelude (Maybe (Just, Nothing), const, error, (.), (<$>))++type role NodeKey nominal nominal++-- | Valid key, guaranteed to be a member of @s@+newtype NodeKey s x = NodeKey (OpenKey x)++-- | @NodeMap s f@ is a map where value of type @f x@ is associated with key @NodeKey s x@.+-- Type variable `s` tracks the set of nodes. Lookups never fail. Maps can+-- be zipped without losing any nodes.+newtype NodeMap s f = NodeMap {unNodeMap :: OpenMap f}++data KeyAndValue s f = forall x. KeyAndValue (NodeKey s x) (f x)++class IsNodeSet s where+  allNodes :: OpenMap Proxy++map :: forall s f g. (forall v. f v -> g v) -> NodeMap s f -> NodeMap s g+map f = NodeMap . OpenMap.map f . unNodeMap++mapWithKey :: forall s f g. (forall x. NodeKey s x -> f x -> g x) -> NodeMap s f -> NodeMap s g+mapWithKey f (NodeMap x) = NodeMap (OpenMap.mapWithKey f' x)+  where+    f' :: OpenKey dx -> f dx -> g dx+    f' key' = f (NodeKey key')++toList :: NodeMap s f -> [KeyAndValue s f]+toList = toListWith KeyAndValue++toListWith :: forall s f r. (forall x. NodeKey s x -> f x -> r) -> NodeMap s f -> [r]+toListWith f (NodeMap m) = wrap <$> OpenMap.toList m+  where+    wrap :: SomeOpenItem f -> r+    wrap (SomeOpenItem key value) = f (NodeKey key) value++elems :: NodeMap s (Const b) -> [b]+elems (NodeMap m) = OpenMap.elems m++lookup :: NodeMap s f -> NodeKey s v -> f v+lookup (NodeMap m) (NodeKey key) =+  case OpenMap.lookup m key of+    Just x -> x+    Nothing -> error "oh fuck"++-- | If key belongs to @s@, @tryLookup@ will return a proof of this fact+-- and a corresponding value from the map. Otherwise returns @Nothing@.+tryLookup :: NodeMap s f -> OpenKey x -> Maybe (NodeKey s x, f x)+tryLookup (NodeMap m) key =+  case OpenMap.lookup m key of+    Just x -> Just (NodeKey key, x)+    Nothing -> Nothing++generate :: forall s f. IsNodeSet s => (forall x. NodeKey s x -> f x) -> NodeMap s f+generate f = case allNodes @s of+  m -> mapWithKey (\key _ -> f key) (NodeMap m)++zipWith :: forall s f g h. (forall x. f x -> g x -> h x) -> NodeMap s f -> NodeMap s g -> NodeMap s h+zipWith f (NodeMap x) (NodeMap y) = NodeMap (OpenMap.intersectionWith f x y)++adjust :: forall s f x. (f x -> f x) -> NodeKey s x -> NodeMap s f -> NodeMap s f+adjust f (NodeKey key) (NodeMap m) = NodeMap (OpenMap.adjust f key m)++data NodeSetWrapper s++instance Reifies s (OpenMap Proxy) => IsNodeSet (NodeSetWrapper s) where+  allNodes = reflect @s Proxy++-- | 'NodeMap' with existential set of nodes.+data SomeNodeMap f where+  SomeNodeMap :: IsNodeSet s => NodeMap s f -> SomeNodeMap f++fromOpenMap :: forall f. OpenMap f -> SomeNodeMap f+fromOpenMap x = reify nodes go+  where+    nodes :: OpenMap Proxy+    nodes = OpenMap.map (const Proxy) x+    go :: forall s. Reifies s (OpenMap Proxy) => Proxy s -> SomeNodeMap f+    go _proxy = SomeNodeMap @(NodeSetWrapper s) (NodeMap x)
+ src/Downhill/Internal/Graph/OpenGraph.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Downhill.Internal.Graph.OpenGraph+  ( OpenEdge (..),+    OpenEndpoint (..),+    OpenNode (..),+    OpenGraph (..),+    recoverSharing,+  )+where++import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.State.Strict (StateT (..), get, modify)+import Downhill.Internal.Graph.OpenMap (OpenKey, OpenMap)+import qualified Downhill.Internal.Graph.OpenMap as OpenMap+import Downhill.Internal.Graph.Types (BackFun (BackFun))+import Downhill.Linear.Expr (BasicVector, Expr (ExprSum, ExprVar), Term (..))+import Prelude hiding (lookup)++data OpenEndpoint a v where+  OpenSourceNode :: OpenEndpoint a a+  OpenInnerNode :: OpenKey v -> OpenEndpoint a v++data OpenEdge a v where+  OpenEdge :: BackFun u v -> OpenEndpoint a u -> OpenEdge a v++data OpenNode a v = BasicVector v => OpenNode [OpenEdge a v]++-- | Maintains a cache of visited 'Expr's.+newtype TreeBuilder a r = TreeCache {unTreeCache :: StateT (OpenMap (OpenNode a)) IO r}+  deriving (Functor, Applicative, Monad)++insertIntoCache :: OpenKey dv -> OpenNode a dv -> TreeBuilder a ()+insertIntoCache name value = TreeCache $ modify (OpenMap.insert name value)++-- | @buildExpr action key@ will run @action@, associate result with @key@ and+-- store it in cache. If @key@ is already in cache, @action@ will not be run.+buildExpr ::+  TreeBuilder a (OpenNode a v) ->+  Expr a v ->+  TreeBuilder a (OpenKey v, OpenNode a v)+buildExpr action key = do+  name <- TreeCache (lift (OpenMap.makeOpenKey key))+  cache <- TreeCache get+  case OpenMap.lookup cache name of+    Just x -> return (name, x)+    Nothing -> do+      value <- action+      insertIntoCache name value+      return (name, value)++runTreeBuilder :: forall a g dv. TreeBuilder a (g dv) -> IO (g dv, OpenMap (OpenNode a))+runTreeBuilder rs_x = runStateT (unTreeCache rs_x) OpenMap.empty++-- | Computational graph under construction. "Open" refers to the set of the nodes – new nodes can be+-- added to this graph. Once the graph is complete the set of nodes will be frozen+-- and the type of the graph will become 'Graph' ("Downhill.Internal.Graph" module).+data OpenGraph a z = OpenGraph (OpenNode a z) (OpenMap (OpenNode a))++goEdges :: BasicVector v => [Term a v] -> TreeBuilder a (OpenNode a v)+goEdges xs = do+  xs' <- traverse goSharing4term xs+  return $ OpenNode xs'++goSharing4arg :: forall a v. Expr a v -> TreeBuilder a (OpenEndpoint a v)+goSharing4arg key = case key of+  ExprVar -> return OpenSourceNode+  ExprSum xs -> do+    (gRef, _) <- buildExpr (goEdges xs) key+    return (OpenInnerNode gRef)++goSharing4term :: forall a v. Term a v -> TreeBuilder a (OpenEdge a v)+goSharing4term = \case+  Term f arg -> do+    arg' <- goSharing4arg arg+    return (OpenEdge (BackFun f) arg')++-- | Collects duplicate nodes in 'Expr' tree and converts it to a graph.+recoverSharing :: forall a z. BasicVector z => [Term a z] -> IO (OpenGraph a z)+recoverSharing xs = do+  (final_node, graph) <- runTreeBuilder (goEdges xs)+  return (OpenGraph final_node graph)
+ src/Downhill/Internal/Graph/OpenMap.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}++module Downhill.Internal.Graph.OpenMap+  ( -- * OpenMap+    OpenMap,+    OpenKey,+    SomeOpenItem (SomeOpenItem),+    -- * Construction+    makeOpenKey,+    empty,+    insert,+    -- * Query+    lookup,+    toList,+    elems,+    -- * Modify+    map,+    mapWithKey,+    mapMaybe,+    adjust,+    intersectionWith,+  )+where++import Control.Applicative (Const (Const))+import Control.Exception (evaluate)+import Data.HashMap.Lazy (HashMap)+import qualified Data.HashMap.Lazy as HashMap+import Data.Kind (Type)+import GHC.Base (Any, Maybe (Just, Nothing), coerce)+import GHC.StableName (StableName)+import System.Mem.StableName (makeStableName)+import Unsafe.Coerce (unsafeCoerce)+import Prelude (Functor (fmap), IO, Monad (return), (.), (<$>))++data SomeExpr f = forall v. SomeExpr (f v)++-- | A key of @OpenMap@.+newtype OpenKey x = OpenKey (StableName Any)++-- | Heterogeneous map with 'StableName' as a key.+newtype OpenMap (f :: Type -> Type) = OpenMap {unOpenMap :: HashMap (StableName Any) (SomeExpr f)}++-- | Key and value.+data SomeOpenItem f = forall x. SomeOpenItem (OpenKey x) (f x)++empty :: OpenMap f+empty = OpenMap HashMap.empty++map :: forall f g. (forall x. f x -> g x) -> OpenMap f -> OpenMap g+map f = OpenMap . fmap go . unOpenMap+  where+    go (SomeExpr y) = SomeExpr (f y)++mapMaybe :: forall f g. (forall x. f x -> Maybe (g x)) -> OpenMap f -> OpenMap g+mapMaybe f = OpenMap . HashMap.mapMaybe go . unOpenMap+  where+    go (SomeExpr y) = case f y of+      Just fy -> Just (SomeExpr fy)+      Nothing -> Nothing++mapWithKey :: forall f g. (forall d. OpenKey d -> f d -> g d) -> OpenMap f -> OpenMap g+mapWithKey f = OpenMap . HashMap.mapWithKey go . unOpenMap+  where+    go key (SomeExpr y) = SomeExpr (f (OpenKey key) y)++lookup :: OpenMap f -> OpenKey x -> Maybe (f x)+lookup (OpenMap m) (OpenKey k) = unsafeCastTypeSomeExpr <$> HashMap.lookup k m++toList :: OpenMap f -> [SomeOpenItem f]+toList = fmap wrap . HashMap.toList . unOpenMap+  where+    wrap :: (StableName Any, SomeExpr f) -> SomeOpenItem f+    wrap (key, x) = case x of+      SomeExpr x' -> SomeOpenItem (OpenKey key) x'++elems :: OpenMap (Const b) -> [b]+elems = fmap unSomeExpr . HashMap.elems . unOpenMap+  where+    unSomeExpr :: SomeExpr (Const r) -> r+    unSomeExpr (SomeExpr (Const x)) = x++unsafeCastTypeSomeExpr :: SomeExpr f -> f v+unsafeCastTypeSomeExpr = \case+  SomeExpr x -> unsafeCoerce x++intersectionWith :: forall f g h. (forall x. f x -> g x -> h x) -> OpenMap f -> OpenMap g -> OpenMap h+intersectionWith f (OpenMap x) (OpenMap y) = OpenMap (HashMap.intersectionWith f' x y)+  where+    f' (SomeExpr x') sy = SomeExpr (f x' y')+      where+        y' = unsafeCastTypeSomeExpr sy++insert :: forall f dx. OpenKey dx -> f dx -> OpenMap f -> OpenMap f+insert (OpenKey k) x (OpenMap m) = OpenMap (HashMap.insert k (SomeExpr x) m)++adjust :: forall f x. (f x -> f x) -> OpenKey x -> OpenMap f -> OpenMap f+adjust f (OpenKey key) (OpenMap m) = OpenMap m'+  where+    m' = HashMap.adjust f' key m+    f' x = SomeExpr (f (unsafeCastTypeSomeExpr x))++makeOpenKey :: f v -> IO (OpenKey v)+makeOpenKey x = do+  x' <- evaluate x+  z <- makeStableName x'+  return (OpenKey (coerce z))
+ src/Downhill/Internal/Graph/Types.hs view
@@ -0,0 +1,37 @@+{-| Types of nodes and edges of the computational graph.++Parameters:++  * @p@ - is parent node; might be 'OpenKey' or 'NodeKey'++  * @e@ - edge type++  * @a@ - type of the initial node of expression++  * @v@ - type of the node.+-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE GADTs #-}+module Downhill.Internal.Graph.Types+(+  -- * Linear functions+  BackFun(..), FwdFun(..),+  flipBackFun, flipFwdFun+)+ where++import Downhill.Linear.Expr (BasicVector (VecBuilder))+++-- | Edge type for backward mode evaluation+newtype BackFun u v = BackFun {unBackFun :: v -> VecBuilder u}++-- | Edge type for forward mode evaluation+newtype FwdFun u v = FwdFun {unFwdFun :: u -> VecBuilder v}++flipBackFun :: BackFun u v -> FwdFun v u+flipBackFun (BackFun f) = FwdFun f++flipFwdFun :: FwdFun u v -> BackFun v u+flipFwdFun (FwdFun f) = BackFun f+
+ src/Downhill/Linear/BackGrad.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeApplications #-}++module Downhill.Linear.BackGrad+  ( BackGrad (..),+    realNode,+    inlineNode,+    sparseNode,+    castBackGrad,+  )+where++import Data.VectorSpace+  ( AdditiveGroup (..),+    Scalar,+    VectorSpace (..),+  )+import Downhill.Linear.Expr+  ( BasicVector (VecBuilder),+    Expr (ExprSum),+    FullVector (identityBuilder, negateBuilder, scaleBuilder),+    Term (Term), SparseVector (unSparseVector),+  )++-- | Linear expression, made for backpropagation.+-- It is similar to @'Expr' 'BackFun'@, but has a more flexible form.+newtype BackGrad a v+  = BackGrad+      ( forall x.+        (x -> VecBuilder v) ->+        Term a x+      )++-- | Creates a @BackGrad@ that is backed by a real node. Gradient of type @v@ will be computed and stored+--   in a graph for this node.+{-# ANN module "HLint: ignore Avoid lambda using `infix`" #-}++realNode :: Expr a v -> BackGrad a v+realNode x = BackGrad (\f -> Term f x)++-- | @inlineNode f x@ will apply function @f@ to variable @x@ without creating a node. All of the gradients+-- coming to this expression will be forwarded to the parents of @x@. However, if this expression is used+-- more than once, @f@ will be evaluated multiple times, too. It is intended to be used for @newtype@ wrappers.+-- @inlineNode f x@ also doesn't prevent+-- compiler to inline and optimize @x@+inlineNode ::+  forall r u v.+  (VecBuilder v -> VecBuilder u) ->+  BackGrad r u ->+  BackGrad r v+inlineNode f (BackGrad g) = BackGrad go+  where+    go :: forall x. (x -> VecBuilder v) -> Term r x+    go h = g (f . h)++sparseNode ::+  forall r a z.+  BasicVector z =>+  (VecBuilder z -> VecBuilder a) ->+  BackGrad r a ->+  BackGrad r z+sparseNode fa (BackGrad x) = castBackGrad (realNode node)+  where+    fa' = fa . unSparseVector+    node :: Expr r (SparseVector z)+    node = ExprSum [x fa']++-- | @BackGrad@ doesn't track the type of the node. Type of @BackGrad@ can be changed freely+-- as long as @VecBuilder@ stays the same.+castBackGrad ::+  forall r v z.+  VecBuilder z ~ VecBuilder v =>+  BackGrad r v ->+  BackGrad r z+castBackGrad (BackGrad g) = BackGrad g++instance (FullVector v) => AdditiveGroup (BackGrad r v) where+  zeroV = realNode (ExprSum [])+  negateV (BackGrad x) = realNode (ExprSum [x negateBuilder])+  BackGrad x ^+^ BackGrad y = realNode (ExprSum [x identityBuilder, y identityBuilder])+  BackGrad x ^-^ BackGrad y = realNode (ExprSum [x identityBuilder, y negateBuilder])++instance FullVector v => VectorSpace (BackGrad r v) where+  type Scalar (BackGrad r v) = Scalar v+  a *^ BackGrad v = realNode (ExprSum [v (scaleBuilder a)])
+ src/Downhill/Linear/Backprop.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module Downhill.Linear.Backprop+  ( -- * Backpropagation+    backprop,++    -- * Graph+    buildGraph,+    --abstractBackprop,+  )+where++import Downhill.Internal.Graph.Graph+  ( SomeGraph (..),+    evalGraph,+    transposeGraph,+  )+import qualified Downhill.Internal.Graph.Graph as Graph+import Downhill.Internal.Graph.OpenGraph (recoverSharing)+import Downhill.Internal.Graph.Types (BackFun, flipBackFun)+import Downhill.Linear.BackGrad (BackGrad (..), castBackGrad)+import Downhill.Linear.Expr+  ( BasicVector (VecBuilder),+    FullVector (identityBuilder),+    SparseVector (SparseVector, unSparseVector),+    Term,+  )+import GHC.IO.Unsafe (unsafePerformIO)++buildGraph ::+  forall a v.+  (BasicVector a, BasicVector v) =>+  [Term a v] ->+  IO (SomeGraph BackFun a v)+buildGraph fidentityBuilder = do+  og <- recoverSharing fidentityBuilder+  return (Graph.unsafeFromOpenGraph og)++abstractBackprop ::+  forall a u v.+  (BasicVector a, BasicVector u, BasicVector v) =>+  BackGrad a u ->+  (v -> VecBuilder u) ->+  v ->+  a+abstractBackprop (BackGrad f) builder x =+  case unsafePerformIO (buildGraph [f builder]) of+    SomeGraph g -> evalGraph (transposeGraph flipBackFun g) x++_backprop :: forall a v. (BasicVector a, BasicVector v) => BackGrad a v -> VecBuilder v -> a+_backprop dvar x =+  abstractBackprop @a @(SparseVector v) @(SparseVector v)+    sparseDVar+    unSparseVector+    (SparseVector x)+  where+    sparseDVar :: BackGrad a (SparseVector v)+    sparseDVar = castBackGrad dvar++-- | Purity of this function depends on laws of arithmetic+-- and linearity law of 'Term'. If your addition is approximately+-- associative, then this function is approximately pure. Fair?+backprop :: forall a v. (BasicVector a, FullVector v) => BackGrad a v -> v -> a+backprop dvar = abstractBackprop dvar identityBuilder
+ src/Downhill/Linear/Expr.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Downhill.Linear.Expr+  ( -- * Expression+    Expr (..),+    Term (..),++    -- * Vectors+    BasicVector (..),+    FullVector (..),+    SparseVector (..),+    DenseVector (..),+    DenseBuilder (..),+    toDenseBuilder,++    -- * Misc+    maybeToMonoid,+  )+where++import Data.Kind (Type)+import Data.Maybe (fromMaybe)+import Data.Semigroup (Sum (Sum, getSum))+import Data.VectorSpace (AdditiveGroup (..), VectorSpace (..))++-- | Argument @f@ in @Term f x@ must be /linear/ function. That's a law.+data Term a v where+  Term :: (v -> VecBuilder u) -> Expr a u -> Term a v++-- | @Expr a v@ represents a linear expression of type @v@, containing some free variables of type @a@.+data Expr a v where+  ExprVar :: Expr a a+  ExprSum :: BasicVector v => [Term a v] -> Expr a v++class Monoid (VecBuilder v) => BasicVector v where+  -- | @VecBuilder v@ is a sparse representation of vector @v@. Edges of a computational graph+  -- produce builders, which are then summed into vectors in nodes. Monoid operation '<>'+  -- means addition of vectors, but it doesn't need to compute the sum immediately - it+  -- might defer computation until 'sumBuilder' is evaluated.+  --+  -- @+  -- sumBuilder mempty = zeroV+  -- sumBuilder (x <> y) = sumBuilder x ^+^ sumBuilder y+  -- @+  --+  -- 'mempty' must be cheap. '<>' must be O(1).+  type VecBuilder v :: Type++  sumBuilder :: VecBuilder v -> v++maybeToMonoid :: Monoid m => Maybe m -> m+maybeToMonoid = fromMaybe mempty++instance BasicVector Integer where+  type VecBuilder Integer = Sum Integer+  sumBuilder = getSum++instance (BasicVector a, BasicVector b) => BasicVector (a, b) where+  type VecBuilder (a, b) = Maybe (VecBuilder a, VecBuilder b)+  sumBuilder = sumPair . maybeToMonoid+    where+      sumPair (a, b) = (sumBuilder a, sumBuilder b)++instance (BasicVector a, BasicVector b, BasicVector c) => BasicVector (a, b, c) where+  type VecBuilder (a, b, c) = Maybe (VecBuilder a, VecBuilder b, VecBuilder c)+  sumBuilder = sumTriple . maybeToMonoid+    where+      sumTriple (a, b, c) = (sumBuilder a, sumBuilder b, sumBuilder c)++instance BasicVector Float where+  type VecBuilder Float = Sum Float+  sumBuilder = getSum++instance BasicVector Double where+  type VecBuilder Double = Sum Double+  sumBuilder = getSum++-- | Full-featured vector.+--+-- Gradients are linear functions and form a vector space.+-- @FullVector@ class provides functionality that is needed to+-- make 'VectorSpace' instances.+class (BasicVector v, VectorSpace v) => FullVector v where+  identityBuilder :: v -> VecBuilder v+  negateBuilder :: v -> VecBuilder v+  scaleBuilder :: Scalar v -> v -> VecBuilder v++instance FullVector Float where+  identityBuilder = Sum+  negateBuilder = Sum . negate+  scaleBuilder x = Sum . (x *)++instance FullVector Double where+  identityBuilder = Sum+  negateBuilder = Sum . negate+  scaleBuilder x = Sum . (x *)++instance FullVector Integer where+  identityBuilder = Sum+  negateBuilder = Sum . negate+  scaleBuilder x = Sum . (x *)++instance (Scalar a ~ Scalar b, FullVector a, FullVector b) => FullVector (a, b) where+  identityBuilder (x, y) = Just (identityBuilder x, identityBuilder y)+  negateBuilder (x, y) = Just (negateBuilder x, negateBuilder y)+  scaleBuilder a (x, y) = Just (scaleBuilder a x, scaleBuilder a y)++instance (s ~ Scalar a, s ~ Scalar b, s ~ Scalar c, FullVector a, FullVector b, FullVector c) => FullVector (a, b, c) where+  identityBuilder (x, y, z) = Just (identityBuilder x, identityBuilder y, identityBuilder z)+  negateBuilder (x, y, z) = Just (negateBuilder x, negateBuilder y, negateBuilder z)+  scaleBuilder a (x, y, z) = Just (scaleBuilder a x, scaleBuilder a y, scaleBuilder a z)++-- |  Normally graph node would compute the sum of gradients and then+-- propagate it to ancestor nodes. That's the best strategy when+-- some computation needs to be performed for backpropagation.+-- Some operations, like constructing/deconstructing tuples or+-- wrapping/unwrapping, don't need to compute the sum. Doing so only+-- destroys sparsity. A node of type @SparseVector v@ won't sum+-- the gradients, it will simply forward builders to its parents.+newtype SparseVector v = SparseVector+  {unSparseVector :: VecBuilder v}++deriving via (VecBuilder v) instance Semigroup (VecBuilder v) => Semigroup (SparseVector v)++instance Monoid (VecBuilder v) => BasicVector (SparseVector v) where+  type VecBuilder (SparseVector v) = VecBuilder v+  sumBuilder = SparseVector++newtype DenseSemibuilder v = DenseSemibuilder {_unDenseSemibuilder :: v}++instance AdditiveGroup v => Semigroup (DenseSemibuilder v) where+  DenseSemibuilder x <> DenseSemibuilder y = DenseSemibuilder (x ^+^ y)++newtype DenseBuilder v = DenseBuilder (Maybe v)+  deriving (Semigroup, Monoid) via (Maybe (DenseSemibuilder v))++toDenseBuilder :: v -> DenseBuilder v+toDenseBuilder = DenseBuilder . Just++-- | When sparsity is not needed, we can use vector @v@ as a builder of itself.+-- @DenseVector@ takes care of that.+newtype DenseVector v = DenseVector v+  deriving (AdditiveGroup, VectorSpace) via v++instance AdditiveGroup v => BasicVector (DenseVector v) where+  type VecBuilder (DenseVector v) = DenseBuilder v+  sumBuilder (DenseBuilder Nothing) = DenseVector zeroV+  sumBuilder (DenseBuilder (Just x)) = DenseVector x++instance VectorSpace v => FullVector (DenseVector v) where+  identityBuilder (DenseVector v) = DenseBuilder (Just v)+  negateBuilder (DenseVector v) = DenseBuilder (Just (negateV v))+  scaleBuilder a (DenseVector v) = DenseBuilder (Just (a *^ v))++instance FullVector v => AdditiveGroup (Expr a v) where+  zeroV = ExprSum []+  negateV x = ExprSum [Term negateBuilder x]+  x ^+^ y = ExprSum [Term identityBuilder x, Term identityBuilder y]+  x ^-^ y = ExprSum [Term identityBuilder x, Term negateBuilder y]++instance FullVector dv => VectorSpace (Expr da dv) where+  type Scalar (Expr da dv) = Scalar dv+  a *^ v = ExprSum [Term (scaleBuilder a) v]
+ src/Downhill/Linear/Lift.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-- | While 'BackGrad' is intended to be simple to construct manually, this module provides a way to do+--   that with a bit less of boilerplate.+module Downhill.Linear.Lift+  ( -- * Lifts+    lift1,+    lift2,+    lift3,++    -- * Dense lifts+    lift1_dense,+    lift2_dense,+    lift3_dense,++    -- * Lifts for 'SparseVector'+    lift1_sparse,+    lift2_sparse,+    lift3_sparse,+  )+where++import Downhill.Linear.BackGrad (BackGrad (..), castBackGrad, realNode)+import Downhill.Linear.Expr (BasicVector (..), Expr (ExprSum), FullVector (identityBuilder), SparseVector (unSparseVector))+import Prelude hiding (fst, snd, zip)++lift1 ::+  forall z r a.+  BasicVector z =>+  (z -> VecBuilder a) ->+  BackGrad r a ->+  BackGrad r z+lift1 fa (BackGrad da) = realNode node+  where+    node = ExprSum [da fa]++lift2 ::+  forall z r a b.+  BasicVector z =>+  (z -> VecBuilder a) ->+  (z -> VecBuilder b) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r z+lift2 fa fb (BackGrad da) (BackGrad db) = realNode node+  where+    node = ExprSum [da fa, db fb]++lift3 ::+  forall z r a b c.+  BasicVector z =>+  (z -> VecBuilder a) ->+  (z -> VecBuilder b) ->+  (z -> VecBuilder c) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r c ->+  BackGrad r z+lift3 fa fb fc (BackGrad da) (BackGrad db) (BackGrad dc) = realNode node+  where+    node = ExprSum [da fa, db fb, dc fc]++-- | Same as 'sparseNode', included here for completeness.+lift1_sparse ::+  forall r a z.+  BasicVector z =>+  (VecBuilder z -> VecBuilder a) ->+  BackGrad r a ->+  BackGrad r z+lift1_sparse fa = castBackGrad . lift1 @(SparseVector z) fa'+  where+    fa' = fa . unSparseVector++lift2_sparse ::+  forall r a b z.+  BasicVector z =>+  (VecBuilder z -> VecBuilder a) ->+  (VecBuilder z -> VecBuilder b) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r z+lift2_sparse fa fb a b = castBackGrad $ lift2 @(SparseVector z) fa' fb' a b+  where+    fa' = fa . unSparseVector+    fb' = fb . unSparseVector++lift3_sparse ::+  forall r a b c z.+  BasicVector z =>+  (VecBuilder z -> VecBuilder a) ->+  (VecBuilder z -> VecBuilder b) ->+  (VecBuilder z -> VecBuilder c) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r c ->+  BackGrad r z+lift3_sparse fa fb fc a b c =+  castBackGrad $+    lift3 @(SparseVector z) fa' fb' fc' a b c+  where+    fa' = fa . unSparseVector+    fb' = fb . unSparseVector+    fc' = fc . unSparseVector++lift1_dense ::+  (BasicVector v, FullVector a) =>+  ((v -> a) -> BackGrad r a -> BackGrad r v)+lift1_dense fa = lift1 (identityBuilder . fa)++lift2_dense ::+  (BasicVector v, FullVector a, FullVector b) =>+  (v -> a) ->+  (v -> b) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r v+lift2_dense fa fb = lift2 (identityBuilder . fa) (identityBuilder . fb)++lift3_dense ::+  (BasicVector v, FullVector a, FullVector b, FullVector c) =>+  (v -> a) ->+  (v -> b) ->+  (v -> c) ->+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r c ->+  BackGrad r v+lift3_dense fa fb fc = lift3 (identityBuilder . fa) (identityBuilder . fb) (identityBuilder . fc)
+ src/Downhill/Linear/Prelude.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Downhill.Linear.Prelude+  ( pattern T2,+    pattern T3,+  )+where++import Downhill.Linear.BackGrad (BackGrad)+import Downhill.Linear.Expr (BasicVector (VecBuilder), maybeToMonoid)+import qualified Downhill.Linear.Lift as Lift+import Prelude (Maybe (Just), Monoid (mempty), fmap, (.))+import qualified Prelude++splitPair :: forall r a b. (BasicVector a, BasicVector b) => BackGrad r (a, b) -> (BackGrad r a, BackGrad r b)+splitPair x = (bg1, bg2)+  where+    go1 :: VecBuilder a -> VecBuilder (a, b)+    go2 :: VecBuilder b -> VecBuilder (a, b)+    go1 da = Just (da, mempty)+    go2 db = Just (mempty, db)+    bg1 :: BackGrad r a+    bg2 :: BackGrad r b+    bg1 = Lift.lift1_sparse go1 x+    bg2 = Lift.lift1_sparse go2 x++toTriple ::+  forall r a b c.+  (BasicVector a, BasicVector b, BasicVector c) =>+  BackGrad r (a, b, c) ->+  (BackGrad r a, BackGrad r b, BackGrad r c)+toTriple x = (bg1, bg2, bg3)+  where+    go1 :: VecBuilder a -> VecBuilder (a, b, c)+    go2 :: VecBuilder b -> VecBuilder (a, b, c)+    go3 :: VecBuilder c -> VecBuilder (a, b, c)+    go1 da = Just (da, mempty, mempty)+    go2 db = Just (mempty, db, mempty)+    go3 dc = Just (mempty, mempty, dc)+    bg1 :: BackGrad r a+    bg2 :: BackGrad r b+    bg3 :: BackGrad r c+    bg1 = Lift.lift1_sparse go1 x+    bg2 = Lift.lift1_sparse go2 x+    bg3 = Lift.lift1_sparse go3 x++-- |+--+-- @+-- getFst :: (BasicVector (DualOf a), BasicVector (DualOf b)) => BackGrad r (a, b) -> BackGrad r a+-- getFst (T2 x _) = x+-- @+--+-- @+-- mkPair :: (BasicVector (DualOf a), BasicVector (DualOf b)) => BackGrad r a -> BackGrad r b -> BackGrad r (a, b)+-- mkPair x y = (T2 x y)+-- @+{-# COMPLETE T2 #-}++pattern T2 :: forall r a b. (BasicVector a, BasicVector b) => BackGrad r a -> BackGrad r b -> BackGrad r (a, b)+pattern T2 a b <-+  (splitPair -> (a, b))+  where+    T2 a b = Lift.lift2_sparse go1 go2 a b+      where+        go1 :: VecBuilder (a, b) -> VecBuilder a+        go2 :: VecBuilder (a, b) -> VecBuilder b+        go1 = maybeToMonoid . fmap Prelude.fst+        go2 = maybeToMonoid . fmap Prelude.snd++{-# COMPLETE T3 #-}++pattern T3 ::+  forall r a b c.+  (BasicVector a, BasicVector b, BasicVector c) =>+  BackGrad r a ->+  BackGrad r b ->+  BackGrad r c ->+  BackGrad r (a, b, c)+pattern T3 a b c <-+  (toTriple -> (a, b, c))+  where+    T3 a b c = Lift.lift3_sparse go1 go2 go3 a b c+      where+        go1 :: VecBuilder (a, b, c) -> VecBuilder a+        go2 :: VecBuilder (a, b, c) -> VecBuilder b+        go3 :: VecBuilder (a, b, c) -> VecBuilder c+        go1 = maybeToMonoid . fmap (\(x, _, _) -> x)+        go2 = maybeToMonoid . fmap (\(_, x, _) -> x)+        go3 = maybeToMonoid . fmap (\(_, _, x) -> x)
+ src/Downhill/TH.hs view
@@ -0,0 +1,917 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Use like this:+--+-- @+-- mkHasGradInstances+--   defaultBVarOptions+--   [d|+--     instance HasGrad MyRecord where+--       type MScalar MyRecord = Float+--     |]+-- @+--+-- Instance declaration passed to @mkHasGradInstances@ gives two important bits of information:+--+--   * Type variables for @MyRecord@, which can be concrete types (such as @instance HasGrad (MyRecord Float)@)+--     or regular type variables (@instance HasGrad (MyRecord a)@)+--+--   * Scalar type.+--+module Downhill.TH+  (+    mkHasGradInstances,+    AffineSpaceOptions (..),+    RecordNamer (..),+    BVarOptions (..),+    defaultBVarOptions,+  )+where++import Control.Monad+import Data.AdditiveGroup ((^+^), (^-^))+import Data.AffineSpace (AffineSpace (Diff, (.+^), (.-.)))+import Data.Foldable (traverse_)+import qualified Data.Map as Map+import Data.Maybe (catMaybes)+import Data.VectorSpace (AdditiveGroup (negateV, zeroV), VectorSpace (Scalar, (*^)))+import Downhill.BVar (BVar (BVar))+import Downhill.Grad+  ( Dual (evalGrad),+    HasGrad (Grad, MScalar, Metric, Tang),+    MetricTensor (MtCovector, MtVector, evalMetric, sqrNorm),+  )+import Downhill.Linear.Expr (BasicVector (VecBuilder, sumBuilder))+import Downhill.Linear.Lift (lift1_sparse)+import GHC.Records (HasField (getField))+import Language.Haskell.TH+  ( Bang (Bang),+    Con (NormalC, RecC),+    Cxt,+    Dec (DataD, InstanceD, NewtypeD, SigD),+    Exp (AppE, ConE, InfixE, VarE),+    Name,+    Pat (VarP),+    Q,+    SourceStrictness (NoSourceStrictness),+    SourceUnpackedness (NoSourceUnpackedness),+    Type (AppT, ConT, VarT),+    nameBase,+    newName,+  )+import Language.Haskell.TH.Datatype (ConstructorInfo (constructorFields, constructorName, constructorVariant), ConstructorVariant (InfixConstructor, NormalConstructor, RecordConstructor), DatatypeInfo (datatypeCons, datatypeInstTypes, datatypeName, datatypeVariant, datatypeVars), DatatypeVariant (Newtype), TypeSubstitution (applySubstitution), reifyDatatype)+import Language.Haskell.TH.Datatype.TyVarBndr (TyVarBndrUnit)+import Language.Haskell.TH.Syntax+  ( BangType,+    Body (NormalB),+    Clause (Clause),+    Dec (FunD, TySynInstD, ValD),+    Exp (AppTypeE),+    TyLit (StrTyLit),+    TySynEqn (TySynEqn),+    Type (ArrowT, EqualityT, LitT, SigT),+    VarBangType,+    mkNameS,+  )+import qualified  Language.Haskell.TH++data DatatypeFields+  = NormalFields [Type]+  | RecordFields [(String, Type)]+  deriving (Show)++data DownhillRecord = DownhillRecord+  { ddtTypeConName :: Name,+    ddtDataConName :: Name,+    ddtFieldTypes :: [Type],+    ddtFieldNames :: Maybe [String],+    ddtTypeVars :: [TyVarBndrUnit],+    ddtFieldCount :: Int,+    ddtVariant :: DatatypeVariant+  }+  deriving (Show)++data RecordNamer = RecordNamer+  { typeConNamer :: String -> String,+    dataConNamer :: String -> String,+    fieldNamer :: String -> String+  }++data RecordTranstorm = RecordTranstorm RecordNamer (Type -> Type)++data AffineSpaceOptions+  = -- | Generate AffineSpace instance+    MakeAffineSpace+  | -- | Don't generate AffineSpace instance+    NoAffineSpace+  | -- | Generate AffineSpace instance if @optExcludeFields@ is empty+    AutoAffineSpace++data BVarOptions = BVarOptions+  { optTangNamer :: RecordNamer,+    optGradNamer :: RecordNamer,+    optMetricNamer :: RecordNamer,+    optBuilderNamer :: RecordNamer,+    optAffineSpace :: AffineSpaceOptions,+     -- | List of fields that take no part in differentiation+    optExcludeFields :: [String]+  }++pattern ConP :: Name -> [Pat] -> Pat+#if MIN_VERSION_template_haskell(2,18,0)+pattern ConP x y = Language.Haskell.TH.ConP x [] y+#else+pattern ConP x y = Language.Haskell.TH.ConP x y+#endif++defaultTangRecordNamer :: RecordNamer+defaultTangRecordNamer =+  RecordNamer+    { typeConNamer = (++ "Tang"),+      dataConNamer = (++ "Tang"),+      fieldNamer = id+    }++defaultGradRecordNamer :: RecordNamer+defaultGradRecordNamer =+  RecordNamer+    { typeConNamer = (++ "Grad"),+      dataConNamer = (++ "Grad"),+      fieldNamer = id+    }++defaultMetricRecordNamer :: RecordNamer+defaultMetricRecordNamer =+  RecordNamer+    { typeConNamer = (++ "Metric"),+      dataConNamer = (++ "Metric"),+      fieldNamer = id+    }++defaultBuilderRecordNamer :: RecordNamer+defaultBuilderRecordNamer =+  RecordNamer+    { typeConNamer = (++ "Builder"),+      dataConNamer = (++ "Builder"),+      fieldNamer = id+    }++defaultBVarOptions :: BVarOptions+defaultBVarOptions =+  BVarOptions+    { optTangNamer = defaultTangRecordNamer,+      optGradNamer = defaultGradRecordNamer,+      optMetricNamer = defaultMetricRecordNamer,+      optBuilderNamer = defaultBuilderRecordNamer,+      optAffineSpace = AutoAffineSpace,+      optExcludeFields = []+    }++mkConstructor :: DownhillRecord -> Con+mkConstructor record =+  case ddtFieldNames record of+    Nothing ->+      NormalC newConstrName (map mkType (ddtFieldTypes record))+    Just names ->+      RecC newConstrName (zipWith mkRecType names (ddtFieldTypes record))+  where+    newConstrName :: Name+    newConstrName = ddtDataConName record+    mkRecType :: String -> Type -> VarBangType+    mkRecType name type_ =+      ( mkNameS name,+        Bang NoSourceUnpackedness NoSourceStrictness,+        type_+      )+    mkType :: Type -> BangType+    mkType type_ =+      ( Bang NoSourceUnpackedness NoSourceStrictness,+        type_+      )++parseGradConstructor :: Name -> DatatypeInfo -> ConstructorInfo -> [TyVarBndrUnit] -> Q DownhillRecord+parseGradConstructor tyName dinfo cinfo typevars = do+  let types = constructorFields cinfo+      n = length types+  (fieldTypes, fieldNames) <- case constructorVariant cinfo of+    NormalConstructor -> return (types, Nothing)+    InfixConstructor -> return (types, Nothing)+    RecordConstructor fieldNames -> do+      return (types, Just (nameBase <$> fieldNames))+  return+    DownhillRecord+      { ddtTypeConName = tyName,+        ddtDataConName = constructorName cinfo,+        ddtTypeVars = typevars,+        ddtFieldCount = n,+        ddtFieldTypes = fieldTypes,+        ddtFieldNames = fieldNames,+        ddtVariant = datatypeVariant dinfo+      }++parseDownhillRecord :: Name -> DatatypeInfo -> Q (DownhillRecord, ConstructorInfo)+parseDownhillRecord recordName record' = do+  let name = datatypeName record'+  let typevars = datatypeVars record'+      constructors' = datatypeCons record'+  constr' <- case constructors' of+    [] -> fail (show recordName <> " has no data constructors")+    [constr''] -> return constr''+    _ -> fail (show recordName <> " has multiple data constructors")++  r <- parseGradConstructor name record' constr' typevars+  return (r, constr')++elementwiseOp :: DownhillRecord -> Name -> Q Dec+elementwiseOp record = elementwiseOp' record record record++elementwiseOp' :: DownhillRecord -> DownhillRecord -> DownhillRecord -> Name -> Q Dec+elementwiseOp' leftRecord rightRecord resRecord func = do+  let n = ddtFieldCount resRecord+  --dataConName :: Name+  --dataConName = ddtDataConName record+  xs <- replicateM n (newName "x")+  ys <- replicateM n (newName "y")+  let fieldOp :: Name -> Name -> Exp+      fieldOp x y = InfixE (Just (VarE x)) (VarE func) (Just (VarE y))+      resultFields :: [Exp]+      resultFields = zipWith fieldOp xs ys+      leftPat = ConP (ddtDataConName leftRecord) (map VarP xs)+      rightPat = ConP (ddtDataConName rightRecord) (map VarP ys)+      rhs :: Exp+      rhs = foldl AppE (ConE (ddtDataConName resRecord)) resultFields+      dec =+        FunD+          func+          [ Clause+              [leftPat, rightPat]+              (NormalB rhs)+              []+          ]+  return dec++elementwiseValue :: DownhillRecord -> Name -> Q Dec+elementwiseValue record func = do+  let n = ddtFieldCount record+      dataConName :: Name+      dataConName = ddtDataConName record+      rhs :: Exp+      rhs = foldl AppE (ConE dataConName) (replicate n (VarE 'zeroV))+      dec = ValD (VarP func) (NormalB rhs) []+  return dec++elementwiseFunc :: DownhillRecord -> Name -> Q Dec+elementwiseFunc record func = do+  let n = ddtFieldCount record+      dataConName :: Name+      dataConName = ddtDataConName record+      rhsConName = ddtDataConName record+  xs <- case ddtFieldNames record of+    Nothing -> replicateM n (newName "x")+    Just names -> traverse newName names+  let fieldOp :: Name -> Exp+      fieldOp = AppE (VarE func) . VarE+      resultFields :: [Exp]+      resultFields = map fieldOp xs+      leftPat = ConP dataConName (map VarP xs)+      rhs :: Exp+      rhs = foldl AppE (ConE rhsConName) resultFields+      dec =+        FunD+          func+          [ Clause+              [leftPat]+              (NormalB rhs)+              []+          ]+  return dec++mkClassInstance :: Name -> Cxt -> DownhillRecord -> [Type] -> [Dec] -> Q [Dec]+mkClassInstance className cxt record instVars decs = do+  let recordType = ConT (ddtTypeConName record)+      ihead = AppT (ConT className) (foldl AppT recordType instVars)+  return [InstanceD Nothing cxt ihead decs]++mkSemigroupInstance :: Cxt -> DownhillRecord -> [Type] -> Q [Dec]+mkSemigroupInstance cxt record instVars = do+  dec <- elementwiseOp record '(<>)+  mkClassInstance ''Semigroup cxt record instVars [dec]++mkAdditiveGroupInstance :: Cxt -> DownhillRecord -> [Type] -> Q [Dec]+mkAdditiveGroupInstance cxt record instVars = do+  zeroVDec <- elementwiseValue record 'zeroV+  negateDec <- elementwiseFunc record 'negateV+  plusDec <- elementwiseOp record '(^+^)+  minusDec <- elementwiseOp record '(^-^)+  let decs =+        [ zeroVDec,+          negateDec,+          plusDec,+          minusDec+        ]+  mkClassInstance ''AdditiveGroup cxt record instVars decs++mkVectorSpaceInstance :: DownhillRecord -> Type -> Cxt -> [Type] -> Q [Dec]+mkVectorSpaceInstance record scalarType cxt instVars = do+  let n = ddtFieldCount record+      dataConName :: Name+      dataConName = ddtDataConName record+  xs <- case ddtFieldNames record of+    Nothing -> replicateM n (newName "x")+    Just names -> traverse newName names++  lhsName <- newName "s"+  let rightPat = ConP (ddtDataConName record) (map VarP xs)+      recordType = foldl AppT (ConT (ddtTypeConName record)) instVars+      mulField :: Name -> Exp+      mulField y = InfixE (Just (VarE lhsName)) (VarE '(*^)) (Just (VarE y))+      rhsMulV :: Exp+      rhsMulV = foldl AppE (ConE dataConName) (map mulField xs)+  let vmulDec =+        FunD+          '(*^)+          [ Clause+              [VarP lhsName, rightPat]+              (NormalB rhsMulV)+              []+          ]+      scalarTypeDec =+        TySynInstD+          ( TySynEqn+              Nothing+              (AppT (ConT ''Scalar) recordType)+              scalarType+          )+      decs = [scalarTypeDec, vmulDec]+  mkClassInstance ''VectorSpace cxt record instVars decs++mkBasicVectorInstance :: DownhillRecord -> BVarOptions -> Cxt -> [Type] -> Q [Dec]+mkBasicVectorInstance vectorRecord options cxt instVars = do+  sumBuilderDec <- mkSumBuilder+  mkClassInstance ''BasicVector cxt vectorRecord instVars [vecbuilderDec, sumBuilderDec]+  where+    n = ddtFieldCount vectorRecord+    builderRecord = renameDownhillRecord (builderTransform options) vectorRecord++    -- not an elementiseOp, because right hand side is wrapped in Maybe+    mkSumBuilder :: Q Dec+    mkSumBuilder = do+      builders <- replicateM n (newName "x")+      let pat :: Pat+          pat = ConP (ddtDataConName builderRecord) (map VarP builders)+          rhs :: Exp+          rhs =+            foldl+              AppE+              (ConE (ddtDataConName vectorRecord))+              [AppE (VarE 'sumBuilder) (VarE x) | x <- builders]+      return $+        FunD+          'sumBuilder+          [ Clause [ConP 'Nothing []] (NormalB (VarE 'zeroV)) [],+            Clause [ConP 'Just [pat]] (NormalB rhs) []+          ]++    vecbuilderDec =+      TySynInstD+        ( TySynEqn+            Nothing+            (AppT (ConT ''VecBuilder) vectorType)+            (AppT (ConT ''Maybe) builderType)+        )+      where+        vectorType = foldl AppT (ConT (ddtTypeConName vectorRecord)) instVars+        builderType = foldl AppT (ConT (ddtTypeConName builderRecord)) instVars++sumVExpr :: [Exp] -> Exp+sumVExpr = \case+  [] -> VarE 'zeroV+  exps -> foldl1 (zipExpInfix '(^+^)) exps+  where+    zipExpInfix :: Name -> Exp -> Exp -> Exp+    zipExpInfix f x y = InfixE (Just x) (VarE f) (Just y)++mkDualInstance ::+  DownhillRecord ->+  DownhillRecord ->+  Type ->+  Cxt ->+  [Type] ->+  Q [Dec]+mkDualInstance tangRecord gradRecord scalarType cxt instVars = do+  when (ddtFieldCount tangRecord /= ddtFieldCount gradRecord) $+    fail "mkDualInstance: ddtFieldCount tangRecord /= ddtFieldCount gradRecord"+  scalarTypeName <- newName "s"+  mkClassDec (VarT scalarTypeName)+  where+    n = ddtFieldCount tangRecord++    -- instance (cxt, AdditiveGroup s, s ~ scalarType) => AdditiveGroup (Record a1 … an) where+    --   …+    mkClassDec :: Type -> Q [Dec]+    mkClassDec scalarVar = do+      evalGradDec <- mkEvalGradDec+      return [InstanceD Nothing (cxt ++ newConstraints) ihead [evalGradDec]]+      where+        -- Dual s (RecordTang a1 … an) (RecordGrad a1 … an)+        ihead :: Type+        ihead = ConT ''Dual `AppT` scalarVar `AppT` vecType `AppT` gradType+          where+            vecType = foldl AppT (ConT $ ddtTypeConName tangRecord) instVars+            gradType = foldl AppT (ConT $ ddtTypeConName gradRecord) instVars+        newConstraints :: Cxt+        newConstraints =+          [ -- AdditiveGroup s+            AppT (ConT ''AdditiveGroup) scalarVar,+            -- s ~ scalarType+            AppT (AppT EqualityT scalarVar) scalarType+          ]++        -- evalGrad (RecordGrad x1 … xn) (RecordTang y1 … yn) = evalGrad x1 y1 ^+^ … ^+^ evalGrad xn yn+        mkEvalGradDec :: Q Dec+        mkEvalGradDec = do+          xs <- replicateM n (newName "x")+          ys <- replicateM n (newName "y")+          let leftPat = ConP (ddtDataConName gradRecord) (map VarP xs)+              rightPat = ConP (ddtDataConName tangRecord) (map VarP ys)+              -- terms = [evalGrad x1 y1, …, evalGrad xn yn]+              terms :: [Exp]+              terms = zipWith evalGradExp xs ys+                where+                  evalGradExp :: Name -> Name -> Exp+                  evalGradExp x y = VarE 'evalGrad `AppE` VarE x `AppE` VarE y+              rhs = sumVExpr terms+          return $+            FunD+              'evalGrad+              [ Clause+                  [leftPat, rightPat]+                  (NormalB rhs)+                  []+              ]++mkMetricInstance ::+  DownhillRecord ->+  DownhillRecord ->+  DownhillRecord ->+  Type ->+  Cxt ->+  [Type] ->+  Q [Dec]+mkMetricInstance metricRecord tangRecord gradRecord scalarType cxt instVars = do+  scalarTypeName <- newName "s"+  mkClassDec (VarT scalarTypeName)+  where+    -- instance (ctx, s ~ scalarType) => MetricTensor s (RecordMetric a1 … an) where+    --   …+    mkClassDec :: Type -> Q [Dec]+    mkClassDec scalarVar = do+      let newConstraints =+            [ -- s ~ scalarType+              AppT (AppT EqualityT scalarVar) scalarType+            ]+          -- MetricTensor s (RecordMetric a1 … an)+          ihead = ConT ''MetricTensor `AppT` metricType+      evalMetricDec <- mkEvalMetric+      sqrNormDec <- mkSqrNorm+      return+        [ InstanceD+            Nothing+            (cxt ++ newConstraints)+            ihead+            [vectypeDec, covectorTypeDec, evalMetricDec, sqrNormDec]+        ]+      where+        vectorType :: Type+        vectorType = foldl AppT (ConT $ ddtTypeConName tangRecord) instVars+        covectorType :: Type+        covectorType = foldl AppT (ConT $ ddtTypeConName gradRecord) instVars+        metricType :: Type+        metricType = foldl AppT (ConT $ ddtTypeConName metricRecord) instVars+        -- type MtVector (RecordMetric a1 … an) = RecordTang a1 … an+        vectypeDec =+          TySynInstD+            ( TySynEqn+                Nothing+                (AppT (ConT ''MtVector) metricType)+                vectorType+            )+        -- type MtCovector (RecordMetric a1 … an) = RecordGrad a1 … an+        covectorTypeDec =+          TySynInstD+            ( TySynEqn+                Nothing+                (AppT (ConT ''MtCovector) metricType)+                covectorType+            )++        mkEvalMetric :: Q Dec+        mkEvalMetric = do+          let n = ddtFieldCount metricRecord+          xs <- replicateM n (newName "m")+          ys <- replicateM n (newName "dv")+          let leftPat, rightPat :: Pat+              leftPat = ConP (ddtDataConName metricRecord) (map VarP xs)+              rightPat = ConP (ddtDataConName gradRecord) (map VarP ys)+              terms :: [Exp]+              terms = zipWith evalGradExp xs ys+                where+                  evalGradExp :: Name -> Name -> Exp+                  evalGradExp x y = VarE 'evalMetric `AppE` VarE x `AppE` VarE y+              rhs =+                foldl+                  AppE+                  (ConE (ddtDataConName tangRecord))+                  terms+          return $+            FunD+              'evalMetric+              [ Clause+                  [leftPat, rightPat]+                  (NormalB rhs)+                  []+              ]++        mkSqrNorm :: Q Dec+        mkSqrNorm = do+          let n = ddtFieldCount metricRecord+          xs <- replicateM n (newName "m")+          ys <- replicateM n (newName "dv")+          let leftPat, rightPat :: Pat+              leftPat = ConP (ddtDataConName metricRecord) (map VarP xs)+              rightPat = ConP (ddtDataConName gradRecord) (map VarP ys)+              terms :: [Exp]+              terms = zipWith evalSqrtNorm xs ys+                where+                  evalSqrtNorm :: Name -> Name -> Exp+                  evalSqrtNorm x y = VarE 'sqrNorm `AppE` VarE x `AppE` VarE y+              rhs = sumVExpr terms+          return $+            FunD+              'sqrNorm+              [ Clause+                  [leftPat, rightPat]+                  (NormalB rhs)+                  []+              ]++mkRecord :: DownhillRecord -> Q [Dec]+mkRecord record = do+  let newConstr = mkConstructor record+  let newRecordName = ddtTypeConName record+  let dataType = case ddtVariant record of+        Newtype -> NewtypeD [] newRecordName (ddtTypeVars record) Nothing newConstr []+        _ -> DataD [] newRecordName (ddtTypeVars record) Nothing [newConstr] []+  return [dataType]++renameTypeS :: (String -> String) -> Name -> Name+renameTypeS f = mkNameS . f . nameBase++data FieldInfo = FieldInfo+  { fiName :: String,+    fiIndex :: Int,+    fiType :: Type+  }++mkGetField ::+  DownhillRecord ->+  DownhillRecord ->+  Cxt ->+  [Type] ->+  FieldInfo ->+  Q [Dec]+mkGetField pointRecord gradBuilderRecord cxt instVars field = do+  rName <- newName "r"+  xName <- newName "x"+  dxName <- newName "dx"+  goName <- newName "go"+  dxdaName <- newName "dx_da"+  let rhsFieldList :: [Exp]+      rhsFieldList =+        replicate (fiIndex field) (VarE 'mempty)+          ++ [VarE dxdaName]+          ++ replicate (n - fiIndex field - 1) (VarE 'mempty)+      -- rhs = MyRecordGradBuilder mempty … mempty dx_da_a6SX mempty … mempty+      rhs :: Exp+      rhs = foldl AppE (ConE (ddtDataConName gradBuilderRecord)) rhsFieldList+  return+    [ InstanceD+        Nothing+        cxt+        ( AppT+            ( AppT+                (AppT (ConT ''HasField) (LitT (StrTyLit (fiName field))))+                (AppT (AppT (ConT ''BVar) (VarT rName)) pointType)+            )+            (AppT (AppT (ConT ''BVar) (VarT rName)) (fiType field))+        )+        [ FunD+            'getField+            [ Clause+                [ConP 'BVar [VarP xName, VarP dxName]]+                ( NormalB+                    ( AppE+                        ( AppE+                            (ConE 'BVar)+                            (AppE (AppTypeE (VarE 'getField) (LitT (StrTyLit (fiName field)))) (VarE xName))+                        )+                        (AppE (AppE (VarE 'lift1_sparse) (VarE goName)) (VarE dxName))+                    )+                )+                [ SigD+                    goName+                    ( AppT+                        ( AppT+                            ArrowT+                            ( ConT ''VecBuilder+                                `AppT` AppT (ConT ''Grad) (fiType field)+                            )+                        )+                        (ConT ''Maybe `AppT` gradBuilderType)+                    ),+                  FunD+                    goName+                    [ Clause+                        [VarP dxdaName]+                        ( NormalB+                            ( AppE+                                (ConE 'Just)+                                rhs+                            )+                        )+                        []+                    ]+                ]+            ]+        ]+    ]+  where+    n = ddtFieldCount pointRecord+    applyVars :: Type -> Type+    applyVars x = foldl AppT x instVars+    pointType :: Type+    pointType = applyVars (ConT $ ddtTypeConName pointRecord)+    gradBuilderType = applyVars (ConT $ ddtTypeConName gradBuilderRecord)++renameDownhillRecord :: RecordTranstorm -> DownhillRecord -> DownhillRecord+renameDownhillRecord (RecordTranstorm namer typeFun) record =+  DownhillRecord+    { ddtTypeConName = renameTypeS (typeConNamer namer) (ddtTypeConName record),+      ddtDataConName = renameTypeS (dataConNamer namer) (ddtDataConName record),+      ddtTypeVars = ddtTypeVars record,+      ddtFieldCount = ddtFieldCount record,+      ddtFieldTypes = typeFun <$> ddtFieldTypes record,+      ddtFieldNames = fmap (fmap (fieldNamer namer)) (ddtFieldNames record),+      ddtVariant = ddtVariant record+    }++builderTransform :: BVarOptions -> RecordTranstorm+builderTransform options = RecordTranstorm (optBuilderNamer options) (AppT (ConT ''VecBuilder))++tangTransform :: BVarOptions -> RecordTranstorm+tangTransform options = RecordTranstorm (optTangNamer options) (AppT (ConT ''Tang))++gradTransform :: BVarOptions -> RecordTranstorm+gradTransform options = RecordTranstorm (optGradNamer options) (AppT (ConT ''Grad))++metricTransform :: BVarOptions -> RecordTranstorm+metricTransform options = RecordTranstorm (optMetricNamer options) (AppT (ConT ''Metric))++mkVec :: Cxt -> [Type] -> Type -> DownhillRecord -> BVarOptions -> Q [Dec]+mkVec cxt instVars scalarType vectorType options = do+  let builderType = renameDownhillRecord (builderTransform options) vectorType+  tangDec <- mkRecord vectorType+  tangBuilderDec <- mkRecord builderType+  tangSemigroup <- mkSemigroupInstance cxt builderType instVars+  tangInst <- mkBasicVectorInstance vectorType options cxt instVars+  additiveTang <- mkAdditiveGroupInstance cxt vectorType instVars+  vspaceTang <- mkVectorSpaceInstance vectorType scalarType cxt instVars+  return+    ( concat+        [ tangDec,+          tangBuilderDec,+          tangInst,+          tangSemigroup,+          additiveTang,+          vspaceTang+        ]+    )++mkDVar'' ::+  Cxt ->+  DownhillRecord ->+  BVarOptions ->+  Type ->+  [Type] ->+  ConstructorInfo ->+  Q [Dec]+mkDVar'' cxt pointRecord options scalarType instVars substitutedCInfo = do+  let tangRecord = renameDownhillRecord (tangTransform options) pointRecord+      gradRecord = renameDownhillRecord (gradTransform options) pointRecord+      metricRecord = renameDownhillRecord (metricTransform options) pointRecord++  tangDecs <- mkVec cxt instVars scalarType tangRecord options+  gradDecs <- mkVec cxt instVars scalarType gradRecord options++  metricDec <- mkRecord metricRecord+  additiveMetric <- mkAdditiveGroupInstance cxt metricRecord instVars+  vspaceMetric <- mkVectorSpaceInstance metricRecord scalarType cxt instVars+  dualInstance <- mkDualInstance tangRecord gradRecord scalarType cxt instVars+  metricInstance <- mkMetricInstance metricRecord tangRecord gradRecord scalarType cxt instVars+  let needAffineSpace = case optAffineSpace options of+        MakeAffineSpace -> True+        NoAffineSpace -> False+        AutoAffineSpace -> null (optExcludeFields options)++  affineSpaceInstance <-+    if needAffineSpace+      then mkAffineSpaceInstance cxt pointRecord tangRecord instVars+      else return []++  hasFieldInstance <- case ddtFieldNames pointRecord of+    Nothing -> return []+    Just names ->+      let info :: Int -> String -> Type -> FieldInfo+          info index name = FieldInfo name index+          substitutedFields = constructorFields substitutedCInfo+          fields :: [FieldInfo]+          fields = zipWith3 info [0 ..] names substitutedFields+       in concat+            <$> traverse+              ( mkGetField+                  pointRecord+                  ( renameDownhillRecord (builderTransform options) gradRecord+                  )+                  cxt+                  instVars+              )+              fields++  let decs =+        [ tangDecs,+          gradDecs,+          additiveMetric,+          vspaceMetric,+          dualInstance,+          metricDec,+          metricInstance,+          hasFieldInstance,+          affineSpaceInstance+        ]+  return (concat decs)++parseRecordType :: Type -> [Type] -> Q (Name, [Type])+parseRecordType type_ vars = case type_ of+  AppT inner typeVar -> parseRecordType inner (typeVar : vars)+  ConT recordName -> return (recordName, vars)+  _ -> fail "Expected (T a1 ... an) in constraint"++mkAffineSpaceInstance :: Cxt -> DownhillRecord -> DownhillRecord -> [Type] -> Q [Dec]+mkAffineSpaceInstance cxt recordPoint recordTang instVars = do+  plusDec <- elementwiseOp' recordPoint recordTang recordPoint '(.+^)+  minusDec <- elementwiseOp' recordPoint recordPoint recordTang '(.-.)+  let recordTypePoint = foldl AppT (ConT (ddtTypeConName recordPoint)) instVars+      recordTypeTang = foldl AppT (ConT (ddtTypeConName recordTang)) instVars+      diffTypeDec =+        TySynInstD+          ( TySynEqn+              Nothing+              (AppT (ConT ''Diff) recordTypePoint)+              recordTypeTang+          )+  let decs =+        [ plusDec,+          minusDec,+          diffTypeDec+        ]+  mkClassInstance ''AffineSpace cxt recordPoint instVars decs++filterFields :: forall m. MonadFail m => BVarOptions -> DownhillRecord -> m DownhillRecord+filterFields options record =+  case optExcludeFields options of+    [] -> return record+    _ -> do+      fieldList <- case ddtFieldNames record of+        Just fields -> return fields+        Nothing -> fail (nameBase (ddtTypeConName record) ++ " is not a records, can't exclude fields")+      doFilterFields fieldList+  where+    doFilterFields fieldList = do+      traverse_ check (optExcludeFields options)+      return+        record+          { ddtFieldTypes = go (ddtFieldTypes record),+            ddtFieldNames = go <$> ddtFieldNames record,+            ddtFieldCount = goN (ddtFieldCount record)+          }+      where+        check :: String -> m ()+        check name+          | name `elem` fieldList = return ()+          | otherwise = fail ("Field " ++ name ++ " is not a member of " ++ nameBase (ddtTypeConName record))+        excludeZipList :: [x -> Maybe x]+        excludeZipList = filterField <$> fieldList+          where+            filterField :: String -> x -> Maybe x+            filterField fieldName x+              | fieldName `elem` optExcludeFields options = Nothing+              | otherwise = Just x+        go :: [a] -> [a]+        go = catMaybes . zipWith ($) excludeZipList+        goN :: Int -> Int+        goN n = length . go $ replicate n ()++mkDVarC1 :: BVarOptions -> Dec -> Q [Dec]+mkDVarC1 options = \case+  InstanceD mayOverlap cxt type_ decs -> do+    case mayOverlap of+      Just _ -> fail "Overlapping instances not implemented"+      _ -> return ()+    case type_ of+      AppT (ConT hasgradCtx) recordInConstraintType -> do+        when (hasgradCtx /= ''HasGrad) $+          fail $ "Constraint must be `HasGrad`, got " ++ show hasgradCtx+        (recordName, instVars) <- parseRecordType recordInConstraintType []+        record' <- reifyDatatype recordName++        (fullParsedRecord, cinfo) <- parseDownhillRecord recordName record'+        parsedRecord <- filterFields options fullParsedRecord+        recordTypeVarNames <- do+          let getName x = case x of+                SigT (VarT y) _ -> return y+                _ -> fail "Type variable is not VarT"+          traverse getName (datatypeInstTypes record')+        -- We have two sets of type variables: one in record definition (as in `data MyRecord a b c = ...`)+        -- and another one in instance head (`instance HasGrad (MyRecord a' b' c')). We need+        -- those from instance head for HasField instances.+        let substPairs = zip recordTypeVarNames instVars+            substitutedRecord = applySubstitution (Map.fromList substPairs) cinfo++        scalarType <- case decs of+          [] -> fail "`HasGrad` instance has no declarations"+          [dec1] -> case dec1 of+            TySynInstD (TySynEqn _ (AppT (ConT scalarName) _) scalarType) -> do+              when (scalarName /= ''MScalar) $+                fail ("Expected `Scalar` equation, got " ++ show scalarName)+              return scalarType+            _ -> fail "HasGrad instance must contain `Scalar ... = ...` declaration"+          _ -> fail "`HasGrad` has multiple declarations"++        dvar <- mkDVar'' cxt parsedRecord options scalarType instVars substitutedRecord++        let tangName = ddtTypeConName (renameDownhillRecord (tangTransform options) parsedRecord)+            gradName = ddtTypeConName (renameDownhillRecord (gradTransform options) parsedRecord)+            metricName = ddtTypeConName (renameDownhillRecord (metricTransform options) parsedRecord)+            tangTypeDec =+              TySynInstD+                ( TySynEqn+                    Nothing+                    (AppT (ConT ''Tang) recordInConstraintType)+                    (foldl AppT (ConT tangName) instVars)+                )+            gradTypeDec =+              TySynInstD+                ( TySynEqn+                    Nothing+                    (AppT (ConT ''Grad) recordInConstraintType)+                    (foldl AppT (ConT gradName) instVars)+                )+            metricTypeDec =+              TySynInstD+                ( TySynEqn+                    Nothing+                    (AppT (ConT ''Metric) recordInConstraintType)+                    (foldl AppT (ConT metricName) instVars)+                )++            hasgradInstance =+              InstanceD+                Nothing+                cxt+                type_+                ( decs+                    ++ [ tangTypeDec,+                         gradTypeDec,+                         metricTypeDec+                       ]+                )+        return $ dvar ++ [hasgradInstance]+      _ -> fail "Instance head is not a constraint"+  _ -> fail "Expected instance declaration"++-- | Generates @HasGrad@ instance, along with @Tang@ and @Grad@ types,+-- @VecBuilder@ types and all other instances needed for @HasGrad@.+mkHasGradInstances :: BVarOptions -> Q [Dec] -> Q [Dec]+mkHasGradInstances options decs = concat <$> (traverse (mkDVarC1 options) =<< decs)
+ test/DownhillTest/Point.hs view
@@ -0,0 +1,7 @@+module DownhillTest.Point where++data Vector a = Vector { vectorX :: a, vectorY :: a }+data Point a = Point { pointX :: a, pointY :: a}++--vectorX' :: BVar (Vector a) da dv -> BVar a da dv+--vectorX' = 
+ test/DownhillTest/TH.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-}++module DownhillTest.TH (thTest) where++import Data.AffineSpace (AffineSpace (..))+import Downhill.Grad (HasGrad (MScalar, Tang))+import Downhill.TH (BVarOptions (..), RecordNamer (..), mkHasGradInstances)+import Test.Tasty (TestTree, testGroup)+import DownhillTest.TestTHOptions (defaultDVarOptions)++{-# ANN module "HLint: ignore Use newtype instead of data" #-}+newtype MyRecord1 = MyRecord1 Float++data MyRecord2 = MyRecord2 Float++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance HasGrad MyRecord1 where+      type MScalar MyRecord1 = Float+    |]++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance HasGrad MyRecord2 where+      type MScalar MyRecord2 = Float+    |]++data MyRecord3 = MyRecord3++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance HasGrad MyRecord3 where+      type MScalar MyRecord3 = ()+    |]++data MyRecord4 a = MyRecord4 a++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance (AffineSpace a, HasGrad a, Diff a ~ Tang a) => HasGrad (MyRecord4 a) where+      type MScalar (MyRecord4 a) = MScalar a+    |]++data MyRecord5 a b = MyRecord5 a b++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance+      ( AffineSpace a,+        AffineSpace b,+        HasGrad a,+        HasGrad b,+        MScalar a ~ MScalar b,+        Diff a ~ Tang a,+        Diff b ~ Tang b+      ) =>+      HasGrad (MyRecord5 a b)+      where+      type MScalar (MyRecord5 a b) = MScalar a+    |]++data MyRecord6 a b = MyRecord6 a b++mkHasGradInstances+  defaultDVarOptions+  [d|+    instance+      ( AffineSpace a,+        HasGrad a,+        MScalar a ~ Float,+        Diff a ~ Tang a+      ) =>+      HasGrad (MyRecord6 a Float)+      where+      type MScalar (MyRecord6 a Float) = Float+    |]++data MyRecord7 a = MyRecord7+  { myField7 :: a+  , myLabel7 :: String+  }++mkHasGradInstances+  defaultDVarOptions {optExcludeFields = ["myLabel7"]}+  [d|+    instance HasGrad a => HasGrad (MyRecord7 a) where+      type MScalar (MyRecord7 a) = MScalar a+    |]++thTest :: TestTree+thTest = testGroup "Template Haskell" [] -- just test if it compiles...
+ test/DownhillTest/TestTHOptions.hs view
@@ -0,0 +1,46 @@+module DownhillTest.TestTHOptions(defaultDVarOptions) where+import Downhill.TH ( mkHasGradInstances, RecordNamer(..), BVarOptions(..), AffineSpaceOptions (AutoAffineSpace))++defaultTangRecordNamer :: RecordNamer+defaultTangRecordNamer =+  RecordNamer+    { typeConNamer = (++ "TangT"),+      dataConNamer = (++ "TangD"),+      fieldNamer = id+    }++defaultGradRecordNamer :: RecordNamer+defaultGradRecordNamer =+  RecordNamer+    { typeConNamer = (++ "GradT"),+      dataConNamer = (++ "GradD"),+      fieldNamer = id+    }++defaultMetricRecordNamer :: RecordNamer+defaultMetricRecordNamer =+  RecordNamer+    { typeConNamer = (++ "MetricT"),+      dataConNamer = (++ "MetricD"),+      fieldNamer = id+    }++defaultBuilderRecordNamer :: RecordNamer+defaultBuilderRecordNamer =+  RecordNamer+    { typeConNamer = (++ "BuilderT"),+      dataConNamer = (++ "BuilderD"),+      fieldNamer = id+    }++defaultDVarOptions :: BVarOptions+defaultDVarOptions =+  BVarOptions+    { optTangNamer = defaultTangRecordNamer,+      optGradNamer = defaultGradRecordNamer,+      optMetricNamer = defaultMetricRecordNamer,+      optBuilderNamer = defaultBuilderRecordNamer,+      optAffineSpace = AutoAffineSpace,+      optExcludeFields = []+    }+
+ test/DownhillTest/Traversable.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}++module DownhillTest.Traversable(recordTest) where++import Downhill.BVar.Traversable (TraversableVar (TraversableVar), backpropTraversable, splitTraversable)+import Downhill.BVar (BVar (BVar), backprop, var)+import Downhill.Grad (HasGrad (Grad))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase, (@?=))++data MyRecord a = MyRecord+  { memberPair :: (a, a),+    memberList :: [a]+  }+  deriving (Eq, Functor, Foldable, Traversable, Show)++deriving via (TraversableVar MyRecord a) instance HasGrad a => HasGrad (MyRecord a)++test_r :: MyRecord Integer+test_r = MyRecord (10, 11) [12, 13, 14]++expectedResult :: MyRecord (Integer, Integer)+expectedResult =+  MyRecord+    ((10, 2), (11, 3))+    [(12, 5), (13, 5), (14, 5)]++test_fun :: Num a => MyRecord a -> a+test_fun (MyRecord (x, y) zs) = 2 * x + 3 * y + 5 * sum zs++type MyGrad a = Grad (MyRecord a)++actualResult :: MyRecord (Integer, Integer)+actualResult = backpropTraversable 1 (,) test_fun test_r+  where+    x :: BVar (MyGrad Integer) (MyRecord Integer)+    x = var test_r+    x' :: MyRecord (BVar (MyGrad Integer) Integer)+    x' = splitTraversable x+    y :: BVar (MyGrad Integer) Integer+    y = test_fun x'++recordTest :: TestTree+recordTest = testCase "Traverse record" (actualResult @?= expectedResult)++main :: IO ()+main = return ()
+ test/Main.hs view
@@ -0,0 +1,25 @@+import Downhill.BVar(bvarValue)+import Test.Tasty (defaultMain, testGroup, TestTree)+import Test.Tasty.HUnit (Assertion, testCase, (@?=))++import qualified Test.Tasty as Tasty+import Downhill.BVar.Num (NumBVar(..), backpropNum, constant, var, numbvarValue, AsNum)+import DownhillTest.Traversable(recordTest)+import DownhillTest.TH (thTest)++basicTests = testGroup "Basic tests"+  [ testCase "Derivative of constant == 0" testConstant+  , testCase "Derivative of identity == 1" testIdentity+  , testCase "Derivative of simple polynomial" testPoly+  ]+  where testConstant = backpropNum (constant 3 :: NumBVar Integer) @?= 0+        testIdentity = backpropNum (var 3 :: NumBVar Integer) @?= 1 +        testPoly =+            let x = var 5 :: NumBVar Integer+                y = 3*x :: NumBVar Integer+            in backpropNum ((2+3*x) * (5+7*x)) @?= 29 + 42 * numbvarValue x++tests :: TestTree+tests = testGroup "Tests" [basicTests, recordTest, thTest]++main = defaultMain tests