uncertain-0.4.0.0: src/Numeric/Uncertain/Correlated/Internal.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_HADDOCK hide #-}
{-# OPTIONS_HADDOCK prune #-}
-- |
-- Module : Numeric.Uncertain.Correlated.Internal
-- Copyright : (c) Justin Le 2016
-- License : BSD3
--
-- Maintainer : justin@jle.im
-- Stability : experimental
-- Portability : non-portable
--
-- Internal utility functions for functionality shared by
-- "Numeric.Uncertain.Correlated" and
-- "Numeric.Uncertain.Correlated.Interactive".
module Numeric.Uncertain.Correlated.Internal (
CVar,
dephantom,
CorrF (..),
Corr,
liftCF,
constC,
liftC,
liftC2,
liftC3,
liftC4,
liftC5,
corrToState,
)
where
import Control.Arrow ((***))
import Control.Monad.Free
import Control.Monad.Trans.State
import Data.Hople
import qualified Data.IntMap.Strict as M
import Data.Kind
import Numeric.AD.Mode.Sparse
import Numeric.Uncertain
import Prelude.Compat
-- | Represents a single sample (or a value calculated from samples) within
-- the 'Corr' monad. These can be created with 'sampleUncert',
-- 'sampleExact', and 'constC', or made by combinining others with its
-- numeric typeclass instances (like 'Num') or its functions lifting
-- arbitrary numeric functions (like 'liftC2'). These keep track of
-- inter-correlations between sources, and if you add together two 'CVar's
-- that are correlated, their results will reflect this.
--
-- Can be "resolved" into the uncertain value they represent using
-- 'resolveUncert'.
--
-- Note that these are parameterized by a dummy phantom parameter 's' so
-- that they can't be "evaluated" out of the 'Corr' they live in with
-- 'evalCorr'.
--
-- Note that a @'CVar' s a@ can only ever meaningfully "exist" in a @'Corr'
-- s a@, meaning that the all samples within that 'Corr' are of the same
-- type.
data CVar s a where
CK :: a -> CVar s a
CV :: M.Key -> CVar s a
CF ::
Functor f =>
(forall t. f (AD t (Sparse a)) -> AD t (Sparse a)) ->
f (CVar s a) ->
CVar s a
-- | Unsafe function to bypass the universal qualification guard for
-- returning 'CVar's from 'Corr's.
dephantom :: CVar s a -> CVar t a
dephantom = \case
CK x -> CK x
CV k -> CV k
CF f xs -> CF f (dephantom <$> xs)
data CorrF :: Type -> Type -> Type -> Type where
Gen :: Uncert a -> (CVar s a -> b) -> CorrF s a b
Fun ::
Functor f =>
(forall t. f (AD t (Sparse a)) -> AD t (Sparse a)) ->
f (CVar s a) ->
(CVar s a -> b) ->
CorrF s a b
Rei ::
CVar s a ->
(Uncert a -> b) ->
CorrF s a b
instance Functor (CorrF s a) where
fmap f = \case
Gen u next -> Gen u (f . next)
Fun g us next -> Fun g us (f . next)
Rei v next -> Rei v (f . next)
-- | The 'Corr' monad allows us to keep track of correlated and
-- non-independent samples. It fixes a basic "failure" of the 'Uncert'
-- type, which can't describe correlated samples.
--
-- For example, consider the difference between:
--
-- @
-- ghci> sum $ replicate 10 (12.5 '+/-' 0.8)
-- 125 +/- 3
-- ghci> 10 * (12.5 +/- 0.8)
-- 125 +/- 8
-- @
--
-- The first one represents the addition of ten independent samples, whose
-- errors will in general cancel eachother out. The second one represents
-- sampling once and multiplying it by ten, which will amplify any error by
-- a full factor of 10.
--
-- See how the 'Corr' monad expresses the above computations:
--
-- @
-- ghci> 'evalCorr' $ do
-- x <- 'sampleUncert' $ 12.5 '+/-' 0.8
-- y1 <- 'resolveUncert' $ sum (replicate 10 x)
-- y2 <- resolveUncert $ 10 * x
-- return (y1, y2)
-- (125 +\/- 8, 125 +\/- 8)
--
-- ghci> 'evalCorr' $ do
-- xs <- replicateM 10 ('sampleUncert' (12.5 +/- 0.8))
-- 'resolveUncert' $ sum xs
-- 125 +/- 3
-- @
--
-- The first example samples once and describes operations on the single
-- sample; the second example samples 10 times with 'replicateM' and sums
-- all of the results.
--
-- Things are more interesting when you sample multiple variables:
--
-- @
-- ghci> 'evalCorr' $ do
-- x <- 'sampleUncert' $ 12.5 '+/-' 0.8
-- y <- sampleUncert $ 15.9 +/- 0.5
-- z <- sampleUncert $ 1.52 +/- 0.07
-- let k = y ** x
-- 'resolveUncert' $ (x+z) * logBase z k
-- 1200 +/- 200
-- @
--
-- The first parameter is a dummy phantom parameter used to prevent 'CVar's
-- from leaking out of the computation (see 'evalCorr'). The second
-- parameter is the numeric type of all samples within the description (for
-- example, if you ever sample an @'Uncert' 'Double'@, the second parameter wil
-- be 'Double'). The third parameter is the result type of the
-- computation -- the value the 'Corr' is describing.
newtype Corr s a b = Corr
{ corrFree :: Free (CorrF s a) b
}
deriving (Functor, Applicative, Monad)
deriving instance MonadFree (CorrF s a) (Corr s a)
corrToState ::
(Monad m, Fractional a) =>
Corr s a b ->
StateT (M.Key, M.IntMap (Uncert a)) m b
corrToState = iterM go . corrFree
where
go = \case
Gen u next -> do
i <- gets fst
modify $ succ *** M.insert i u
next (CV i)
Fun f us next ->
next $ CF f us
Rei v next -> do
u <- gets (getCVar v . snd)
next u
getCVar ::
forall a s.
Fractional a =>
CVar s a ->
M.IntMap (Uncert a) ->
Uncert a
getCVar cv = liftUF (cVarToF cv)
where
cVarToF :: CVar s a -> M.IntMap (AD t (Sparse a)) -> AD t (Sparse a)
cVarToF (CK x) _ = auto x
cVarToF (CV k) us = us M.! k
cVarToF (CF f cs) us = f ((`cVarToF` us) <$> cs)
{-# INLINEABLE corrToState #-}
-- | Lifts a multivariate numeric function on a container (given as an @f
-- a -> a@) to work on a container of 'CVar's. Correctly propagates the
-- uncertainty according to the second-order (multivariate) taylor
-- expansion of the function, and properly takes into account and keeps
-- track of all inter-correlations between the 'CVar' samples. Note that
-- if the higher-degree taylor series terms are large with respect to the
-- means and variances, this approximation may be inaccurate.
--
-- Should take any function sufficiently polymorphic over numeric types, so
-- you can use things like '*', 'sqrt', 'atan2', etc.
--
-- @
-- ghci> 'evalCorr' $ do
-- x <- 'sampleUncert' $ 12.5 '+/-' 0.8
-- y <- sampleUncert $ 15.9 +/- 0.5
-- z <- sampleUncert $ 1.52 +/- 0.07
-- 'resolveUncert' $ liftCF (\\[a,b,c] -> (a+c) * logBase c (b**a)) x y z
-- 1200 +/- 200
-- @
liftCF ::
Functor f =>
-- | Function on container of values to lift
(forall t. f (AD t (Sparse a)) -> AD t (Sparse a)) ->
-- | Container of 'CVar' samples to apply the function to
f (CVar s a) ->
CVar s a
liftCF = CF
{-# INLINE liftCF #-}
-- | Creates a 'CVar' representing a completely independent sample from all
-- other 'CVar's containing the exact value given.
constC :: a -> CVar s a
constC = CK
{-# INLINE constC #-}
-- | Lifts a numeric function over the sample represented by a 'CVar'.
-- Correctly propagates the uncertainty according to the second-order
-- taylor expansion expansion of the function. Note that if the
-- higher-degree taylor series terms are large with respect to the mean and
-- variance, this approximation may be inaccurate.
--
-- Should take any function sufficiently polymorphic over numeric types, so
-- you can use things like 'sqrt', 'sin', 'negate', etc.
--
-- @
-- ghci> 'evalCorr' $ do
-- x <- 'sampleUncert' $ 12.5 '+/-' 0.8
-- y <- sampleUncert $ 15.9 +/- 0.5
-- 'resolveUncert' $ liftC (\\z -> log z ^ 2) (x + y)
-- 11.2 +/- 0.2
-- @
liftC ::
-- | Function on values to lift
(forall t. AD t (Sparse a) -> AD t (Sparse a)) ->
-- | 'CVar' sample to apply the function to
CVar s a ->
CVar s a
liftC f = curryH1 $ liftCF (uncurryH1 f)
{-# INLINEABLE liftC #-}
-- | Lifts a two-argument (curried) function over the samples represented
-- by two 'CVar's. Correctly propagates the uncertainty according to the
-- second-order (multivariate) taylor expansion expansion of the function,
-- and properly takes into account and keeps track of all
-- inter-correlations between the 'CVar' samples. Note that if the
-- higher-degree taylor series terms are large with respect to the mean and
-- variance, this approximation may be inaccurate.
--
-- Should take any function sufficiently polymorphic over numeric types, so
-- you can use things like '*', 'atan2', '**', etc.
--
-- @
-- ghci> 'evalCorr' $ do
-- x <- 'sampleUncert' $ 12.5 '+/-' 0.8
-- y <- sampleUncert $ 15.9 +/- 0.5
-- 'resolveUncert' $ liftC2 (\\a b -> log (a + b) ^ 2) x y
-- 11.2 +/- 0.2
-- @
liftC2 ::
(forall t. AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a)) ->
CVar s a ->
CVar s a ->
CVar s a
liftC2 f = curryH2 $ liftCF (uncurryH2 f)
{-# INLINEABLE liftC2 #-}
-- | Lifts a three-argument (curried) function over the samples represented
-- by three 'CVar's. See 'liftC2' and 'liftCF' for more details.
liftC3 ::
(forall t. AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a)) ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a
liftC3 f = curryH3 $ liftCF (uncurryH3 f)
{-# INLINEABLE liftC3 #-}
-- | Lifts a four-argument (curried) function over the samples represented
-- by four 'CVar's. See 'liftC2' and 'liftCF' for more details.
liftC4 ::
( forall t.
AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a) -> AD t (Sparse a)
) ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a
liftC4 f = curryH4 $ liftCF (uncurryH4 f)
{-# INLINEABLE liftC4 #-}
-- | Lifts a five-argument (curried) function over the samples represented
-- by five 'CVar's. See 'liftC2' and 'liftCF' for more details.
liftC5 ::
( forall t.
AD t (Sparse a) ->
AD t (Sparse a) ->
AD t (Sparse a) ->
AD t (Sparse a) ->
AD t (Sparse a) ->
AD t (Sparse a)
) ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a ->
CVar s a
liftC5 f = curryH5 $ liftCF (uncurryH5 f)
{-# INLINEABLE liftC5 #-}
instance Fractional a => Num (CVar s a) where
(+) = liftC2 (+)
{-# INLINE (+) #-}
(*) = liftC2 (*)
{-# INLINE (*) #-}
(-) = liftC2 (-)
{-# INLINE (-) #-}
negate = liftC negate
{-# INLINE negate #-}
abs = liftC abs
{-# INLINE abs #-}
signum = liftC signum
{-# INLINE signum #-}
fromInteger = constC . fromInteger
{-# INLINE fromInteger #-}
instance Fractional a => Fractional (CVar s a) where
recip = liftC recip
{-# INLINE recip #-}
(/) = liftC2 (/)
{-# INLINE (/) #-}
fromRational = constC . fromRational
{-# INLINE fromRational #-}
instance Floating a => Floating (CVar s a) where
pi = constC pi
{-# INLINE pi #-}
exp = liftC exp
{-# INLINE exp #-}
log = liftC log
{-# INLINE log #-}
sqrt = liftC sqrt
{-# INLINE sqrt #-}
(**) = liftC2 (**)
{-# INLINE (**) #-}
logBase = liftC2 logBase
{-# INLINE logBase #-}
sin = liftC sin
{-# INLINE sin #-}
cos = liftC cos
{-# INLINE cos #-}
asin = liftC asin
{-# INLINE asin #-}
acos = liftC acos
{-# INLINE acos #-}
atan = liftC atan
{-# INLINE atan #-}
sinh = liftC sinh
{-# INLINE sinh #-}
cosh = liftC cosh
{-# INLINE cosh #-}
asinh = liftC asinh
{-# INLINE asinh #-}
acosh = liftC acosh
{-# INLINE acosh #-}
atanh = liftC atanh
{-# INLINE atanh #-}