dtmc-0.2.0.0: test/Dtmc/Distribution/MapSpec.hs
module Dtmc.Distribution.MapSpec (
spec,
) where
import Data.Map.Strict qualified as Map
import Dtmc.Distribution (
Distribution (..),
DistributionError (..),
)
import Dtmc.Distribution.Map (
DistributionMap,
fromList,
mapStates,
pointMass,
toMap,
)
import Dtmc.Simplex (
SimplexError (..),
)
import Dtmc.TestSupport (
approxEq,
)
import Test.Hspec (
Spec,
describe,
expectationFailure,
it,
shouldBe,
shouldSatisfy,
)
spec :: Spec
spec =
describe "DistributionMap" $ do
it "combines duplicates and stores canonical ascending entries" $ do
let distribution =
either (error . show) id $
fromList
[('b', 0.2), ('a', 0.5), ('b', 0.3), ('c', 0)]
distributionWeights distribution `shouldBe` [('a', 0.5), ('b', 0.5)]
support distribution `shouldBe` ['a', 'b']
Map.toAscList (toMap distribution)
`shouldBe` [('a', 0.5), ('b', 0.5)]
it "returns zero for an absent state" $
probabilityAt (pointMass "present") "absent"
`shouldBe` 0
it "pushes weights through a state mapping" $
case
( fromList [(-1, 0.5), (1, 0.5)] ::
Either DistributionError (DistributionMap Int)
)
of
Right steps ->
distributionWeights (mapStates (+ 10) steps)
`shouldBe` [(9, 0.5), (11, 0.5)]
Left err ->
expectationFailure
("expected acceptance, got " <> show err)
it "combines weights whose states map to the same target" $
case
( fromList [(0, 0.25), (1, 0.25), (2, 0.5)] ::
Either DistributionError (DistributionMap Int)
)
of
Right distribution ->
distributionWeights (mapStates (`mod` 2) distribution)
`shouldBe` [(0, 0.75), (1, 0.25)]
Left err ->
expectationFailure
("expected acceptance, got " <> show err)
it "rejects an empty law" $
(fromList [] :: Either DistributionError (DistributionMap Int))
`shouldSatisfy` either (const True) (const False)
it "uses the shared error type" $
fromList ([] :: [(Int, Double)])
`shouldBe` Left (DistributionError (SumOffBy 0))
it "removes weights repaired to zero" $
case fromList [('a', -1e-17), ('b', 1)] of
Right distribution ->
Map.toAscList (toMap distribution)
`shouldBe` [('b', 1)]
Left err ->
expectationFailure
("expected acceptance, got " <> show err)
it "normalises an accepted combined total near one" $
case fromList [('a', 0.5), ('b', 0.5 - 5e-10)] of
Right distribution ->
approxEq
1e-12
(sum (Map.elems (toMap distribution)))
1
`shouldBe` True
Left err ->
expectationFailure
("expected acceptance, got " <> show err)
it "reports a non-finite combined weight by ascending state index" $
case fromList [('b', 1), ('a', 0 / 0), ('a', 0)] of
Left err ->
err `shouldBe` DistributionError (NonFiniteEntry 0)
Right _ ->
expectationFailure "expected rejection"