packages feed

srtree-0.1.0.0: src/Data/SRTree/Random.hs

{-# language ConstraintKinds #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Data.SRTree.Random 
-- Copyright   :  (c) Fabricio Olivetti 2021 - 2021
-- License     :  BSD3
-- Maintainer  :  fabricio.olivetti@gmail.com
-- Stability   :  experimental
-- Portability :  ConstraintKinds
--
-- Functions to generate random trees and nodes.
--
-----------------------------------------------------------------------------
module Data.SRTree.Random 
         ( HasVars
         , HasVals
         , HasFuns
         , HasEverything
         , FullParams(..)
         , RndTree
         , randomVar
         , randomConst
         , randomPow
         , randomFunction
         , randomNode
         , randomNonTerminal
         , randomTree
         , randomTreeBalanced
         )
         where

import System.Random 
import Control.Monad.State 
import Control.Monad.Reader 
import Data.Maybe (fromJust)

import Data.SRTree.Internal

-- * Class definition of properties that a certain parameter type has.
--
-- HasVars: does `p` provides a list of the variable indices?
-- HasVals: does `p` provides a range of values for the constants?
-- HasExps: does `p` provides a range for the integral exponentes?
-- HasFuns: does `p` provides a list of allowed functions?
class HasVars p where
  _vars :: p ix val -> [ix]
class HasVals p where
  _range :: p ix val -> (val, val)
class HasExps p where
  _exponents :: p ix val -> (Int, Int)
class HasFuns p where
  _funs :: p ix val -> [Function]

-- | Constraint synonym for all properties.
type HasEverything p = (HasVars p, HasVals p, HasExps p, HasFuns p)

-- | A structure with every property
data FullParams ix val = P [ix] (val, val) (Int, Int) [Function]

instance HasVars FullParams where
  _vars (P ixs _ _ _) = ixs
instance HasVals FullParams where
  _range (P _ r _ _) = r
instance HasExps FullParams where
  _exponents (P _ _ e _) = e
instance HasFuns FullParams where
  _funs (P _ _ _ fs) = fs

-- auxiliary function to sample between False and True
toss :: StateT StdGen IO Bool
toss = state random
{-# INLINE toss #-}

-- returns a random element of a list
randomFrom :: [a] -> StateT StdGen IO a
randomFrom funs = do n <- randomRange (0, length funs - 1)
                     pure $ funs !! n
{-# INLINE randomFrom #-}

-- returns a random element within a range
randomRange :: (Ord val, Random val) => (val, val) -> StateT StdGen IO val
randomRange rng = state (randomR rng)
{-# INLINE randomRange #-}

-- Replace the child of a unary tree.
replaceChild :: SRTree ix val -> SRTree ix val -> Maybe (SRTree ix val)
replaceChild (Fun g _) t = Just $ Fun g t
replaceChild (Pow _ k) t = Just $ Pow t k
replaceChild _         _ = Nothing 
{-# INLINE replaceChild #-}

-- Replace the children of a binary tree.
replaceChildren :: SRTree ix val -> SRTree ix val -> SRTree ix val -> Maybe (SRTree ix val)
replaceChildren (Add _ _) l r     = Just $ Add l r
replaceChildren (Sub _ _) l r     = Just $ Sub l r
replaceChildren (Mul _ _) l r     = Just $ Mul l r
replaceChildren (Div _ _) l r     = Just $ Div l r
replaceChildren (Power _ _) l r   = Just $ Power l r
replaceChildren (LogBase _ _) l r = Just $ LogBase l r
replaceChildren _             _ _ = Nothing
{-# INLINE replaceChildren #-}

-- | RndTree is a Monad Transformer to generate random trees of type `SRTree ix val` 
-- given the parameters `p ix val` using the random number generator `StdGen`.
type RndTree p ix val = ReaderT (p ix val) (StateT StdGen IO) (SRTree ix val)

-- | Returns a random variable, the parameter `p` must have the `HasVars` property
randomVar :: HasVars p => RndTree p ix val
randomVar = do vars <- asks _vars
               lift $ Var <$> randomFrom vars

-- | Returns a random constant, the parameter `p` must have the `HasConst` property
randomConst :: (Ord val, Random val, HasVals p) => RndTree p ix val
randomConst = do rng <- asks _range
                 lift $ Const <$> randomRange rng

-- | Returns a random integer power node, the parameter `p` must have the `HasExps` property
randomPow :: (Ord val, Random val, HasExps p) => RndTree p ix val
randomPow = do rng <- asks _exponents
               lift $ Pow Empty <$> randomRange rng

-- | Returns a random function, the parameter `p` must have the `HasFuns` property
randomFunction :: HasFuns p => RndTree p ix val
randomFunction = do funs <- asks _funs
                    lift $ (`Fun` Empty) <$> randomFrom funs

-- | Returns a random node, the parameter `p` must have every property.
randomNode :: (Ord val, Random val, HasEverything p) => RndTree p ix val
randomNode = do
  choice <- lift $ randomRange (0, 9 :: Int)
  case choice of
    0 -> randomVar
    1 -> randomConst
    2 -> randomFunction
    3 -> randomPow
    4 -> pure $ Add Empty Empty
    5 -> pure $ Sub Empty Empty
    6 -> pure $ Mul Empty Empty
    7 -> pure $ Div Empty Empty
    8 -> pure $ Power Empty Empty
    9 -> pure $ LogBase Empty Empty

-- | Returns a random non-terminal node, the parameter `p` must have every property.
randomNonTerminal :: (Ord val, Random val, HasEverything p) => RndTree p ix val
randomNonTerminal = do
  choice <- lift $ randomRange (0, 7 :: Int)
  case choice of
    0 -> randomFunction
    1 -> randomPow
    2 -> pure $ Add Empty Empty
    3 -> pure $ Sub Empty Empty
    4 -> pure $ Mul Empty Empty
    5 -> pure $ Div Empty Empty
    6 -> pure $ Power Empty Empty
    7 -> pure $ LogBase Empty Empty
    
-- | Returns a random tree with a limited budget, the parameter `p` must have every property.
randomTree :: (Ord val, Random val, HasEverything p) => Int -> RndTree p ix val
randomTree 0      = do
  coin <- lift toss
  if coin
    then randomVar
    else randomConst
randomTree budget = do 
  node  <- randomNode
  fromJust <$> case arity node of
    0 -> pure $ Just node
    1 -> replaceChild node <$> randomTree (budget - 1)
    2 -> replaceChildren node <$> randomTree (budget `div` 2) <*> randomTree (budget `div` 2)
    
-- | Returns a random tree with a approximately a number `n` of nodes, the parameter `p` must have every property.
randomTreeBalanced :: (Ord val, Random val, HasEverything p) => Int -> RndTree p ix val
randomTreeBalanced n | n <= 1 = do
  coin <- lift toss
  if coin
    then randomVar
    else randomConst
randomTreeBalanced n = do 
  node  <- randomNonTerminal
  fromJust <$> case arity node of
    1 -> replaceChild node <$> randomTreeBalanced (n - 1)
    2 -> replaceChildren node <$> randomTreeBalanced (n `div` 2) <*> randomTreeBalanced (n `div` 2)