lattest-lib-0.1.0.0: src/Lattest/Model/Symbolic/Internal/FreeMonoidX.hs
{-# OPTIONS_HADDOCK hide, prune #-}
{-
This is a modified version of:
TorXakis - Model Based Testing
See LICENSE in the parent Symbolic folder.
-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
-----------------------------------------------------------------------------
-- |
-- Module : FreeMonoidX
-- Copyright : (c) TNO and Radboud University
-- License : BSD3 (see the file license.txt)
--
-- Maintainer : damian.nadales@gmail.com (Embedded Systems Innovation by TNO)
-- Stability : experimental
-- Portability : portable
--
-- Free commutative Monoid with a multiplication operation. Symbolic representation of
-- free-monoids of the form:
--
-- > a0 <> a1 <> ... <> an-1
--
-- `ai \in A`, `<>` is a commutative (binary) operation, `(A, <>)` is a
-- Monoid, there is a multiplication operator `<.>` that is distributive
-- w.r.t `<>`, and there is additive inverse for the elements of `A`:
--
-- > a <> b = b <> a -- for all a, b in A (commutativity)
-- > a <.> (b <> c) = (a <.> b) <> (a <.> c) -- for all a, b, c in A (left distributivity).
-- > (b <> c) <.> a = (b <.> a) <> (c <.> a) -- for all a, b, c in A (right distributivity).
-- > a <> -a = 0 -- for all a, for some (-a), where `0 = mempty`.
--
-- Furthermore, to be able to assign semantics to this symbolic representation
-- it is required that the terms are integer-multipliable, meaning that
--
-- > `a <.> n`
--
-- is equivalent to adding an element `a` `n` times if `n` is non-negative, or
-- removing it `n` times otherwise. See `IntMultipliable` class, and
-- `multiplyLaw` in `FreeMonoidXSpec`.
--
-- Note that we're using `<.>` as having both types `A -> A` and `Integral n =>
-- n -> a`, which is fine at the moment since the `FreeMonoidX`'s we are
-- dealing with are numeric types.
--
--
-- `FreeMonoidX a` is an instance of the `IsList` class, which means that in
-- combination with the `OverloadedLists` extension is possible to write
-- free-monoids as lists. For instance the free-monoid:
--
-- > a0 <> a1 <> ... <> an-1
--
-- Can be written as
--
-- [a0, a1, ..., an-1]
--
-----------------------------------------------------------------------------
module Lattest.Model.Symbolic.Internal.FreeMonoidX
( -- * Free Monoid with multiplication Type
FreeMonoidX (..)
-- * Query
, nrofDistinctTerms
, distinctTerms -- exposed for performance reasons checking properties for
-- all distinct terms is faster than for all terms
, distinctTermsT
, allFreeMonoidX
-- * Map
, mapTerms
, mapFreeMonoidX
-- * Filter
, partition
, partitionT
-- * Folds
, foldrTerms
, foldOccur
, foldFMX
, Lattest.Model.Symbolic.Internal.FreeMonoidX.fold
-- * Manipulation of the free monoid
, append
, remove
, appendMany
, flatten
-- * Multiplication operator
, (<.>)
-- * Monoid terms restriction
, IntMultipliable
, TermWrapper (..)
-- * Occurrence lists
, toOccurList
, toDistinctAscOccurList
, fromOccurList
, fromDistinctAscOccurList
-- ** Occurrence lists of `TermWrapper`'s
, fromListT
, toOccurListT
, toDistinctAscOccurListT
, fromOccurListT
, fromDistinctAscPowerListT
-- ** Lists of direct items
, GHC.Exts.toList
, GHC.Exts.fromList
)
where
import Control.Arrow (first, (***))
import Data.Data
import Data.Foldable
import Data.List (genericReplicate)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import GHC.Exts
import GHC.Generics (Generic)
-- | Symbolic representation of a polynomial, where each term is a member of
-- type `a`.
--
-- The integer value of the map represents the number of occurrences of a term
-- in the polynomial. Given this representation it is crucial that the
-- operation is commutative, since the information of about the order of the
-- term is lost in this representation.
--
newtype FreeMonoidX a = FMX { asMap :: Map a Integer }
deriving (Eq, Ord, Read, Generic, Data)
-- | A term of the monoid which wraps a value. This could be for instance a
-- sum-term or a product term.
class TermWrapper f where
wrap :: a -> f a
unwrap :: f a -> a
-- | Types that can be multiplied by an integral. This restriction is required
-- to be able to assign a correct semantic to the symbolic representation of
-- the free-monoid. If the elements of the free-monoid are not
-- `IntMultipliable`, then `foldFMX` is ill-defined.
--
-- See the test code `FreeMonoidXspec` for examples of instances of this class.
class IntMultipliable a where
-- | `n <.> x` multiplies `x` `n` times.
(<.>) :: Integral n
=> n -- ^ Multiplication factor.
-> a -- ^ Element to multiply.
-> a
instance Ord a => IntMultipliable (FreeMonoidX a) where
0 <.> _ = mempty
n <.> (FMX p) = FMX $ (toInteger n *) <$> p
instance Ord a => Semigroup (FreeMonoidX a) where
FMX p0 <> FMX p1 = FMX $ Map.filter (/= 0) $ Map.unionWith (+) p0 p1
instance Ord a => Monoid (FreeMonoidX a) where
mempty = FMX []
instance Ord a => IsList (FreeMonoidX a) where
type Item (FreeMonoidX a) = a
fromList xs = FMX $ Map.fromListWith (+) $ map (,1) xs
toList (FMX p) = do
(x, n) <- Map.toList p
genericReplicate n x
-- | /O(t*log t)/. Create a product from a list of terms using the given term
-- wrapper.
fromListT :: (Ord (t a), TermWrapper t) => [a] -> FreeMonoidX (t a)
fromListT = fromList . (wrap <$>)
-- | Fold the free-monoid.
foldFMX :: (IntMultipliable a, Monoid a) => FreeMonoidX a -> a
foldFMX (FMX p) = Map.foldrWithKey (\x n -> (n <.> x <>)) mempty p
-- | Fold the free-monoid of term wrappers.
fold :: (IntMultipliable (t a), Monoid (t a), TermWrapper t) => FreeMonoidX (t a) -> a
fold = unwrap . foldFMX
-- | Number of distinct terms in the free-monoid.
nrofDistinctTerms :: FreeMonoidX a -> Int
nrofDistinctTerms = Map.size . asMap
-- | /O(n)/. The distinct terms of a free-monoid., each term occurs only once in
-- the list.
--
distinctTerms :: FreeMonoidX a -> [a]
distinctTerms = Map.keys . asMap
distinctTermsT :: TermWrapper t => FreeMonoidX (t a) -> [a]
distinctTermsT = (unwrap <$>) . distinctTerms
-- | /O(n)/. Convert the free-monoid to a list of (term, occurrence) tuples.
--
-- For instance given the term:
--
-- > a <> b <> a <> b <> a
--
-- `toOccurList` applied to it will result in the following list:
--
-- > [(a, 3), (b, 2)]
--
toOccurList :: FreeMonoidX a -> [(a, Integer)]
toOccurList = toDistinctAscOccurList
toOccurListT :: TermWrapper t => FreeMonoidX (t a) -> [(a, Integer)]
toOccurListT = (first unwrap <$>) . toDistinctAscOccurList
-- | /O(n)/. Convert the free-monoid to a distinct ascending list of term\/multiplier
-- pairs.
toDistinctAscOccurList :: FreeMonoidX a -> [(a, Integer)]
toDistinctAscOccurList = Map.toAscList . asMap
toDistinctAscOccurListT :: TermWrapper t => FreeMonoidX (t a) -> [(a, Integer)]
toDistinctAscOccurListT = (first unwrap <$>) . toDistinctAscOccurList
-- | /O(n*log n)/. Create a free-monoid from a list of term\/multiplier pairs.
fromOccurList :: Ord a => [(a, Integer)] -> FreeMonoidX a
fromOccurList = FMX . Map.filter (0/=) . Map.fromListWith (+)
fromOccurListT :: (Ord (t a), TermWrapper t) => [(a, Integer)] -> FreeMonoidX (t a)
fromOccurListT = fromOccurList . (first wrap <$>)
-- | /O(n)/. Build a free-monoid from an ascending list of term\/multiplier
-- pairs where each term appears only once. /The precondition (input list is
-- strictly ascending) is not checked./
fromDistinctAscOccurList :: [(a, Integer)] -> FreeMonoidX a
fromDistinctAscOccurList = FMX . Map.filter (0/=) . Map.fromDistinctAscList
fromDistinctAscPowerListT :: TermWrapper t => [(a, Integer)] -> FreeMonoidX (t a)
fromDistinctAscPowerListT = fromDistinctAscOccurList . (first wrap <$>)
-- | Append a term to the free-monoid.
--
-- > append 2 [1, 2, 3]
--
-- should be equivalent to the free-monoid.
--
-- > [1, 2, 3, 2]
append :: Ord a => a -> FreeMonoidX a -> FreeMonoidX a
append = appendMany 1
-- | Remove a term from the free-monoid.
--
-- > remove 2 (1 <> 2 <> 3)
--
-- should be equivalent to the free-monoid
--
-- > (1 <> 3)
remove :: Ord a => a -> FreeMonoidX a -> FreeMonoidX a
remove = appendMany (-1)
-- | Add the term `x` `n` times. If `n` is negative the term will be removed
-- `n` times.
--
-- > appendMany 2 10 (10 <> 12 <> 12)
--
-- should be equivalent to
--
-- (10 <> 10 <> 10 <> 12 <> 12)
--
-- > appendMany (-2) 10 (10 <> 12 <> 12)
--
-- should be equivalent to
--
-- > (-10 <> 12 <> 12)
appendMany :: Ord a => Integer -> a -> FreeMonoidX a -> FreeMonoidX a
appendMany 0 _ s = s -- invariant: no term with multiplier 0 is stored.
appendMany m x s = (FMX . Map.alter increment x . asMap) s
where
increment :: Maybe Integer -> Maybe Integer
increment Nothing = Just m
increment (Just n) | n == -m = Nothing -- Terms with multiplier zero are removed
increment (Just n) = Just (n+m)
-- | /O(n)/. Partition the free-monoid into two free-monoids, one with all
-- elements that satisfy the predicate and one with all elements that don't
-- satisfy the predicate.
partition :: (a -> Bool) -> FreeMonoidX a -> (FreeMonoidX a,FreeMonoidX a)
partition p = (FMX *** FMX) . Map.partitionWithKey (\k _ -> p k) . asMap
partitionT :: TermWrapper t => (a -> Bool) -> FreeMonoidX (t a)
-> (FreeMonoidX (t a), FreeMonoidX (t a))
partitionT p = partition (p . unwrap)
-- | /O(n)/. Fold over the terms of the free-monoid with their multipliers.
foldOccur :: (a -> Integer -> b -> b) -> b -> FreeMonoidX a -> b
foldOccur f z = Map.foldrWithKey f z . asMap
foldrTerms :: (TermWrapper f) => (a -> b -> b) -> b -> FreeMonoidX (f a) -> b
foldrTerms f e = foldr f e . distinctTermsT
-- | Map the terms of the free-monoid.
--
mapTerms :: Ord b => (a -> b) -> FreeMonoidX a -> FreeMonoidX b
mapTerms f = fromOccurList . (first f <$>) . toOccurList
mapFreeMonoidX :: (Functor f, Ord (f b)) => (a -> b) -> FreeMonoidX (f a) -> FreeMonoidX (f b)
mapFreeMonoidX f = mapTerms (fmap f)
allFreeMonoidX :: (TermWrapper f) => (a -> Bool) -> FreeMonoidX (f a) -> Bool
allFreeMonoidX p = all p . distinctTermsT
-- | Flatten a free-monoid.
--
-- For instance, the monoid
--
-- > (a <> b) <> (a <> b) <> a
--
-- will be rewritten as:
--
-- > a <> a <> a <> b <> b
--
-- Assuming `a < b`.
--
flatten :: (Ord a) => FreeMonoidX (FreeMonoidX a) -> FreeMonoidX a
flatten (FMX p) = Data.Foldable.fold $ multiplyFMX <$> Map.toAscList p
where
multiplyFMX :: Ord a => (FreeMonoidX a, Integer) -> FreeMonoidX a
multiplyFMX (fm, n) = n <.> fm