From f0ecb4446d4e6ca52f543e72756c5a03fb967d1a Mon Sep 17 00:00:00 2001
From: Hans-Peter Deifel <hpd@hpdeifel.de>
Date: Sat, 14 Jul 2018 12:04:04 +0200
Subject: [PATCH] Implement distribution functor

---
 ma.cabal                              |  2 +
 src/MA/Functors.hs                    |  3 +-
 src/MA/Functors/Distribution.hs       | 61 +++++++++++++++++++++++++
 src/MA/Functors/MonoidValued.hs       |  8 +++-
 tests/MA/Functors/DistributionSpec.hs | 65 +++++++++++++++++++++++++++
 5 files changed, 137 insertions(+), 2 deletions(-)
 create mode 100644 src/MA/Functors/Distribution.hs
 create mode 100644 tests/MA/Functors/DistributionSpec.hs

diff --git a/ma.cabal b/ma.cabal
index 069acdd..5e163e4 100644
--- a/ma.cabal
+++ b/ma.cabal
@@ -35,6 +35,7 @@ library
                      , MA.Functors.Bag
                      , MA.Functors.FixedProduct
                      , MA.Functors.MonoidValued
+                     , MA.Functors.Distribution
                      , MA.Functors.SomeFunctor
                      , MA.Parser
                      , MA.Parser.Lexer
@@ -107,6 +108,7 @@ test-suite spec
                      , MA.Functors.PowersetSpec
                      , MA.Functors.MonoidValuedSpec
                      , MA.Functors.BagSpec
+                     , MA.Functors.DistributionSpec
                      , MA.FunctorExpression.ParserSpec
                      , MA.FunctorExpression.PrettySpec
                      , MA.FunctorExpression.SortsSpec
diff --git a/src/MA/Functors.hs b/src/MA/Functors.hs
index 7b834db..0ddc463 100644
--- a/src/MA/Functors.hs
+++ b/src/MA/Functors.hs
@@ -8,12 +8,13 @@ import           MA.Functors.FixedProduct (fixedproduct)
 import           MA.Functors.MonoidValued (intValued, realValued)
 import           MA.Functors.Powerset (powerset)
 import           MA.Functors.Bag (bag)
+import           MA.Functors.Distribution (distribution)
 import           MA.Functors.SomeFunctor
 import           MA.FunctorExpression.Parser
 
 registeredFunctors :: [[FunctorParser SomeFunctor]]
 registeredFunctors =
   [ [someFunctor intValued, someFunctor realValued]
-  , [someFunctor powerset, someFunctor bag]
+  , [someFunctor powerset, someFunctor bag, someFunctor distribution]
   , [someFunctor fixedproduct]
   ]
diff --git a/src/MA/Functors/Distribution.hs b/src/MA/Functors/Distribution.hs
new file mode 100644
index 0000000..f748a8a
--- /dev/null
+++ b/src/MA/Functors/Distribution.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE TemplateHaskell #-}
+module MA.Functors.Distribution
+  ( distribution
+  , Distribution(..)
+  ) where
+
+import           Prelude hiding (init)
+import           Control.Monad (when)
+
+import           Text.Megaparsec
+import           Data.Eq.Deriving (deriveEq1)
+import           Text.Show.Deriving (deriveShow1)
+
+import           MA.RefinementInterface
+import qualified MA.Parser.Lexer as L
+import           MA.FunctorExpression.Parser
+import           MA.Coalgebra.RefinementTypes
+import           MA.Coalgebra.Parser
+import           MA.Functors.MonoidValued
+
+
+newtype Distribution x = Distribution x
+  deriving (Functor, Foldable, Traversable)
+
+distribution :: FunctorParser Distribution
+distribution = Prefix ((L.symbol "D" <|> L.symbol "Ɗ") >> pure Distribution)
+
+type instance Label Distribution = Label (MonoidValued Double)
+type instance Weight Distribution = Weight (MonoidValued Double)
+type instance H1 Distribution = H1 (MonoidValued Double)
+type instance H3 Distribution = H3 (MonoidValued Double)
+
+$(deriveEq1 ''Distribution)
+$(deriveShow1 ''Distribution)
+
+deriving instance Show (Distribution ())
+
+instance ParseMorphism Distribution where
+  parseMorphismPoint (Distribution inner) = do
+    (h1, succs) <- parseMorphismPoint (RealValued inner)
+
+    when (h1 /= 1) $
+      fail "distribution: Sum of outgoing labels is not 1"
+
+    return (h1, succs)
+
+instance RefinementInterface Distribution where
+  init _ _ = (0, 1)
+  update weightsToS (toRest, toC) =
+    let
+      toS = sum weightsToS
+      toCwithoutS = toC - toS
+      isOk x = x >= 0 && x <= 1
+     in
+      if isOk toRest && isOk toCwithoutS && isOk toS
+        then mkRes (toRest, toCwithoutS, toS)
+        else mkRes (0, 0, 1)
+    where
+      mkRes h3@(a, b, c) = ((a + b, c), h3, (a + c, b))
diff --git a/src/MA/Functors/MonoidValued.hs b/src/MA/Functors/MonoidValued.hs
index 401a262..cd9b5a3 100644
--- a/src/MA/Functors/MonoidValued.hs
+++ b/src/MA/Functors/MonoidValued.hs
@@ -1,6 +1,11 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 
+-- | The monoid valued functor @M^X@.
+--
+-- The 'RefinementInterface' for this functor is implemented in an abstract
+-- fashion over any type that satisfies the 'Num' constraint, but concrete
+-- parsers only exist for integers and reals.
 module MA.Functors.MonoidValued
   ( intValued
   , realValued
@@ -20,7 +25,8 @@ import qualified MA.Parser.Lexer as L
 import           MA.Parser.Types
 
 data MonoidValued m a where
-  RealValued :: a -> MonoidValued Double a
+  RealValued :: a -> MonoidValued Double a  -- FIXME: Handle rounding and
+                                            -- comparison of doubles sensibly
   IntValued  :: a -> MonoidValued Int a
 
 deriving instance Show (MonoidValued m ())
diff --git a/tests/MA/Functors/DistributionSpec.hs b/tests/MA/Functors/DistributionSpec.hs
new file mode 100644
index 0000000..aa285d2
--- /dev/null
+++ b/tests/MA/Functors/DistributionSpec.hs
@@ -0,0 +1,65 @@
+module MA.Functors.DistributionSpec (spec) where
+
+import           Test.Hspec
+import           Test.Hspec.Megaparsec
+
+import           Data.Proxy
+import           Control.Monad.ST
+import           Data.Either
+
+import qualified Data.Vector as V
+
+import           Data.MorphismEncoding (Encoding)
+import qualified Data.MorphismEncoding as Encoding
+import qualified Data.Partition as Part
+import           MA.Algorithm
+import           MA.Coalgebra.Parser
+import           MA.FunctorExpression.Desorting
+import           MA.FunctorExpression.Parser
+import           MA.FunctorExpression.Type
+import           MA.Functors.Distribution
+
+
+spec :: Spec
+spec = do
+  functorExpressionSpec
+  parseMorphismPointSpec
+  refineSpec
+
+functorExpressionSpec :: Spec
+functorExpressionSpec = describe "functorExpression" $ do
+  it "parses a normal D correctly" $
+    parseFunctorExpression [[distribution]] "" "DX" `shouldParse` (Functor 1 (Distribution Variable))
+
+  it "parses a unicode D (Ɗ) correctly" $
+    parseFunctorExpression [[distribution]] "" "ƊX" `shouldParse` (Functor 1 (Distribution Variable))
+
+parseMorphismPointSpec :: Spec
+parseMorphismPointSpec = describe "parseMorphismPoint" $ do
+  it "works for a simple example" $
+    (snd <$>
+     parseMorphisms
+       (Functor 1 (Distribution Variable))
+       ""
+       "x: {x: 0.5, y: 0.5}\ny: {x: 1.0}") `shouldParse`
+    encoding [(1, 1), (1, 1)] [(0, (1, 0.5), 0), (0, (1, 0.5), 1), (1, (1, 1), 0)]
+
+  it "errors if esge weight sum isn't 1" $
+    parseMorphisms (Functor 1 (Distribution Variable)) "" `shouldFailOn`
+      "x: {x: 0.5}"
+
+refineSpec :: Spec
+refineSpec = describe "refining" $ do
+  let f = Functor 1 (Distribution Variable)
+
+  it "handles states with incoming edges greater than 1" $ do
+    let x = parseMorphisms f "" "x: {z: 1.0}\ny: {z: 1.0}\nz: {y: 1.0}"
+    x `shouldSatisfy` isRight
+    let Right (_, enc) = x
+    stToIO (refine (Proxy @(Desorted Distribution)) enc) `shouldReturn` Part.fromBlocks [[0, 1, 2]]
+
+-- FIXME: Remove duplicate definition of this function
+encoding :: [h1] -> [(Int, l, Int)] -> Encoding l h1
+encoding h1 es = Encoding.new (V.fromList h1) (V.fromList (map toEdge es))
+  where
+    toEdge (from, lab, to) = Encoding.Edge from lab to
-- 
GitLab