packages feed

dynobud-1.0.0.0: src/Dyno/View/CustomFunction.hs

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}

module Dyno.View.CustomFunction
       ( CustomFun(..)
       , DerivGen(..)
       , toCustomFun
       ) where

import Control.Monad ( zipWithM )
import Data.Proxy
import qualified Data.Vector as V
import Data.Vector ( Vector )
import qualified Data.Foldable as F

import Casadi.Sparsity ( Sparsity, dense )
import Casadi.Option ( Opt(..), setOption )
import Casadi.SharedObject ( soInit )

import qualified Dyno.TypeVecs as TV
import Dyno.TypeVecs ( Vec, Dim, reifyDim )
import Dyno.View.CasadiMat ( DMatrix, sparsity )
import Dyno.View.Scheme ( Scheme(..) )
import Dyno.View.Fun ( Fun(..) )
import Casadi.Callback ( makeCustomEvaluate, makeDerivativeGenerator )
import qualified Casadi.Core.Classes.Function as C
import qualified Casadi.Core.Classes.CustomFunction as C
import qualified Casadi.Core.Classes.IOInterfaceFunction as C
import Casadi.Core.Classes.DerivativeGenerator ( DerivativeGenerator )


data CustomFun f g =
  CustomFun
  { cfFun :: f DMatrix -> IO (g DMatrix)
  , cfSparsityIn :: Maybe (f Sparsity)
  , cfSparsityOut :: Maybe (g Sparsity)
  , cfDerivGenerator :: Maybe (DerivGen f g)
  , cfOptions :: [(String, Opt)]
  }

data DerivGen f g =
  DerivGen
  { dgGetSeeds :: forall nfwd nadj
                  . (Dim nfwd, Dim nadj)
                  => f DMatrix -> Vec nfwd (f DMatrix) -> Vec nadj (g DMatrix)
                  -> IO (g DMatrix, Vec nfwd (g DMatrix), Vec nadj (f DMatrix))
  , dgOptions :: [(String, Opt)]
  , dgFwdSparsity :: Maybe (f Sparsity)
  , dgAdjSparsity :: Maybe (g Sparsity)
  }

groupsOf :: Int -> [a] -> [[a]]
groupsOf _ [] = []
groupsOf k vs
  | length vs0 == k = vs0 : groupsOf k vs1
  | otherwise = error "groupsOf not divisible"
  where
    (vs0, vs1) = splitAt k vs

toDerivGen :: forall f g . (Scheme f, Scheme g) => DerivGen f g -> IO DerivativeGenerator
toDerivGen dg = makeDerivativeGenerator $ \originalFun nfwd nadj -> do
  let f fun = do
        numIn <- C.ioInterfaceFunction_getNumInputs fun
        inputs <- mapM (C.ioInterfaceFunction_getInput__2 fun) (take numIn [0..])

        let nf = numFields (Proxy :: Proxy f)
            ng = numFields (Proxy :: Proxy g)

        let f' :: forall nfwd nadj
                  . (Dim nfwd, Dim nadj)
                  => Proxy nfwd -> Proxy nadj -> IO (Vector DMatrix)
            f' _ _ = do
              let (inputs0', inputs12') = splitAt nf inputs
                  (inputs1', inputs2') = splitAt (nfwd*nf) inputs12'

                  inputs0 :: f DMatrix
                  inputs0 = fromVector (V.fromList inputs0')

                  inputs1 :: Vec nfwd (f DMatrix)
                  inputs1 = TV.mkVec' (map (fromVector . V.fromList) (groupsOf nf inputs1'))

                  inputs2 :: Vec nadj (g DMatrix)
                  inputs2 = TV.mkVec' (map (fromVector . V.fromList) (groupsOf ng inputs2'))
              (out0, out1, out2) <- dgGetSeeds dg inputs0 inputs1 inputs2
              let out0' = toVector out0
                  out1' = V.concat $ F.toList (fmap toVector out1)
                  out2' = V.concat $ F.toList (fmap toVector out2)
              return (V.concat [out0', out1', out2'])

        outs <- reifyDim nfwd $ \pnfwd ->
          reifyDim nadj $ \pnadj -> f' pnfwd pnadj
        _ <- zipWithM (C.ioInterfaceFunction_setOutput__2 fun) (V.toList outs) [0..]
        return ()

  ce <- makeCustomEvaluate f


  numIn <- C.ioInterfaceFunction_getNumInputs originalFun
  numOut <- C.ioInterfaceFunction_getNumOutputs originalFun
  spIns0 <- mapM (fmap sparsity . (C.ioInterfaceFunction_getInput__2 originalFun)) (take numIn [0..])
  spOuts0 <- mapM (fmap sparsity . (C.ioInterfaceFunction_getOutput__2 originalFun)) (take numOut [0..])

  let spFwd = case dgFwdSparsity dg of
--        Just sp -> toVector sp
        _ -> V.fromList $ map (uncurry dense) $ sizeList (Proxy :: Proxy f)
      spAdj = case dgAdjSparsity dg of
--        Just sp -> toVector sp
        _ -> V.fromList $ map (uncurry dense) $ sizeList (Proxy :: Proxy g)

      -- TODO: this is only right when everything's dense because it depends on jac sparsity!!!
      spIns = V.concat [ V.fromList spIns0
                       , V.concat (replicate nfwd spFwd)
                       , V.concat (replicate nadj spAdj)
                       ]
      spOuts = V.concat [ V.fromList spOuts0
                        , V.concat (replicate nfwd spAdj)
                        , V.concat (replicate nadj spFwd)
                        ]
  cf <- C.customFunction__1 ce spIns spOuts
  mapM_ (\(n,Opt o) -> setOption cf n o) (dgOptions dg)
  
  soInit cf
  
  return (C.castFunction cf)

toCustomFun ::
  forall f g
  . (Scheme f, Scheme g)
  => CustomFun f g
  -> IO (Fun f g)
toCustomFun customFun = do
  ce <- makeCustomEvaluate $ \fun -> do
        numIn <- C.ioInterfaceFunction_getNumInputs fun
        inputs <- mapM (C.ioInterfaceFunction_getInput__2 fun) (take numIn [0..])
        outputs <- cfFun customFun $ fromVector (V.fromList inputs)
        _ <- zipWithM (C.ioInterfaceFunction_setOutput__2 fun) (V.toList (toVector outputs)) [0..]
        return ()

  let spIn :: Vector Sparsity
      spIn = case cfSparsityIn customFun of
        Just spIn' -> toVector spIn'
        Nothing -> V.fromList $ map (uncurry dense) $ sizeList (Proxy :: Proxy f)
      spOut :: Vector Sparsity
      spOut = case cfSparsityOut customFun of
        Just spOut' -> toVector spOut'
        Nothing -> V.fromList $ map (uncurry dense) $ sizeList (Proxy :: Proxy g)

  cf <- C.customFunction__1 ce spIn spOut
  
  mapM_ (\(n,Opt o) -> setOption cf n o) (cfOptions customFun)
  case cfDerivGenerator customFun of
    Nothing -> return ()
    Just dg -> do
      dgen <- toDerivGen dg
      setOption cf "derivative_generator" dgen
  soInit cf
  
  return (Fun (C.castFunction cf))