dtmc-0.2.0.0: src/Dtmc/State/Internal.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{- |
Module : Dtmc.State.Internal
Description : Checked conversions for finite-state indices.
Shared conversions between named finite states and the integer indices used by
dynamic graph and linear-algebra code. Keeping the reverse conversion here
prevents an arbitrary 'Int' from being passed directly to 'Data.Finite.finite',
which wraps out-of-range values modulo the state-space cardinality.
-}
module Dtmc.State.Internal (
stateCardinalityInt,
stateIndexInt,
stateFromInt,
) where
import Data.Finite (
finite,
getFinite,
)
import Data.Proxy (
Proxy (Proxy),
)
import Dtmc.State (
Cardinality,
FiniteState,
stateAt,
stateIndex,
)
import GHC.TypeNats (
natVal,
)
{- | Return the number of states as a runtime 'Int'.
Complexity: @O(1)@ time and @O(1)@ space.
-}
stateCardinalityInt :: forall state. (FiniteState state) => Int
stateCardinalityInt =
fromIntegral (natVal (Proxy @(Cardinality state)))
{- | Return the canonical zero-based integer index of a state.
Complexity: the cost of 'stateIndex' plus @O(1)@ time and @O(1)@ space.
-}
stateIndexInt :: (FiniteState state) => state -> Int
stateIndexInt = fromIntegral . getFinite . stateIndex
{- | Recover the state at a runtime integer index. Returns 'Nothing' rather
than wrapping a negative or out-of-range integer modulo the state count.
Complexity: the cost of 'stateAt' plus @O(1)@ time and @O(1)@ space.
-}
stateFromInt :: forall state. (FiniteState state) => Int -> Maybe state
stateFromInt index
| index < 0 || index >= stateCardinalityInt @state = Nothing
| otherwise = Just (stateAt (finite (fromIntegral index)))