dynobud-1.3.0.0: examples/ExampleDsl/OcpMonad.hs
{-# OPTIONS_GHC -Wall #-}
{-# Language ScopedTypeVariables #-}
{-# Language PackageImports #-}
{-# Language MultiParamTypeClasses #-}
{-# Language FunctionalDependencies #-}
{-# Language GeneralizedNewtypeDeriving #-}
{-# Language FlexibleContexts #-}
{-# Language RankNTypes #-}
{-# Language DataKinds #-}
module ExampleDsl.OcpMonad
( OcpMonad
, EqMonad(..)
, LeqMonad(..)
, DaeMonad
, BCMonad
, SXElement
, diffState
, algVar
, control
, parameter
, output
, lagrangeTerm
, solveStaticOcp
) where
import Control.Applicative ( Applicative(..) )
import Control.Lens ( Lens', over )
import Control.Monad ( when )
import qualified "mtl" Control.Monad.State as State
import "mtl" Control.Monad.Reader ( MonadIO(..) )
import "mtl" Control.Monad.Writer ( WriterT, Writer, MonadWriter, runWriterT, runWriter )
import "mtl" Control.Monad.State ( StateT, MonadState, runStateT )
import "mtl" Control.Monad.Except ( ExceptT, MonadError, runExceptT )
import qualified Data.Foldable as F
import qualified Data.HashSet as HS
import qualified Data.Sequence as S
import qualified Data.Map as M
import Data.Sequence ( (|>) )
import Data.Vector ( Vector )
import qualified Data.Vector as V
import Data.Proxy ( Proxy(..) )
import Casadi.Option ( setOption )
import Casadi.SXFunction ( sxFunction )
import Casadi.SX ( SX )
import Casadi.Function ( callSX )
import Casadi.SharedObject ( soInit )
import Casadi.CMatrix as CM
import Dyno.View.Unsafe.View ( mkJ )
import Dyno.SXElement ( SXElement, sxElementSym, sxElementToSX, sxToSXElement, sxSplitJV )
import Dyno.Ocp ( OcpPhase(..) )
import Dyno.Nlp ( Bounds )
import Dyno.Vectorize ( Vectorize(..), fill )
import Dyno.TypeVecs ( Vec )
import qualified Dyno.TypeVecs as TV
import Dyno.Solvers ( Solver )
import Dyno.DirectCollocation.Quadratures ( QuadratureRoots(..) )
import Dyno.DirectCollocation.Dynamic ( DynPlotPoints, CollTrajMeta(..), NameTree(..) )
import Dyno.DirectCollocation ( solveOcp )
import ExampleDsl.LogsAndErrors
import ExampleDsl.Types
--withEllipse :: Int -> String -> String
--withEllipse n blah
-- | length blah <= n = blah
-- | otherwise = take n blah ++ "..."
newtype OcpMonad a =
OcpMonad
{ runOcp :: ExceptT ErrorMessage (WriterT [LogMessage] (StateT OcpState IO)) a
} deriving ( Functor
, Applicative
, Monad
, MonadError ErrorMessage
, MonadState OcpState
, MonadWriter [LogMessage]
, MonadIO
)
newtype BCMonad a =
BCMonad
{ runBc :: ExceptT ErrorMessage (WriterT [LogMessage] (StateT (S.Seq (Constraint SXElement)) IO)) a
} deriving ( Functor
, Applicative
, Monad
, MonadError ErrorMessage
, MonadState (S.Seq (Constraint SXElement))
, MonadWriter [LogMessage]
, MonadIO
)
newtype DaeMonad a =
DaeMonad
{ runDae :: ExceptT ErrorMessage (WriterT [LogMessage] (StateT DaeState IO)) a
} deriving ( Functor
, Applicative
, Monad
, MonadError ErrorMessage
, MonadState DaeState
, MonadWriter [LogMessage]
, MonadIO
)
emptySymbolicDae :: DaeState
emptySymbolicDae = DaeState S.empty S.empty S.empty S.empty S.empty M.empty HS.empty S.empty
buildDae :: DaeMonad a -> IO (Either ErrorMessage a, [LogMessage], DaeState)
buildDae = buildDae' emptySymbolicDae
where
buildDae' :: DaeState -> DaeMonad a -> IO (Either ErrorMessage a, [LogMessage], DaeState)
buildDae' nlp0 builder = do
((result,logs),state) <- flip runStateT nlp0 . runWriterT . runExceptT . runDae $ builder
return (result, logs, state)
newDaeVariable ::
(MonadState DaeState m, MonadError ErrorMessage m, MonadWriter [LogMessage] m, MonadIO m)
=> String -> Lens' DaeState (S.Seq (String, SXElement)) -> String -> m SXElement
newDaeVariable description lens name = do
debug $ "adding " ++ description ++ " \""++name++"\""
case name of [] -> err "name cannot be empty"
('_':_) -> err $ "name \"" ++ name ++
"\" cannot have leading underscore (this is reserved for internal use)"
_ -> return ()
state0 <- State.get
let map0 = daeNameSet state0
sym <- liftIO (sxElementSym name)
when (HS.member name map0) $ err $ name ++ " already in name set"
let state1 = state0 { daeNameSet = HS.insert name map0 }
state2 = over lens (|> (name, sym)) state1
State.put state2
return sym
svector :: Vector SXElement -> SX
svector = CM.vertcat . fmap sxElementToSX
diffState :: String -> DaeMonad (SXElement, SXElement)
diffState name = do
x <- newDaeVariable "differential state" daeX name
xdot <- newDaeVariable "differential state derivative" daeXDot ("ddt( " ++ name ++ " )")
return (x, xdot)
algVar :: String -> DaeMonad SXElement
algVar = newDaeVariable "algebraic variable" daeZ
control :: String -> DaeMonad SXElement
control = newDaeVariable "control" daeU
parameter :: String -> DaeMonad SXElement
parameter = newDaeVariable "parameter" daeP
output :: String -> SXElement -> DaeMonad ()
output name expr = do
debug $ "adding output \""++name++"\""
-- debug $ "adding output \""++name++"\": " ++ withEllipse 30 (show expr)
state0 <- State.get
let nameSet0 = daeNameSet state0
outputs0 = _daeO state0
when (HS.member name nameSet0) $ err $ name ++ " already in name set"
when (M.member name outputs0) $ impossible $ name ++ " already in output map"
let state1 = state0 { daeNameSet = HS.insert name nameSet0
, _daeO = M.insert name expr outputs0
}
State.put state1
infix 4 ===
class EqMonad m a | m -> a where
(===) :: a -> a -> m ()
instance EqMonad DaeMonad SXElement where
(===) lhs rhs = do
debug $ "adding equality constraint: "
-- ++ withEllipse 30 (show lhs) ++ " == " ++ withEllipse 30 (show rhs)
state0 <- State.get
State.put $ state0 { daeConstraints = daeConstraints state0 |> (lhs, rhs) }
instance EqMonad OcpMonad SXElement where
(===) lhs rhs = do
debug $ "adding equality constraint: "
-- ++ withEllipse 30 (show lhs) ++ " == " ++ withEllipse 30 (show rhs)
state0 <- State.get
State.put $ state0 { ocpPathConstraints = ocpPathConstraints state0 |> Eq2 lhs rhs }
infix 4 <==
class LeqMonad m where
(<==) :: SXElement -> SXElement -> m ()
instance LeqMonad OcpMonad where
(<==) lhs rhs = do
debug $ "adding inequality constraint: "
-- ++ withEllipse 30 (show lhs) ++ " <= " ++ withEllipse 30 (show rhs)
state0 <- State.get
State.put $ state0 { ocpPathConstraints = ocpPathConstraints state0 |> Ineq2 lhs rhs }
instance EqMonad BCMonad SXElement where
(===) lhs rhs = do
debug $ "adding inequality constraint: "
-- ++ withEllipse 30 (show lhs) ++ " == " ++ withEllipse 30 (show rhs)
state0 <- State.get
State.put $ state0 |> Eq2 lhs rhs
instance LeqMonad BCMonad where
(<==) lhs rhs = do
debug $ "adding inequality constraint: "
-- ++ withEllipse 30 (show lhs) ++ " <= " ++ withEllipse 30 (show rhs)
state0 <- State.get
State.put $ state0 |> Ineq2 lhs rhs
constr :: Constraint SXElement -> (SXElement, Bounds)
constr (Eq2 lhs rhs) = (lhs - rhs, (Just 0, Just 0))
constr (Ineq2 lhs rhs) = (lhs - rhs, (Nothing, Just 0))
constr (Ineq3 x (lhs,rhs)) = (x, (Just lhs, Just rhs))
lagrangeTerm :: SXElement -> OcpMonad ()
lagrangeTerm obj = do
debug "setting lagrange term"
--debug $ "setting lagrange term: " ++ withEllipse 30 (show obj)
state0 <- State.get
case ocpLagrangeObj state0 of
Objective _x -> err $ init $ unlines
[ "you set the lagrange objective function twice"
-- , " old val: " ++ withEllipse 30 (show x)
-- , " new val: " ++ withEllipse 30 (show obj)
]
ObjectiveUnset -> State.put $ state0 { ocpLagrangeObj = Objective obj }
emptySymbolicOcp :: OcpState
emptySymbolicOcp = OcpState S.empty ObjectiveUnset HomotopyParamUnset
reifyOcpPhase ::
forall ret .
(SXElement -> DaeMonad ())
-> (forall a m . (Floating a, Monad m) => a -> (String -> m a) -> (String -> m a) -> m a)
-> ((String -> BCMonad SXElement) -> (String -> BCMonad SXElement) -> BCMonad ())
-> (SXElement -> (String -> OcpMonad SXElement) -> OcpMonad ())
-> (Maybe Double, Maybe Double)
-> (forall x z u p r o c h .
( Vectorize x, Vectorize z, Vectorize u, Vectorize p, Vectorize r, Vectorize o
, Vectorize c, Vectorize h
)
=> OcpPhase x z u p r o c h (Vec 0) -> CollTrajMeta -> IO ret)
-> IO ret
reifyOcpPhase daeMonad mayerMonad bcMonad ocpMonad tbnds f = do
time <- sxElementSym "_t"
endT <- sxElementSym "T"
let time' = sxElementToSX time
endT' = sxElementToSX endT
dae' <- buildDae (daeMonad time)
let dae :: DaeState
dae = case dae' of
(Left errmsg, _, _) -> error $ "buildOcpPhase: buildDae failure: " ++ show errmsg
(_, _, daeState) -> daeState
xdotnames, xnames, znames, unames, pnames :: Vector String
xdots, xs, zs, us, ps :: Vector SXElement
(xdotnames,xdots) = V.unzip $ V.fromList $ F.toList $ _daeXDot dae
(xnames,xs) = V.unzip $ V.fromList $ F.toList $ _daeX dae
(znames,zs) = V.unzip $ V.fromList $ F.toList $ _daeZ dae
(unames,us) = V.unzip $ V.fromList $ F.toList $ _daeU dae
(pnames,ps) = V.unzip $ V.fromList $ F.toList $ _daeP dae
xdots' = svector xdots
xs' = svector xs
zs' = svector zs
us' = svector us
ps' = svector ps
daeResidual :: Vector SXElement
daeResidual = V.map (uncurry (-)) $ V.fromList $ F.toList $ daeConstraints dae
onames :: Vector String
osOut :: Vector SXElement
(onames, osOut) = V.unzip $ V.fromList $ M.toList $ _daeO dae
os <- V.mapM sxElementSym onames :: IO (Vector SXElement)
let os' = svector os
lookupThingy :: String -> OcpMonad SXElement
lookupThingy name = do
debug $ "ocp monad: looking up \"" ++ name ++ "\""
case M.lookup name varmap of
Nothing -> err $ "ocp monad: nothing named \"" ++ name ++ "\""
Just expr -> do
debug $ "ocp monad: found \"" ++ name ++ "\""
--debug $ "ocp monad: found \"" ++ name ++ "\": " ++ show expr
return expr
where
varmap :: M.Map String SXElement
varmap = M.fromList $ F.toList $ V.concat
[ V.zip xdotnames xdots
, V.zip xnames xs
, V.zip znames zs
, V.zip unames us
, V.zip pnames ps
, V.zip onames os
]
ocp' <- flip runStateT emptySymbolicOcp $ runWriterT $ runExceptT (runOcp (ocpMonad time lookupThingy))
let ocp :: OcpState
ocp = case ocp' of
((Left errmsg, logs),_) ->
error $ unlines $ ("" : map show logs) ++ ["","ocp monad failure: " ++ show errmsg]
((Right _, _), ocpState) -> ocpState
obj = case ocpLagrangeObj ocp of
ObjectiveUnset -> 0
Objective obj' -> obj'
lagFunSX <- sxFunction (V.fromList [xs',zs',us',ps',os',time',endT']) (V.fromList [svector (V.singleton obj)])
setOption lagFunSX "name" "lagrange"
soInit lagFunSX
let pathConstraints :: [SXElement]
pathConstraintBnds :: [(Maybe Double, Maybe Double)]
(pathConstraints, pathConstraintBnds) = unzip $ map constr (F.toList (ocpPathConstraints ocp))
pathcFunSX <- sxFunction (V.fromList [xs',zs',us',ps',os',time'])
(V.singleton (svector (V.fromList pathConstraints)))
setOption pathcFunSX "name" "pathConstraints"
soInit pathcFunSX
daeFunSX <- sxFunction (V.fromList [xdots', xs', zs', us', ps', time'])
(V.fromList [svector daeResidual, svector osOut])
setOption pathcFunSX "name" "daeResidualAndOutputs"
soInit daeFunSX
-- run the mayer function
x0s <- mapM (sxElementSym . (++ "_0")) (F.toList xnames)
xFs <- mapM (sxElementSym . (++ "_F")) (F.toList xnames)
let lookupState :: M.Map String SXElement -> String
-> ExceptT ErrorMessage (Writer [LogMessage]) SXElement
lookupState xmap name = do
debug $ "mayer monad: looking up \"" ++ name ++ "\""
case M.lookup name xmap of
Nothing -> err $ "mayer monad: no state named \"" ++ name ++ "\""
Just expr -> do
debug $ "mayer monad: found \"" ++ name ++ "\""
return expr
xmap0 :: M.Map String SXElement
xmap0 = M.fromList $ zip (F.toList xnames) x0s
xmapF :: M.Map String SXElement
xmapF = M.fromList $ zip (F.toList xnames) xFs
mayerObj :: SXElement
mayerObj = case runWriter (runExceptT (mayerMonad endT (lookupState xmap0) (lookupState xmapF))) of
(Left errmsg, logs) ->
error $ unlines $ ("" : map show logs) ++ ["","mayer monad failure: " ++ show errmsg]
(Right ret, _) -> ret
mayerFunSX <- sxFunction (V.fromList [ svector (V.singleton endT)
, svector (V.fromList x0s)
, svector (V.fromList xFs)
, svector V.empty
, svector V.empty
])
(V.singleton (svector (V.singleton mayerObj)))
setOption mayerFunSX "name" "mayer"
soInit mayerFunSX
let lookupState0 :: String -> BCMonad SXElement
lookupState0 name = do
debug $ "boundary condition monad: looking up initial \"" ++ name ++ "\""
case M.lookup name xmap0 of
Nothing -> err $ "boundary condition monad: no state named \"" ++ name ++ "\""
Just expr -> do
debug $ "boundary condition monad: found \"" ++ name ++ "\""
return expr
lookupStateF :: String -> BCMonad SXElement
lookupStateF name = do
debug $ "boundary condition monad: looking up final \"" ++ name ++ "\""
case M.lookup name xmapF of
Nothing -> err $ "boundary condition monad: no state named \"" ++ name ++ "\""
Just expr -> do
debug $ "boundary condition monad: found \"" ++ name ++ "\""
return expr
bcs' <- flip runStateT S.empty $ runWriterT (runExceptT (runBc $ bcMonad lookupState0 lookupStateF))
let bcs :: Vector SXElement
bcbnds :: Vector Bounds
(bcs,bcbnds) = case bcs' of
((Left errmsg, logs),_) ->
error $ unlines $ ("" : map show logs) ++ ["","boundary condition monad failure: " ++ show errmsg]
((Right _,_), ret) -> V.unzip $ V.fromList $ map constr $ F.toList ret
bcFunSX <- sxFunction (V.fromList [ svector (V.fromList x0s)
, svector (V.fromList xFs)
, svector V.empty
, svector V.empty
, svector (V.singleton endT)
])
(V.singleton (svector bcs))
setOption bcFunSX "name" "boundaryConditions"
soInit bcFunSX
let meta = CollTrajMeta
{ ctmX = NameTreeNode ("", "") (zip (F.toList xnames) (map NameTreeLeaf [0..]))
, ctmZ = NameTreeNode ("", "") (zip (F.toList znames) (map NameTreeLeaf [0..]))
, ctmU = NameTreeNode ("", "") (zip (F.toList unames) (map NameTreeLeaf [0..]))
, ctmP = NameTreeNode ("", "") (zip (F.toList pnames) (map NameTreeLeaf [0..]))
, ctmO = NameTreeNode ("", "") (zip (F.toList onames) (map NameTreeLeaf [0..]))
, ctmQ = NameTreeNode ("", "") []
}
ctmNx = V.length xnames
ctmNz = V.length znames
ctmNu = V.length unames
ctmNp = V.length pnames
TV.reifyDim (ctmNx) $ \(Proxy :: Proxy nx) ->
TV.reifyDim (ctmNz) $ \(Proxy :: Proxy nz) ->
TV.reifyDim (ctmNu) $ \(Proxy :: Proxy nu) ->
TV.reifyDim (ctmNp) $ \(Proxy :: Proxy np) ->
TV.reifyDim (V.length daeResidual) $ \(Proxy :: Proxy nr) ->
TV.reifyDim (V.length onames) $ \(Proxy :: Proxy no) ->
TV.reifyDim (V.length bcs) $ \(Proxy :: Proxy nc) ->
TV.reifyDim (length pathConstraints) $ \(Proxy :: Proxy nh) -> do
-- TV.reifyDim ncov $ \(Proxy :: Proxy ncov) -> do
-- TV.reifyDim nsh $ \(Proxy :: Proxy nsh) -> do
-- TV.reifyDim nsc $ \(Proxy :: Proxy nsc) -> do
let daeFun :: Vec nx SXElement -> Vec nx SXElement -> Vec nz SXElement -> Vec nu SXElement
-> Vec np SXElement -> SXElement
-> (Vec nr SXElement, Vec no SXElement)
daeFun x' x z u p t = (devec (rets V.! 0), devec (rets V.! 1))
where
rets = callSX daeFunSX (V.fromList [vec x', vec x, vec z, vec u, vec p, sxElementToSX t])
lagrangeFun :: Vec nx SXElement -> Vec nz SXElement -> Vec nu SXElement -> Vec np SXElement -> Vec no SXElement -> SXElement -> SXElement -> SXElement
lagrangeFun x z u p o t tf =
sxToSXElement $ V.head $ callSX lagFunSX $
(V.fromList [vec x, vec z, vec u, vec p, vec o, sxElementToSX t, sxElementToSX tf])
--Left errmsg -> error $ "toOcpPhase: lagrangeFun: " ++ errmsg ++
-- "\ninputs: " ++ show (xnames ++ znames ++ unames ++ pnames) ++ show onames ++
-- "\nnumeric inputs x: " ++ show (V.length x) ++
-- "\nnumeric inputs z: " ++ show (V.length z) ++
-- "\nnumeric inputs u: " ++ show (V.length u) ++
-- "\nnumeric inputs p: " ++ show (V.length p) ++
-- "\nnumeric inputs o: " ++ show (V.length o)
pathConstraintFun :: Vec nx SXElement -> Vec nz SXElement -> Vec nu SXElement
-> Vec np SXElement -> Vec no SXElement -> SXElement -> Vec nh SXElement
pathConstraintFun x z u p o t =
devec $ V.head $ callSX pathcFunSX (V.fromList [vec x, vec z, vec u, vec p, vec o, sxElementToSX t])
mayerFun :: SXElement -> Vec nx SXElement -> Vec nx SXElement -> Vec 0 SXElement -> Vec np SXElement
-> SXElement
mayerFun endT'' x0 xF qF p = sxToSXElement $ V.head $ callSX mayerFunSX (V.fromList [sxElementToSX endT'', vec x0, vec xF, vec qF, vec p])
bcFun :: Vec nx SXElement -> Vec nx SXElement -> Vec 0 SXElement -> Vec np SXElement -> SXElement -> Vec nc SXElement
bcFun x0 xF qF p t = devec $ V.head $ callSX bcFunSX (V.fromList [vec x0, vec xF, vec qF, vec p, sxElementToSX t])
ocpPhase =
OcpPhase { ocpMayer = mayerFun
, ocpLagrange = lagrangeFun
, ocpQuadratures = \_ _ _ _ _ _ _ -> fill 0
, ocpDae = daeFun
, ocpBc = bcFun
, ocpBcBnds = devectorize bcbnds
, ocpPathC = pathConstraintFun
, ocpPathCBnds = devectorize (V.fromList pathConstraintBnds)
, ocpXbnd = fill (Nothing, Nothing)
, ocpZbnd = fill (Nothing, Nothing)
, ocpUbnd = fill (Nothing, Nothing)
, ocpPbnd = fill (Nothing, Nothing)
, ocpTbnd = tbnds
, ocpObjScale = Nothing
, ocpTScale = Nothing
, ocpXScale = Nothing
, ocpZScale = Nothing
, ocpUScale = Nothing
, ocpPScale = Nothing
, ocpResidualScale = Nothing
, ocpBcScale = Nothing
, ocpPathCScale = Nothing
}
f ocpPhase meta
vec :: Vectorize f => f SXElement -> SX
vec = svector . vectorize
devec :: Vectorize f => SX -> f SXElement
devec = sxSplitJV . mkJ
solveStaticOcp ::
QuadratureRoots
-> Solver
-> (SXElement -> DaeMonad ())
-> (forall a m . (Floating a, Monad m) => a -> (String -> m a) -> (String -> m a) -> m a)
-> ((String -> BCMonad SXElement) -> (String -> BCMonad SXElement) -> BCMonad ())
-> (SXElement -> (String -> OcpMonad SXElement) -> OcpMonad ())
-> (Maybe Double, Maybe Double)
-> Int -> Int
-> Maybe (CollTrajMeta -> DynPlotPoints Double -> IO Bool)
-> IO (Either String String)
solveStaticOcp roots solverStuff dae mayer bc ocp tbnds n deg cb =
reifyOcpPhase dae mayer bc ocp tbnds woo
where
woo ocpphase meta = solveOcp roots solverStuff n deg (cb <*> pure meta) ocpphase