packages feed

srtree-2.0.0.0: apps/tinygp/GP.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE BangPatterns #-}
module GP where

import Data.SRTree
import Algorithm.SRTree.Opt
import Algorithm.SRTree.Likelihoods
import Data.SRTree.Print
import Data.SRTree.Eval
import Data.SRTree.Recursion ( cata )
import System.Random
import Control.Monad.State
import Control.Monad
import Data.Vector qualified as V
import Control.Monad (when)
import Data.Massiv.Array qualified as M
import Debug.Trace ( traceShow, trace )

data Method = Grow | Full | BTC

type Rng a = StateT StdGen IO a 
type GenUni = Fix SRTree -> Fix SRTree 
type GenBin = Fix SRTree -> Fix SRTree -> Fix SRTree
type FitFun = Individual -> Individual 

data Individual = Individual { _tree :: Fix SRTree, _fit :: Double, _params :: PVector }

instance Show Individual where 
    show (Individual t f p) = showExpr t <> "," <> show f <> "," <> show p 

toss :: Rng Bool
toss = state random
{-# INLINE toss #-}

randomRange :: (Ord val, Random val) => (val, val) -> Rng val
randomRange rng = state (randomR rng)
{-# INLINE randomRange #-}

randomFrom :: [a] -> Rng a
randomFrom funs = do n <- randomRange (0, length funs - 1)
                     pure $ funs !! n
{-# INLINE randomFrom #-}

randomFromV :: V.Vector a -> Rng a
randomFromV funs = do n <- randomRange (0, length funs - 1)
                      pure $ funs V.! n
{-# INLINE randomFromV #-}

countNodes' :: Fix SRTree -> Int
countNodes' = cata alg 
  where 
    alg (Var _)     = 1
    alg (Param _)   = 1
    alg (Const _)   = 0
    alg (Bin _ l r) = 1 + l + r
    alg (Uni Abs t) = t
    alg (Uni _ t)   = 1 + t
{-# INLINE countNodes' #-}


randomTree :: HyperParams -> Bool -> Rng (Fix SRTree)
randomTree hp grow 
  | depth <= 1 || size <= 2 = randomFrom term 
  | (min_depth >= 0 || (depth > 2 && not grow)) && size > 2 = genNonTerm 
  | otherwise = genTermOrNon
  where 
    min_depth = _minDepth hp
    depth     = _maxDepth hp
    size      = _maxSize hp
    term      = _term hp
    nonterm   = _nonterm hp

    genNonTerm =
       do et <- randomFrom nonterm
          case et of 
            Left uniT -> uniT <$> randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow
            Right binT -> do l <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow
                             r <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1 - countNodes' l} grow
                             pure (binT l r)
    genTermOrNon = do r <- toss
                      if r
                        then randomFrom term 
                        else genNonTerm        
{-# INLINE randomTree #-}

data HyperParams = 
    HP { _minDepth  :: Int 
       , _maxDepth  :: Int
       , _maxSize   :: Int 
       , _popSize   :: Int
       , _tournSize :: Int
       , _pc        :: Double 
       , _pm        :: Double 
       , _term      :: [Fix SRTree]
       , _nonterm   :: [Either GenUni GenBin] 
       }

tournament :: HyperParams -> V.Vector Individual -> Rng Individual
tournament hp pop = do
  selection <- replicateM (_tournSize hp) (randomFromV $ V.filter (not.isNaN._fit) pop)
  let maxFitness = maximum (fmap _fit selection)
      champions = V.filter ((== maxFitness) . _fit) pop
  if null selection
     then randomFromV pop
     else randomFromV champions
{-# INLINE tournament #-}

randomIndividual :: HyperParams -> FitFun -> Bool -> Rng Individual
randomIndividual hyperparams fitFun grow = do
    t <- randomTree hyperparams grow 
    let p = countParams t
    theta' <- replicateM p (randomRange (-1,1))
    let ind = fitFun $ Individual t 0.0 (M.fromList compMode theta' :: PVector)
    pure ind
    --if isInfinite (_fit ind)
    --   then randomIndividual hyperparams fitFun grow 
    --   else pure ind
{-# INLINE randomIndividual #-}

initialPop :: HyperParams -> FitFun -> Rng (V.Vector Individual)
initialPop hyperparams fitFun = do 
   let depths = [3 .. _maxDepth hyperparams]
   pop <- forM depths $ \md -> 
           do let m = _popSize hyperparams `div` (_maxDepth hyperparams - 3 + 1)
                  g = V.fromList . take m $ cycle [True, False]
              mapM (randomIndividual hyperparams{ _maxDepth = md} fitFun) g
   pure (V.concat pop)
{-# INLINE initialPop #-}

fitness :: SRMatrix -> PVector -> Individual -> Individual
fitness x y ind =
    let 
        tree = relabelParams $ _tree ind
        thetaOrig = _params ind
        (theta, fit) = minimizeNLL Gaussian (Just 1) 10 x y tree thetaOrig
        --theta = _params ind
        fit' = negate $ mse x y tree thetaOrig -- nll Gaussian (Just 1) x y (relabelParams $ _tree ind) (_params ind)
       -- (fit, g) = gradNLL Gaussian Nothing x y (_tree ind) (_params ind)
    in if M.isNull (_params ind)
          then ind{_fit=fit'}
          else ind{_fit = negate (mse x y tree theta), _params = theta}
    --in ind{_fit = fit, _params = theta}
{-# INLINE fitness #-}

isAbs (Fix (Uni Abs _)) = True 
isAbs _ = False 
{-# INLINE isAbs #-}

isInv (Fix (Bin Div (Fix (Const 1.0)) _)) = True 
isInv _ = False 
{-# INLINE isInv #-}

mutate :: HyperParams -> Individual -> Rng Individual
mutate hp ind = do
  let sz = countNodes' (_tree ind)
  (t, b) <- go sz (_pm hp) (_tree ind)
  if b 
     then pure $ Individual t 0.0 M.empty
     else pure ind
      where
        go s p t
          | isAbs t = do let [x] = getChildren t
                         (t', b) <- go s p x
                         pure (Fix $ replaceChildren [t'] $ unfix t, b)
          | otherwise = do
              v <- state random 
              if v < p 
                then do let sz2 = countNodes' t 
                            maxSz = _maxSize hp - s + sz2 - 2
                        (, True) <$> randomTree hp{_maxSize = maxSz} True 
                else case arity t of 
                       0 -> pure (t, False)
                       1 -> do let [x] = getChildren t 
                               (t', b) <- go s p x 
                               pure (Fix $ replaceChildren [t'] $ unfix t, b)
                       2 -> do let [l,r] = getChildren t 
                               (l', b) <- if isInv t 
                                             then pure (l, False)
                                             else go s p l
                               if b 
                                 then pure (Fix $ replaceChildren [l', r] $ unfix t, b)
                                 else do (r', b') <- go s p r 
                                         pure (Fix $ replaceChildren [l, r'] $ unfix t, b')

crossover :: HyperParams -> Individual -> Individual -> Rng Individual
crossover hp ind1 ind2 = pure ind1

evolve :: HyperParams -> FitFun -> V.Vector Individual -> Rng Individual
evolve hp fitFun pop = do 
    parent1 <- tournament hp pop
    parent2 <- tournament hp pop 
    child <- crossover hp parent1 parent2
    child' <- mutate hp child
    let p = countParams (_tree child')
    theta' <- M.fromList compMode <$> replicateM p (randomRange (-1,1))
    pure $ fitFun child'{_params = theta'}
{-# INLINE evolve #-}

report :: Int -> V.Vector Individual -> IO ()
report gen = mapM_ reportOne
  where reportOne ind = do putStr (show gen)
                           putStr ": "
                           putStr (showExpr (_tree ind))
                           putStr " - " 
                           putStr (show (_fit ind))
                           putStr " "
                           print (M.toList $ _params ind)
{-# INLINE report #-}

evolution :: Int -> HyperParams -> FitFun -> Rng (V.Vector Individual)
evolution gen hp fitFun = do 
    pop <- initialPop hp fitFun
    liftIO $ report (-1) pop
    go gen pop 
        where 
            go 0 pop = pure pop 
            go n pop = do 
                let best = V.maximumOn _fit $ V.filter (not.isNaN._fit) pop
                pop' <- V.cons best <$> V.replicateM (_popSize hp - 1) (evolve hp fitFun pop)
                liftIO $ report (gen-n) pop'
                go (n-1) pop'