dtmc-0.2.0.0: test/Dtmc/Analysis/ProbabilityOracle.hs
module Dtmc.Analysis.ProbabilityOracle (
TruncatedLaw,
transitionWeight,
trajectoryProbability,
stateProbability,
observationProbability,
hittingLaw,
returnLaw,
visitLawBefore,
raceProbabilityWithin,
lawProbability,
lawUnresolvedMass,
lawFiniteExpectation,
) where
import Data.Finite (
getFinite,
)
import Data.List (
findIndex,
)
import Data.Map.Strict (
Map,
)
import Data.Map.Strict qualified as Map
import Dtmc.Analysis.Event (
DiscreteEvent (..),
includesInfiniteOutcome,
matches,
)
import Dtmc.State (
FiniteState,
finiteStates,
stateIndex,
)
import Dtmc.Transition.Matrix (
TransitionMatrix,
toRows,
)
import Numeric.Natural (
Natural,
)
data WeightedPath state = WeightedPath [state] Double
{- | A finite prefix of a discrete law. 'lawUnresolvedMass' is the probability
whose event time is strictly beyond the stored horizon, including any atom at
infinity. This test-only type deliberately does not appear in the library API.
-}
data TruncatedLaw = TruncatedLaw
{ lawHorizon :: Natural
, lawFiniteMasses :: Map Natural Double
, lawUnresolvedMass :: Double
}
toIndex :: (FiniteState state) => state -> Int
toIndex = fromIntegral . getFinite . stateIndex
transitionWeight ::
(FiniteState state) =>
TransitionMatrix state ->
state ->
state ->
Double
transitionWeight matrix source destination =
toRows matrix !! toIndex source !! toIndex destination
iterateNatural :: Natural -> (value -> value) -> value -> value
iterateNatural steps advance = go steps
where
go 0 value = value
go remaining value = go (remaining - 1) (advance value)
weightedTrajectories ::
(FiniteState state) =>
Natural ->
[(state, Double)] ->
TransitionMatrix state ->
[WeightedPath state]
weightedTrajectories steps initial matrix =
iterateNatural steps advance initialPaths
where
initialPaths =
[ WeightedPath [state] weight
| (state, weight) <- initial
, weight /= 0
]
advance paths = paths >>= extend
extend (WeightedPath path weight) =
[ WeightedPath (path <> [destination]) (weight * probability)
| destination <- finiteStates
, let probability = transitionWeight matrix (last path) destination
, probability /= 0
]
trajectoryProbability ::
(FiniteState state) =>
[(state, Double)] ->
TransitionMatrix state ->
[state] ->
Double
trajectoryProbability _ _ [] = 0
trajectoryProbability initial matrix (first : rest) =
initialMass first * go first rest
where
initialMass state =
sum [weight | (candidate, weight) <- initial, candidate == state]
go _ [] = 1
go previous (next : more) =
transitionWeight matrix previous next * go next more
stateProbability ::
(FiniteState state) =>
Natural ->
[(state, Double)] ->
TransitionMatrix state ->
state ->
Double
stateProbability time initial matrix destination =
sum
[ weight
| WeightedPath path weight <- weightedTrajectories time initial matrix
, last path == destination
]
observationProbability ::
(FiniteState state) =>
Natural ->
[(state, Double)] ->
TransitionMatrix state ->
[(Natural, state)] ->
Double
observationProbability horizon initial matrix observations =
sum
[ weight
| WeightedPath path weight <- weightedTrajectories horizon initial matrix
, all (matchesAt path) observations
]
where
matchesAt path (time, expected) =
path !! fromIntegral time == expected
lawFromFirstOccurrence ::
Natural ->
[WeightedPath state] ->
([state] -> Maybe Natural) ->
TruncatedLaw
lawFromFirstOccurrence horizon paths occurrence =
TruncatedLaw horizon masses unresolved
where
(masses, unresolved) = foldr addPath (Map.empty, 0) paths
addPath (WeightedPath path weight) (known, unknown) =
case occurrence path of
Nothing -> (known, unknown + weight)
Just time -> (Map.insertWith (+) time weight known, unknown)
hittingLaw ::
(FiniteState state) =>
Natural ->
TransitionMatrix state ->
(state -> Bool) ->
state ->
TruncatedLaw
hittingLaw horizon matrix isTarget initial =
lawFromFirstOccurrence horizon paths firstHit
where
paths = weightedTrajectories horizon [(initial, 1)] matrix
firstHit path = fromIntegral <$> findIndex isTarget path
returnLaw ::
(FiniteState state) =>
Natural ->
TransitionMatrix state ->
state ->
TruncatedLaw
returnLaw horizon matrix initial =
lawFromFirstOccurrence horizon paths firstReturn
where
paths = weightedTrajectories horizon [(initial, 1)] matrix
firstReturn path =
fromIntegral . (+ 1) <$> findIndex (== initial) (drop 1 path)
visitLawBefore ::
(FiniteState state) =>
Natural ->
[(state, Double)] ->
TransitionMatrix state ->
(state -> Bool) ->
TruncatedLaw
visitLawBefore bound initial matrix isVisited =
TruncatedLaw bound masses 0
where
steps
| bound == 0 = 0
| otherwise = bound - 1
paths = weightedTrajectories steps initial matrix
count path =
fromIntegral (length (filter isVisited (take (fromIntegral bound) path)))
masses =
Map.fromListWith
(+)
[(count path, weight) | WeightedPath path weight <- paths]
raceProbabilityWithin ::
(FiniteState state) =>
Natural ->
TransitionMatrix state ->
(state -> Bool) ->
(state -> Bool) ->
state ->
Double
raceProbabilityWithin horizon matrix isSuccessful isCompeting initial =
sum
[ weight
| WeightedPath path weight <-
weightedTrajectories horizon [(initial, 1)] matrix
, wins path
]
where
wins path =
case (findIndex isSuccessful path, findIndex isCompeting path) of
(Just successfulTime, Just competingTime) ->
successfulTime < competingTime
(Just _, Nothing) -> True
_ -> False
lawProbability :: DiscreteEvent -> TruncatedLaw -> Maybe Double
lawProbability event law
| eventKnown event (lawHorizon law) =
Just (finiteMass + unresolvedContribution)
| otherwise = Nothing
where
finiteMass =
sum
[ mass
| (value, mass) <- Map.toList (lawFiniteMasses law)
, matches event value
]
unresolvedContribution
| includesInfiniteOutcome event = lawUnresolvedMass law
| otherwise = 0
eventKnown :: DiscreteEvent -> Natural -> Bool
eventKnown event horizon =
case event of
EqualTo threshold -> threshold <= horizon
LessThan threshold -> threshold <= horizon + 1
AtMost threshold -> threshold <= horizon
GreaterThan threshold -> threshold <= horizon
AtLeast threshold -> threshold <= horizon + 1
lawFiniteExpectation :: TruncatedLaw -> Maybe Double
lawFiniteExpectation law
| lawUnresolvedMass law == 0 =
Just
( sum
[ fromIntegral value * mass
| (value, mass) <- Map.toList (lawFiniteMasses law)
]
)
| otherwise = Nothing