packages feed

dtmc-0.2.0.0: src/Dtmc/Dynamics/Internal.hs

{- |
Module      : Dtmc.Dynamics.Internal
Description : Unchecked sparse forward-dynamics primitive.

Sparse weight propagation shared by public evolution and path-analysis
algorithms. Inputs may be sub-probability maps; no simplex invariant is
required or restored here.
-}
module Dtmc.Dynamics.Internal (
    pushSparseWeights,
) where

import Data.Map.Strict (
    Map,
 )
import Data.Map.Strict qualified as Map
import Dtmc.Distribution.Map.Internal (
    unDistributionMap,
 )
import Dtmc.Transition (
    Transition (..),
 )

{- | Push a finite, possibly sub-probability weight map through one locally
finite kernel step. Exact zero results are removed. No validation, clamping,
or renormalisation is performed.

For the complexity bounds, @s@ is the number of source states, @e@ the number
of traversed support edges, @u@ the number of distinct destinations
encountered, and @r@ the number retained after exact-zero removal.

Complexity: excluding 'transitionLaw' evaluation,
@O(s + e log(u + 1) + u)@ time, @O(u)@ temporary space, and @O(r)@ result
space.
-}
pushSparseWeights ::
    (Transition kernel, Ord (TransitionState kernel)) =>
    Map (TransitionState kernel) Double ->
    kernel ->
    Map (TransitionState kernel) Double
pushSparseWeights weights kernel =
    Map.filter (/= 0) (Map.foldlWithKey' pushState Map.empty weights)
  where
    pushState accumulated state stateWeight =
        Map.foldlWithKey'
            ( \next nextState transitionWeight ->
                Map.insertWith
                    (+)
                    nextState
                    (stateWeight * transitionWeight)
                    next
            )
            accumulated
            (unDistributionMap (transitionLaw kernel state))