packages feed

verismith-1.1.0: src/Verismith/Verilog2005/Randomness.hs

-- Module      : Verismith.Verilog2005.Randomness
-- Description : Random sources
-- Copyright   : (c) 2023 Quentin Corradi
-- License     : GPL-3
-- Maintainer  : q [dot] corradi22 [at] imperial [dot] ac [dot] uk
-- Stability   : stable
-- Portability : POSIX
{-# LANGUAGE ScopedTypeVariables #-}

module Verismith.Verilog2005.Randomness
  ( sampleCategoricalProbability,
    sampleNumberProbability,
    sampleIn,
    sampleInString,
    sampleBernoulli,
    choice,
    sampleMaybe,
    sampleEither,
    sampleSegment,
    sampleEnum,
    sampleMaybeEnum,
    sampleWeighted,
    sampleFrom,
    sampleFromString,
    sampleBranch,
    sampleNum,
    sampleN,
    sampleNE,
    sampleString,
    sampleNEString,
    sampleFiltered,
    GenM,
  )
where

import Control.Applicative (liftA2)
import Control.Monad (join, replicateM)
import Control.Monad.Reader
import qualified Data.ByteString as B
import Data.List
import Data.List.NonEmpty (NonEmpty (..), toList)
import qualified Data.List.NonEmpty as NE
import Control.Monad.Primitive (PrimMonad, PrimState, RealWorld)
import Data.Word
import System.Random.MWC.Probability
import Verismith.Config (CategoricalProbability (..), NumberProbability (..), uniformCP)
import Verismith.Utils (nonEmpty, foldrMap1)

infixl 4 <.>

(<.>) :: (Monad m, Applicative m) => m (a -> m b) -> m a -> m b
(<.>) mf mx = join $ mf <*> mx

avoid :: [Int] -> Int -> Int
avoid l x = case l of
  h : t | h <= x -> avoid t $ x + 1
  _ -> x

uniq :: Ord b => (a -> b) -> (a -> a -> a) -> [a] -> [a]
uniq f m =
  nonEmpty [] $ toList
    . foldrMap1 (:|[]) (\e (x :| a) -> if f x == f e then (m x e) :| a else e :| x : a)
    . NE.sortWith f
    

clean :: Int -> [(Double, Int)] -> [(Double, Int)]
clean t =
  map (\(x, y) -> (max 0 x, y))
    . uniq snd (\(x1, y1) (x2, y2) -> (x1 + x2, y1))
    . filter ((<= t) . snd)

sampleCategoricalProbability ::
  PrimMonad m => Int -> Gen (PrimState m) -> CategoricalProbability -> m Int
sampleCategoricalProbability t gen d = case d of
  CPDiscrete l ->
    let ll = NE.take (t + 1) l
     in case ll of
          [] -> error "Probability vector cannot be empty"
          [x] -> pure 0
          _ -> sample (categorical ll) gen
  CPBiasedUniform l b ->
    let ll = clean t l
        uw = fromIntegral (t + 1 - length ll) * b
     in nonEmpty
          (pure Nothing)
          (flip sample gen . discrete . ((uw, Nothing) :) . map (\(x, y) -> (x, Just y)) . toList)
          ll
          >>= maybe (avoid (map snd ll) <$> sample (uniformR (0, t - length ll)) gen) pure

sampleNumberProbability :: PrimMonad m => Gen (PrimState m) -> NumberProbability -> m Int
sampleNumberProbability gen d = case d of
  NPUniform l h -> sample (uniformR (l, h)) gen
  NPBinomial o t f -> (o +) <$> sample (binomial t f) gen
  NPNegativeBinomial o r f -> (o +) <$> sample (negativeBinomial f r) gen
  NPPoisson o p -> (o +) <$> sample (poisson p) gen
  NPDiscrete l -> sample (discrete l) gen
  NPLinearComb l -> sample (discrete l) gen >>= sampleNumberProbability gen

sampleIn :: (Functor m, PrimMonad m) => [a] -> Gen (PrimState m) -> CategoricalProbability -> m a
sampleIn l gen d = (l !!) <$> sampleCategoricalProbability (length l - 1) gen d

sampleInString ::
  (Functor m, PrimMonad m) =>
  B.ByteString ->
  Gen (PrimState m) ->
  CategoricalProbability ->
  m Word8
sampleInString s gen d = (B.index s) <$> sampleCategoricalProbability (B.length s - 1) gen d

type GenM p = ReaderT (p, Gen RealWorld) IO

sampleWrapper :: (p -> d) -> (Gen RealWorld -> d -> GenM p x) -> GenM p x
sampleWrapper p f = f <$> asks snd <.> asks (p . fst)

sampleBernoulli :: (p -> Double) -> GenM p Bool
sampleBernoulli p = sample <$> (bernoulli <$> asks (p . fst)) <.> asks snd

choice :: (p -> Double) -> GenM p a -> GenM p a -> GenM p a
choice c t f = sampleBernoulli c >>= \b -> if b then t else f

sampleMaybe :: (p -> Double) -> GenM p a -> GenM p (Maybe a)
sampleMaybe c x = choice c (Just <$> x) (pure Nothing)

sampleEither :: (p -> Double) -> GenM p a -> GenM p b -> GenM p (Either a b)
sampleEither c t f = choice c (Left <$> t) (Right <$> f)

sampleSegment :: (p -> CategoricalProbability) -> Int -> Int -> GenM p Int
sampleSegment p l h = (l +) <$> (sampleWrapper p $ sampleCategoricalProbability $ h - l)

sampleEnum :: forall a p. (Bounded a, Enum a) => (p -> CategoricalProbability) -> GenM p a
sampleEnum p = toEnum <$> sampleSegment p (fromEnum (minBound :: a)) (fromEnum (maxBound :: a))

sampleMaybeEnum ::
  forall a p. (Bounded a, Enum a) => (p -> CategoricalProbability) -> GenM p (Maybe a)
sampleMaybeEnum p =
  (\n -> if n == 0 then Nothing else Just $ toEnum $ mib + n - 1)
    <$> (sampleWrapper p $ sampleCategoricalProbability $ mab - mib + 1)
  where
    mib = fromEnum (minBound :: a)
    mab = fromEnum (maxBound :: a)

sampleWeighted :: [(Double, a)] -> GenM p a
sampleWeighted l = case l of
  [] -> error "Probability vector cannot be empty"
  [(_, x)] -> pure x
  _ -> asks snd >>= sample (discrete l)

sampleFrom :: (p -> CategoricalProbability) -> [a] -> GenM p a
sampleFrom p l = sampleWrapper p $ sampleIn l

sampleFromString :: (p -> CategoricalProbability) -> B.ByteString -> GenM p Word8
sampleFromString p s = sampleWrapper p $ sampleInString s

sampleBranch :: (p -> CategoricalProbability) -> [GenM p a] -> GenM p a
sampleBranch p l = join $ sampleFrom p l

sampleNum :: (p -> NumberProbability) -> GenM p Int
sampleNum p = sampleWrapper p sampleNumberProbability

sampleN :: (p -> NumberProbability) -> GenM p b -> GenM p [b]
sampleN p x = sampleNum p >>= flip replicateM x

sampleNE :: (p -> NumberProbability) -> GenM p b -> GenM p (NonEmpty b)
sampleNE p x = liftA2 (:|) x $ sampleN p x

sampleString ::
  (p -> NumberProbability) -> (p -> CategoricalProbability) -> B.ByteString -> GenM p B.ByteString
sampleString np cp s = B.pack <$> sampleN np (sampleFromString cp s)

sampleNEString ::
  (p -> NumberProbability) -> (p -> CategoricalProbability) -> B.ByteString -> GenM p B.ByteString
sampleNEString np cp s = B.pack . toList <$> sampleNE np (sampleFromString cp s)

deleteFirstOrdered :: Ord c => (a -> c) -> (b -> c) -> [a] -> [b] -> [a]
deleteFirstOrdered pa pb la lb = case (la, lb) of
  (ha : ta, hb : tb) -> case compare (pa ha) (pb hb) of
    LT -> ha : deleteFirstOrdered pa pb ta lb
    EQ -> deleteFirstOrdered pa pb ta tb
    GT -> deleteFirstOrdered pa pb la tb
  _ -> la

merge :: Ord a => [a] -> [a] -> [a]
merge la lb = case (la, lb) of
  (ha : ta, hb : tb) -> case compare ha hb of
    LT -> ha : merge ta lb
    EQ -> ha : hb : merge ta tb
    GT -> hb : merge la tb
  (_, []) -> la
  _ -> lb

sampleFiltered :: (p -> CategoricalProbability) -> Int -> [Int] -> GenM p Int
sampleFiltered p t l = do
  gen <- asks snd
  d <- asks $ p . fst
  case d of
    CPDiscrete l ->
      avoid ll
        <$> sample (discrete $ deleteFirstOrdered snd id (zip (NE.take (t + 1) l) [0 .. t]) ll) gen
    CPBiasedUniform l' b ->
      let ll' = deleteFirstOrdered snd id (clean t l') ll
          uw = fromIntegral (t - length ll - length ll') * b
       in sample (discrete $ (uw, Nothing) : map (\(x, y) -> (x, Just y)) ll') gen
            >>= maybe
              ( avoid (merge ll $ map snd ll')
                  <$> sample (uniformR (0, t - length ll - length ll')) gen
              )
              pure
  where
    ll = sort $ filter (<= t) l