{-# LANGUAGE RankNTypes #-}

-- |
-- Module      : Numeric.Uncertain.Correlated
-- Copyright   : (c) Justin Le 2016
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- Provides the 'Corr' monad, which allows one to describe complex
-- relationships between random variables and evaluate their propagated
-- uncertainties /respecting/ their inter-correlations.
--
-- See the "Numeric.Uncertain.Correlated.Interactive" module for an
-- "interactive" and exploratory interface for this module's functionality.
--

module Numeric.Uncertain.Correlated
  ( -- * 'Corr'
    Corr, evalCorr
    -- * Uncertain and Correlated Values
  , CVar
    -- ** Sampling
  , sampleUncert, sampleExact, constC
    -- ** Resolving
  , resolveUncert
    -- * Applying arbitrary functions
  , liftC, liftC2, liftC3, liftC4, liftC5, liftCF
  )
  where

import           Control.Monad.Free
import           Control.Monad.Trans.State
import           Numeric.Uncertain
import           Numeric.Uncertain.Correlated.Internal
import qualified Data.IntMap.Strict                 as M

-- | Evaluates the value described by a 'Corr' monad, taking into account
-- inter-correlations between samples.
--
-- Takes a universally qualified 'Corr', which should not affect usage.
-- See the examples in the documentation for 'Corr'.  The univeral
-- qualification is mostly a type system trick to ensure that you aren't
-- allowed to ever use 'evalCorr' to evaluate a 'CVar'.
evalCorr :: Fractional a => (forall s. Corr s a b) -> b
evalCorr c = evalState (corrToState c) (0, M.empty)
{-# INLINABLE evalCorr #-}

-- | Generate a sample in 'Corr' from an 'Uncert' value, independently from
-- all other samples.
--
-- Note that you can only sample @'Uncert' a@s within a @'Corr' s a@, meaning
-- that all other "sampled" values are also @a@s.
sampleUncert :: Uncert a -> Corr s a (CVar s a)
sampleUncert u = liftF $ Gen u id
{-# INLINE sampleUncert #-}

-- | Generate an exact sample in 'Corr' with zero uncertainty,
-- independently from all other samples.
--
-- Not super useful, since you can do something equivalent with 'constC'
-- or the numeric instances:
--
-- @
-- sampleExact x  ≡ return ('constC' x)
-- sampleExact 10 ≡ return 10
-- @
--
-- But is provided for completeness alongside 'sampleUncert'.
--
-- Note that you can exactly sample an @a@ within a @'Corr' s a@, meaning
-- that all other "sampled" values are also @a@s.
--
sampleExact :: a -> Corr s a (CVar s a)
sampleExact = return . constC
{-# INLINE sampleExact #-}

-- | "Resolve" an 'Uncert' from a 'CVar' using its potential multiple
-- samples and sample sources, taking into account inter-correlations
-- between 'CVar's and samples.
--
-- Note that if you use 'sampleUncert' on the result, the new sample will
-- be treated as something completely independent.  Usually this should
-- only be used as the "exit point" of a 'Corr' description.
resolveUncert :: CVar s a -> Corr s a (Uncert a)
resolveUncert v = liftF $ Rei v id
{-# INLINE resolveUncert #-}