packages feed

dtmc-0.2.0.0: test/Dtmc/Transition/InterfaceSpec.hs

{-# LANGUAGE TypeApplications #-}

module Dtmc.Transition.InterfaceSpec (
    spec,
) where

import Data.Finite (
    Finite,
    finites,
 )
import Dtmc.Analysis.FiniteTime (
    stepProbability,
 )
import Dtmc.Distribution qualified as Distribution
import Dtmc.Distribution.Map qualified as DistributionMap
import Dtmc.TestSupport (
    approxEq,
    chunksOf,
    genTransitionRows,
 )
import Dtmc.Transition qualified as Transition
import Dtmc.Transition.Kernel qualified as Kernel
import Dtmc.Transition.Matrix (
    TransitionMatrix,
    TransitionMatrixError,
    fromRows,
 )
import Test.Hspec (
    Spec,
    describe,
    it,
    shouldBe,
 )
import Test.Hspec.QuickCheck (
    prop,
 )
import Test.QuickCheck (
    conjoin,
    counterexample,
    forAll,
 )

checked :: (Show error) => Either error value -> value
checked = either (error . show) id

finiteChain :: TransitionMatrix (Finite 3)
finiteChain =
    checked $
        fromRows
            ( chunksOf
                3
                [ 0.5
                , 0.5
                , 0
                , 0
                , 0.2
                , 0.8
                , 1
                , 0
                , 0
                ]
            )

asTransitionKernel ::
    TransitionMatrix (Finite 3) ->
    Kernel.TransitionKernel (Finite 3)
asTransitionKernel matrix =
    Kernel.fromLaws $ \source ->
        checked $
            DistributionMap.fromList
                [ (destination, stepProbability matrix source destination)
                | destination <- finites
                ]

spec :: Spec
spec =
    describe "Transition interface" $ do
        it "exposes a matrix row as a finite-support transition law" $
            Distribution.distributionWeights (Transition.transitionLaw finiteChain 1)
                `shouldBe` [(1, 0.2), (2, 0.8)]

        it "exposes a source-dependent kernel through the same operation" $
            let kernel =
                    Kernel.fromLaws $ \source ->
                        checked $
                            DistributionMap.fromList
                                [(source, 0.25), (source + 1, 0.75 :: Double)]
             in Distribution.distributionWeights (Transition.transitionLaw kernel (4 :: Int))
                    `shouldBe` [(4, 0.25), (5, 0.75)]

        it "exposes deterministic kernels as point-mass laws" $
            Distribution.distributionWeights
                ( Transition.transitionLaw
                    (Kernel.fromLaws (DistributionMap.pointMass . (+ 1)))
                    (4 :: Int)
                )
                `shouldBe` [(5, 1)]

        prop "gives matrices and equivalent kernels approximately equal laws" $
            forAll (genTransitionRows 3) $ \rawMatrix ->
                case fromRows rawMatrix ::
                        Either TransitionMatrixError (TransitionMatrix (Finite 3)) of
                    Left problem -> counterexample (show problem) False
                    Right matrix ->
                        let kernel = asTransitionKernel matrix
                         in conjoin
                                [ let matrixLaw =
                                        Transition.transitionLaw matrix source
                                      kernelLaw =
                                        Transition.transitionLaw kernel source
                                   in counterexample ("source: " <> show source) $
                                        Distribution.support matrixLaw
                                            == Distribution.support kernelLaw
                                            && and
                                                [ approxEq
                                                    1e-12
                                                    ( Distribution.probabilityAt
                                                        matrixLaw
                                                        destination
                                                    )
                                                    ( Distribution.probabilityAt
                                                        kernelLaw
                                                        destination
                                                    )
                                                | destination <- finites :: [Finite 3]
                                                ]
                                | source <- finites :: [Finite 3]
                                ]