on master help sign in
Added documentation
commitb3fbc732ac00945a10bdec63c9bdf520e2fad26d authorArmando Santos <armandoifsantos@gmail.com> dateSun 29 Dec 2019, 16:46 (7 years ago) parent62d6018
7 files changed+506−167
app/Main.hs+1−1 src/Dist.hs+45−10 src/Dist/Internal.hs+12−1 src/Matrix/Nat.hs+67−61 src/Matrix/Type.hs+171−65 src/Utils.hs+97−7 src/Utils/Internal.hs+113−22
app/Main.hs+1−1
@@ -34,7 +34,7 @@ mulSS = coerceNat (*)
3434
3535sumSSM = fromF' (uncurry sumSS)
3636
37−mulSSM3 = fromF' (mulSS (Nat 3))
37+mulSSM3 = fromF' (mulSS (nat 3))
3838
3939die :: Dist (Natural 6) 6
4040die = uniform [nat @6 1 .. nat 6]
src/Dist.hs+45−10
@@ -1,13 +1,48 @@
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Dist
4+-- Copyright : (c) Armando Santos 2019-2020
5+-- Maintainer : armandoifsantos@gmail.com
6+-- Stability : experimental
7+--
8+-- __LAoP__ is a library for algebraic (inductive) construction and manipulation of matrices
9+-- in Haskell. See <https://github.com/bolt12/master-thesis my Msc Thesis> for the
10+-- motivation behind the library, the underlying theory, and implementation details.
11+--
12+-- This module exports a type synonym 'Dist'
13+-- that represents a stochastic distribution
14+-- matrix and several distribution construction
15+-- functions.
16+--
17+-----------------------------------------------------------------------------
18+
119module Dist
2− (
3− Dist(..),
4− choose,
5− shape,
6− linear,
7− uniform,
8− negExp,
9− normal
10− )
11− where
20+ ( -- | If the sum of the rows of a column matrix
21+ -- is equal to 1 then this stochastic matrix
22+ -- can be seen as a probability distribution.
23+ --
24+ --
25+ -- This module is still experimental but it's
26+ -- already possible to model probabilistic programming
27+ -- problems with it. Import 'Matrix.Nat' or 'Matrix.Type'
28+ -- to access LAoP matrix combinators and then all you have
29+ -- to do is to define your sample space, either by creating a new data
30+ -- type or by abstracting it out via 'Natural'.
31+ --
32+ -- Write manipulation functions and promote them to matrices via
33+ -- 'fromF' or 'fromF'' and you're good to go!
34+
35+ -- * 'Dist' and 'Prob' type synonyms
36+ Dist (..),
37+ Prob,
38+ -- * Distribution construction functions
39+ choose,
40+ shape,
41+ linear,
42+ uniform,
43+ negExp,
44+ normal,
45+ )
46+where
1247
1348import Dist.Internal
src/Dist/Internal.hs+12−1
@@ -5,6 +5,7 @@
55module Dist.Internal
66 (
77 Dist(..),
8+ Prob,
89 choose,
910 shape,
1011 linear,
@@ -17,12 +18,18 @@ module Dist.Internal
1718import Matrix.Nat
1819import Utils
1920
21+-- | Type synonym for probability value
2022type Prob = Double
23+
24+-- | Type synonym for column vector matrices. This represents a probability
25+-- distribution.
2126type Dist a m = (FromLists Prob () (FromNat m)) => Matrix Prob 1 m
2227
28+-- | Constructs a Bernoulli distribution
2329choose :: Prob -> Dist a 2
2430choose prob = col [prob, 1 - prob]
2531
32+-- | Creates a distribution given a shape function
2633shape :: (Prob -> Prob) -> [a] -> Dist a m
2734shape _ [] = error "Probability.shape: empty list"
2835shape f xs =
@@ -30,19 +37,23 @@ shape f xs =
3037 ps = map f (iterate (+incr) 0)
3138 in fromFreqs (zip xs ps)
3239
40+-- | Constructs a Linear distribution
3341linear :: [a] -> Dist a m
3442linear = shape id
3543
44+-- | Constructs an Uniform distribution
3645uniform :: [a] -> Dist a m
3746uniform = shape (const 1)
3847
48+-- | Constructs an Negative Exponential distribution
3949negExp :: [a] -> Dist a m
4050negExp = shape (\x -> exp (-x))
4151
52+-- | Constructs an Normal distribution
4253normal :: [a] -> Dist a m
4354normal = shape (normalCurve 0.5 0.5)
4455
45−---
56+-- Auxiliary functions
4657
4758fromFreqs :: [(a,Prob)] -> Dist a m
4859fromFreqs xs = col (map (\(x,p) -> p/q) xs)
src/Matrix/Nat.hs+67−61
@@ -13,6 +13,26 @@
1313{-# LANGUAGE TypeOperators #-}
1414{-# LANGUAGE UndecidableInstances #-}
1515
16+-----------------------------------------------------------------------------
17+-- |
18+-- Module : Matrix.Nat
19+-- Copyright : (c) Armando Santos 2019-2020
20+-- Maintainer : armandoifsantos@gmail.com
21+-- Stability : experimental
22+--
23+-- The LAoP discipline generalises relations and functions treating them as
24+-- Boolean matrices and in turn consider these as arrows.
25+--
26+-- __LAoP__ is a library for algebraic (inductive) construction and manipulation of matrices
27+-- in Haskell. See <https://github.com/bolt12/master-thesis my Msc Thesis> for the
28+-- motivation behind the library, the underlying theory, and implementation details.
29+--
30+-- This module offers a newtype wrapper around 'Matrix.Type.Matrix' that
31+-- uses type level naturals instead of standard data types for the matrices
32+-- dimensions.
33+--
34+-----------------------------------------------------------------------------
35+
1636module Matrix.Nat
1737 ( -- | LAoP (Linear Algebra of Programming) Inductive Matrix definition.
1838 --
@@ -95,9 +115,7 @@ module Matrix.Nat
95115 -- | Note that given the restrictions imposed it is not possible to
96116 -- implement the standard type classes present in standard Haskell.
97117 -- *** Matrix pairing projections
98− KhatriP1,
99118 kp1,
100− KhatriP2,
101119 kp2,
102120
103121 -- *** Matrix pairing
@@ -340,59 +358,38 @@ infixl 5 -|-
340358
341359-- Khatri Rao Product and projections
342360
343−class KhatriP1 e (m :: Nat) (k :: Nat) where
344− kp1 :: Matrix e (m * k) m
345−
346−instance KhatriP1 e 0 k where
347− kp1 = empty
348−
349−instance
350− forall e m k.
361+kp1 ::
362+ forall e m k .
351363 ( Num e,
352− KnownNat k,
353− I.FromLists e (I.FromNat (m * k)) (I.FromNat m),
354364 KnownNat (I.Count (I.FromNat m)),
355− KnownNat (I.Count (I.FromNat (m * k)))
356− ) =>
357− KhatriP1 e m k
358− where
359− kp1 = matrixBuilder f
360− where
361− offset = fromInteger (natVal (Proxy :: Proxy k))
362− f (x, y)
363− | y >= (x * offset) && y <= (x * offset + offset - 1) = 1
364− | otherwise = 0
365−
366−class KhatriP2 e (m :: Nat) (k :: Nat) where
367− kp2 :: Matrix e (m * k) k
368−
369−instance KhatriP2 e m 0 where
370− kp2 = empty
371−
372−instance
373− forall e m k.
374− ( Num e,
375− KnownNat k,
376365 KnownNat (I.Count (I.FromNat k)),
377− I.FromLists e (I.FromNat (m * k)) (I.FromNat k),
378− KnownNat (I.Count (I.FromNat m)),
379− KnownNat (I.Count (I.FromNat (m * k)))
380− ) =>
381− KhatriP2 e m k
382− where
383− kp2 = matrixBuilder f
384− where
385− offset = fromInteger (natVal (Proxy :: Proxy k))
386− f (x, y)
387− | x == y || mod (y - x) offset == 0 = 1
388− | otherwise = 0
366+ KnownNat (I.Count (I.FromNat (m * k))),
367+ I.FromLists e (I.FromNat (m * k)) (I.FromNat m),
368+ I.FromNat (m * k) ~ I.FromNat (I.Count (I.FromNat m) * I.Count (I.FromNat k))
369+ ) => Matrix e (m * k) m
370+kp1 = M (I.kp1 @e @(I.FromNat m) @(I.FromNat k))
371+
372+kp2 ::
373+ forall e m k.
374+ ( Num e,
375+ KnownNat (I.Count (I.FromNat k)),
376+ I.FromLists e (I.FromNat (m * k)) (I.FromNat k),
377+ KnownNat (I.Count (I.FromNat m)),
378+ KnownNat (I.Count (I.FromNat (m * k))),
379+ I.FromNat (m * k) ~ I.FromNat (I.Count (I.FromNat m) * I.Count (I.FromNat k))
380+ ) => Matrix e (m * k) k
381+kp2 = M (I.kp2 @e @(I.FromNat m) @(I.FromNat k))
389382
390383khatri ::
391384 forall e cols a b.
392− (Num e, KhatriP1 e a b, KhatriP2 e a b) =>
393− Matrix e cols a ->
394− Matrix e cols b ->
395− Matrix e cols (a * b)
385+ ( Num e,
386+ KnownNat (I.Count (I.FromNat a)),
387+ KnownNat (I.Count (I.FromNat b)),
388+ KnownNat (I.Count (I.FromNat (a * b))),
389+ I.FromLists e (I.FromNat (a * b)) (I.FromNat a),
390+ I.FromLists e (I.FromNat (a * b)) (I.FromNat b),
391+ I.FromNat (a * b) ~ I.FromNat (I.Count (I.FromNat a) * I.Count (I.FromNat b))
392+ ) => Matrix e cols a -> Matrix e cols b -> Matrix e cols (a * b)
396393khatri a b =
397394 let kp1' = kp1 @e @a @b
398395 kp2' = kp2 @e @a @b
@@ -404,10 +401,20 @@ infixl 4 ><
404401
405402(><) ::
406403 forall e m p n q.
407− (Num e, KhatriP1 e m n, KhatriP2 e m n, KhatriP1 e p q, KhatriP2 e p q) =>
408− Matrix e m p ->
409− Matrix e n q ->
410− Matrix e (m * n) (p * q)
404+ ( Num e,
405+ KnownNat (I.Count (I.FromNat m)),
406+ KnownNat (I.Count (I.FromNat n)),
407+ KnownNat (I.Count (I.FromNat p)),
408+ KnownNat (I.Count (I.FromNat q)),
409+ KnownNat (I.Count (I.FromNat (m * n))),
410+ KnownNat (I.Count (I.FromNat (p * q))),
411+ I.FromLists e (I.FromNat (m * n)) (I.FromNat m),
412+ I.FromLists e (I.FromNat (m * n)) (I.FromNat n),
413+ I.FromLists e (I.FromNat (p * q)) (I.FromNat p),
414+ I.FromLists e (I.FromNat (p * q)) (I.FromNat q),
415+ I.FromNat (m * n) ~ I.FromNat (I.Count (I.FromNat m) * I.Count (I.FromNat n)),
416+ I.FromNat (p * q) ~ I.FromNat (I.Count (I.FromNat p) * I.Count (I.FromNat q))
417+ ) => Matrix e m p -> Matrix e n q -> Matrix e (m * n) (p * q)
411418(><) a b =
412419 let kp1' = kp1 @e @m @n
413420 kp2' = kp2 @e @m @n
@@ -430,13 +437,13 @@ tr (M m) = M (I.tr m)
430437
431438-- Selective 'select' operator
432439
433−select :: (Bounded a1, Bounded b, Enum a1, Enum b, Num e, Ord e,
434− KnownNat (I.Count a2), KnownNat (I.Count (I.FromNat rows1)),
435− I.FromLists e (I.FromNat rows1) a2,
436− I.FromLists e (I.FromNat rows1) (I.FromNat rows1), Eq b,
437− I.FromNat cols1 ~ I.FromNat cols2,
438− I.FromNat rows2 ~ Either a2 (I.FromNat rows1)) =>
439− Matrix e cols1 rows2 -> (a1 -> b) -> Matrix e cols2 rows1
440+select :: ( Bounded a1, Bounded b, Enum a1, Enum b, Num e, Ord e,
441+ KnownNat (I.Count a2), KnownNat (I.Count (I.FromNat rows1)),
442+ I.FromLists e (I.FromNat rows1) a2,
443+ I.FromLists e (I.FromNat rows1) (I.FromNat rows1), Eq b,
444+ I.FromNat cols1 ~ I.FromNat cols2,
445+ I.FromNat rows2 ~ Either a2 (I.FromNat rows1)) =>
446+ Matrix e cols1 rows2 -> (a1 -> b) -> Matrix e cols2 rows1
440447select (M m) y = M (I.select m y)
441448
442449-- McCarthy's Conditional
@@ -447,7 +454,6 @@ cond ::
447454 I.FromLists e () (I.FromNat cols),
448455 I.FromLists e (I.FromNat cols) (),
449456 I.FromLists e (I.FromNat cols) (I.FromNat cols),
450− I.KhatriP2 e () (I.FromNat cols),
451457 Bounded a,
452458 Enum a,
453459 Num e,
src/Matrix/Type.hs+171−65
@@ -13,22 +13,34 @@
1313{-# LANGUAGE TypeOperators #-}
1414{-# LANGUAGE UndecidableInstances #-}
1515
16+-----------------------------------------------------------------------------
17+-- |
18+-- Module : Matrix.Type
19+-- Copyright : (c) Armando Santos 2019-2020
20+-- Maintainer : armandoifsantos@gmail.com
21+-- Stability : experimental
22+--
23+-- The LAoP discipline generalises relations and functions treating them as
24+-- Boolean matrices and in turn consider these as arrows.
25+--
26+-- __LAoP__ is a library for algebraic (inductive) construction and manipulation of matrices
27+-- in Haskell. See <https://github.com/bolt12/master-thesis my Msc Thesis> for the
28+-- motivation behind the library, the underlying theory, and implementation details.
29+--
30+-- This module offers many of the combinators mentioned in the work of
31+-- Macedo (2012) and Oliveira (2012).
32+--
33+-----------------------------------------------------------------------------
34+
1635module Matrix.Type
17− ( -- | LAoP (Linear Algebra of Programming) Inductive Matrix definition.
18− --
19− -- LAoP generalises relations and functions treating them as
20− -- Boolean matrices and in turn consider these as arrows.
21− -- This library offers many of the combinators mentioned in the work of
22− -- Macedo (2012) and Oliveira (2012).
23− --
24− -- This definition makes use of the fact that 'Void' is
25− -- isomorphic to 0 and '()' to 1 and captures matrix
26− -- dimensions as stacks of 'Either's.
36+ ( -- | This definition makes use of the fact that 'Void' is
37+ -- isomorphic to 0 and '()' to 1 and captures matrix
38+ -- dimensions as stacks of 'Either's.
2739 --
28− -- There exists two type families that make it easier to write
29− -- matrix dimensions: 'FromNat' and 'Count'. This approach
30− -- leads to a very straightforward implementation
31− -- of LAoP combinators.
40+ -- There exists two type families that make it easier to write
41+ -- matrix dimensions: 'FromNat' and 'Count'. This approach
42+ -- leads to a very straightforward implementation
43+ -- of LAoP combinators.
3244
3345 -- * Type safe matrix representation
3446 Matrix (..),
@@ -95,9 +107,7 @@ module Matrix.Type
95107 -- | Note that given the restrictions imposed it is not possible to
96108 -- implement the standard type classes present in standard Haskell.
97109 -- *** Matrix pairing projections
98− KhatriP1,
99110 kp1,
100− KhatriP2,
101111 kp2,
102112
103113 -- *** Matrix pairing
@@ -127,8 +137,9 @@ import Data.List
127137import Data.Proxy
128138import Data.Void
129139import GHC.TypeLits
140+import GHC.Generics
130141
131−-- | Matrix data type
142+-- | LAoP (Linear Algebra of Programming) Inductive Matrix definition.
132143data Matrix e cols rows where
133144 Empty :: Matrix e Void Void
134145 One :: e -> Matrix e () ()
@@ -137,17 +148,24 @@ data Matrix e cols rows where
137148
138149deriving instance (Show e) => Show (Matrix e cols rows)
139150
140−-- | Type family that computes the cardinality of a given type dimension
151+-- | Type family that computes the cardinality of a given type dimension.
152+-- It can also count the cardinality of custom types that implement the
153+-- 'Generic' instance.
141154type family Count (d :: Type) where
142− Count Void = 0
143− Count () = 1
144− Count Bool = 2
145155 Count (Natural n) = n
146156 Count (Either a b) = (+) (Count a) (Count b)
147157 Count (a, b) = (*) (Count a) (Count b)
148158 Count (a -> b) = (^) (Count b) (Count a)
149−
150−-- | Type family that computes of a given type dimension from a fiven natural
159+ -- Generics
160+ Count (M1 _ _ f p) = Count (f p)
161+ Count (K1 _ _ _) = 1
162+ Count (V1 _) = 0
163+ Count (U1 _) = 1
164+ Count ((:*:) a b p) = Count (a p) * Count (b p)
165+ Count ((:+:) a b p) = Count (a p) + Count (b p)
166+ Count d = Count (Rep d R)
167+
168+-- | Type family that computes of a given type dimension from a given natural
151169type family FromNat (n :: Nat) where
152170 FromNat 0 = Void
153171 FromNat 1 = ()
@@ -199,47 +217,65 @@ instance Num e => Num (Matrix e cols rows) where
199217
200218-- Primitives
201219
220+-- | Empty matrix constructor
202221empty :: Matrix e Void Void
203222empty = Empty
204223
224+-- | Unit matrix constructor
205225one :: e -> Matrix e () ()
206226one = One
207227
228+-- | Matrix 'Junc' constructor
208229junc :: Matrix e a rows -> Matrix e b rows -> Matrix e (Either a b) rows
209230junc = Junc
210231
211232infixl 3 |||
233+
234+-- | Matrix 'Junc' constructor
212235(|||) :: Matrix e a rows -> Matrix e b rows -> Matrix e (Either a b) rows
213236(|||) = Junc
214237
238+-- | Matrix 'Split' constructor
215239split :: Matrix e cols a -> Matrix e cols b -> Matrix e cols (Either a b)
216240split = Split
217241
218242infixl 2 ===
243+
244+-- | Matrix 'Split' constructor
219245(===) :: Matrix e cols a -> Matrix e cols b -> Matrix e cols (Either a b)
220246(===) = Split
221247
222248-- Construction
223249
250+-- | Type class for defining the 'fromList' conversion function.
251+-- Given that it is not possible to branch on types at the term level type
252+-- classes are needed bery much like an inductive definition but on types.
224253class FromLists e cols rows where
254+ -- | Build a matrix out of a list of list of elements. Throws a runtime
255+ -- error if the dimensions do not match.
225256 fromLists :: [[e]] -> Matrix e cols rows
226257
258+-- | Void base case
227259instance FromLists e Void Void where
228260 fromLists [] = Empty
229261 fromLists _ = error "Wrong dimensions"
230262
263+-- | Unit base case
231264instance {-# OVERLAPPING #-} FromLists e () () where
232265 fromLists [[e]] = One e
233266 fromLists _ = error "Wrong dimensions"
234267
268+-- | Row vector matrix definition
235269instance (FromLists e cols ()) => FromLists e (Either () cols) () where
236270 fromLists [h : t] = Junc (One h) (fromLists [t])
237271 fromLists _ = error "Wrong dimensions"
238272
273+-- | Column vector matrix definition
239274instance (FromLists e () rows) => FromLists e () (Either () rows) where
240275 fromLists ([h] : t) = Split (One h) (fromLists t)
241276 fromLists _ = error "Wrong dimensions"
242277
278+-- | Arbitrary matrix definition
243279instance {-# OVERLAPPABLE #-} (FromLists e cols c, FromLists e cols d) => FromLists e cols (Either c d) where
244280 fromLists (h : t) =
245281 let lh = length h
@@ -248,6 +284,8 @@ instance {-# OVERLAPPABLE #-} (FromLists e cols c, FromLists e cols d) => FromLi
248284 then Split (fromLists [h]) (fromLists t)
249285 else error "Not all rows have the same length"
250286
287+-- | Matrix builder function. Constructs a matrix provided with
288+-- a construction function.
251289matrixBuilder ::
252290 forall e cols rows.
253291 ( FromLists e cols rows,
@@ -262,12 +300,17 @@ matrixBuilder f =
262300 positions = [(a, b) | a <- [0 .. (r - 1)], b <- [0 .. (c - 1)]]
263301 in fromLists . map (map f) . groupBy (\(x, _) (w, _) -> x == w) $ positions
264302
303+-- | Constructs a column vector matrix
265304col :: (FromLists e () rows) => [e] -> Matrix e () rows
266305col = fromLists . map (: [])
267306
307+-- | Constructs a row vector matrix
268308row :: (FromLists e cols ()) => [e] -> Matrix e cols ()
269309row = fromLists . (: [])
270310
311+-- | Lifts functions to matrices with arbitrary dimensions.
312+-- NOTE: Be careful to not ask for a matrix bigger than the cardinality of
313+-- types @a@ or @b@ allows.
271314fromF ::
272315 forall a b cols rows e.
273316 ( Bounded a,
@@ -302,6 +345,8 @@ fromF f =
302345 buildList [] _ = []
303346 buildList l r = take r l : buildList (drop r l) r
304347
348+-- | Lifts functions to matrices with dimensions matching @a@ and @b@
349+-- cardinality's.
305350fromF' ::
306351 forall a b e.
307352 ( Bounded a,
@@ -338,32 +383,40 @@ fromF' f =
338383
339384-- Conversion
340385
386+-- | Converts a matrix to a list of lists of elements.
341387toLists :: Matrix e cols rows -> [[e]]
342388toLists Empty = []
343389toLists (One e) = [[e]]
344390toLists (Split l r) = toLists l ++ toLists r
345391toLists (Junc l r) = zipWith (++) (toLists l) (toLists r)
346392
393+-- | Converts a matrix to a list of elements.
347394toList :: Matrix e cols rows -> [e]
348395toList = concat . toLists
349396
350397-- Zeros Matrix
351398
399+-- | The zero matrix. A matrix wholly filled with zeros.
352400zeros :: (Num e, FromLists e cols rows, KnownNat (Count cols), KnownNat (Count rows)) => Matrix e cols rows
353401zeros = matrixBuilder (const 0)
354402
355403-- Ones Matrix
356404
405+-- | The ones matrix. A matrix wholly filled with ones.
406+-- Also known as T (Top) matrix.
357407ones :: (Num e, FromLists e cols rows, KnownNat (Count cols), KnownNat (Count rows)) => Matrix e cols rows
358408ones = matrixBuilder (const 1)
359409
360410-- Const Matrix
361411
412+-- | The constant matrix constructor. A matrix wholly filled with a given
413+-- value.
362414constant :: (Num e, FromLists e cols rows, KnownNat (Count cols), KnownNat (Count rows)) => e -> Matrix e cols rows
363415constant e = matrixBuilder (const e)
364416
365417-- Bang Matrix
366418
419+-- | The T (Top) row vector matrix.
367420bang :: forall e cols. (Num e, Enum e, FromLists e cols (), KnownNat (Count cols)) => Matrix e cols ()
368421bang =
369422 let c = fromInteger $ natVal (Proxy :: Proxy (Count cols))
@@ -371,11 +424,15 @@ bang =
371424
372425-- Identity Matrix
373426
427+-- | Identity matrix.
374428identity :: (Num e, FromLists e cols cols, KnownNat (Count cols)) => Matrix e cols cols
375429identity = matrixBuilder (bool 0 1 . uncurry (==))
376430
377431-- Matrix composition (MMM)
378432
433+-- | Matrix composition. Equivalent to matrix-matrix multiplication.
434+-- This definition takes advantage of divide-and-conquer and fusion laws
435+-- from LAoP.
379436comp :: (Num e) => Matrix e cr rows -> Matrix e cols cr -> Matrix e cols rows
380437comp Empty Empty = Empty
381438comp (One a) (One b) = One (a * b)
@@ -385,12 +442,14 @@ comp c (Junc a b) = Junc (comp c a) (comp c b) -- Junc fusion law
385442
386443-- Projections
387444
445+-- | Biproduct first component projection
388446p1 :: forall e m n. (Num e, KnownNat (Count n), KnownNat (Count m), FromLists e n m, FromLists e m m) => Matrix e (Either m n) m
389447p1 =
390448 let iden = identity :: Matrix e m m
391449 zero = zeros :: Matrix e n m
392450 in junc iden zero
393451
452+-- | Biproduct second component projection
394453p2 :: forall e m n. (Num e, KnownNat (Count n), KnownNat (Count m), FromLists e m n, FromLists e n n) => Matrix e (Either m n) n
395454p2 =
396455 let iden = identity :: Matrix e n n
@@ -399,17 +458,29 @@ p2 =
399458
400459-- Injections
401460
461+-- | Biproduct first component injection
402462i1 :: (Num e, KnownNat (Count n), KnownNat (Count m), FromLists e n m, FromLists e m m) => Matrix e m (Either m n)
403463i1 = tr p1
404464
465+-- | Biproduct second component injection
405466i2 :: (Num e, KnownNat (Count n), KnownNat (Count m), FromLists e m n, FromLists e n n) => Matrix e n (Either m n)
406467i2 = tr p2
407468
408469-- Dimensions
409470
471+-- | Obtain the number of rows.
472+-- NOTE: The 'KnownNat' constaint is needed in order to obtain the
473+-- dimensions in constant time.
474+-- TODO: A 'rows' function that does not need the 'KnownNat' constraint in
475+-- exchange for performance.
410476rows :: forall e cols rows. (KnownNat (Count rows)) => Matrix e cols rows -> Int
411477rows _ = fromInteger $ natVal (Proxy :: Proxy (Count rows))
412478
479+-- | Obtain the number of columns.
480+-- NOTE: The 'KnownNat' constaint is needed in order to obtain the
481+-- dimensions in constant time.
482+-- TODO: A 'columns' function that does not need the 'KnownNat' constraint in
483+-- exchange for performance.
413484columns :: forall e cols rows. (KnownNat (Count cols)) => Matrix e cols rows -> Int
414485columns _ = fromInteger $ natVal (Proxy :: Proxy (Count cols))
415486
@@ -417,6 +488,7 @@ columns _ = fromInteger $ natVal (Proxy :: Proxy (Count cols))
417488
418489infixl 5 -|-
419490
491+-- | Matrix coproduct functor also known as matrix direct sum.
420492(-|-) ::
421493 forall e n k m j.
422494 ( Num e,
@@ -434,51 +506,57 @@ infixl 5 -|-
434506
435507-- Khatri Rao Product and projections
436508
437−class KhatriP1 e m k where
438− kp1 :: Matrix e (Normalize (m, k)) m
439−
440−instance KhatriP1 e Void k where
441− kp1 = Empty
442−
443−instance
509+-- | Khatri Rao product first component projection matrix.
510+kp1 ::
511+ forall e m k .
444512 ( Num e,
445513 KnownNat (Count k),
446514 FromLists e (FromNat (Count m * Count k)) m,
447515 KnownNat (Count m),
448516 KnownNat (Count (Normalize (m, k)))
449− ) =>
450− KhatriP1 e m k
517+ ) => Matrix e (Normalize (m, k)) m
518+kp1 = matrixBuilder f
451519 where
452− kp1 = matrixBuilder f
453− where
454− offset = fromInteger (natVal (Proxy :: Proxy (Count k)))
455− f (x, y)
456− | y >= (x * offset) && y <= (x * offset + offset - 1) = 1
457− | otherwise = 0
458−
459−class KhatriP2 e m k where
460− kp2 :: Matrix e (Normalize (m, k)) k
461−
462−instance KhatriP2 e m Void where
463− kp2 = Empty
464−
465−instance
466− ( Num e,
467− KnownNat (Count k),
468− FromLists e (FromNat (Count m * Count k)) k,
469− KnownNat (Count m),
470− KnownNat (Count (Normalize (m, k)))
471− ) =>
472− KhatriP2 e m k
520+ offset = fromInteger (natVal (Proxy :: Proxy (Count k)))
521+ f (x, y)
522+ | y >= (x * offset) && y <= (x * offset + offset - 1) = 1
523+ | otherwise = 0
524+
525+-- | Khatri Rao product second component projection matrix.
526+kp2 ::
527+ forall e m k .
528+ ( Num e,
529+ KnownNat (Count k),
530+ FromLists e (FromNat (Count m * Count k)) k,
531+ KnownNat (Count m),
532+ KnownNat (Count (Normalize (m, k)))
533+ ) => Matrix e (Normalize (m, k)) k
534+kp2 = matrixBuilder f
473535 where
474− kp2 = matrixBuilder f
475− where
476− offset = fromInteger (natVal (Proxy :: Proxy (Count k)))
477− f (x, y)
478− | x == y || mod (y - x) offset == 0 = 1
479− | otherwise = 0
480−
481−khatri :: forall e cols a b. (Num e, KhatriP1 e a b, KhatriP2 e a b) => Matrix e cols a -> Matrix e cols b -> Matrix e cols (Normalize (a, b))
536+ offset = fromInteger (natVal (Proxy :: Proxy (Count k)))
537+ f (x, y)
538+ | x == y || mod (y - x) offset == 0 = 1
539+ | otherwise = 0
540+
541+-- | Khatri Rao Matrix product also known as matrix pairing.
542+-- NOTE: That this is not a true categorical product, see for instance:
543+--
544+-- @
545+-- | kp1 `comp` khatri a b == a
546+-- khatri a b ==> |
547+-- | kp2 `comp` khatri a b == b
548+-- @
549+--
550+-- __Emphasis__ on the implication symbol.
551+khatri ::
552+ forall e cols a b.
553+ ( Num e,
554+ KnownNat (Count a),
555+ KnownNat (Count b),
556+ KnownNat (Count (Normalize (a, b))),
557+ FromLists e (Normalize (a, b)) a,
558+ FromLists e (Normalize (a, b)) b
559+ ) => Matrix e cols a -> Matrix e cols b -> Matrix e cols (Normalize (a, b))
482560khatri a b =
483561 let kp1' = kp1 @e @a @b
484562 kp2' = kp2 @e @a @b
@@ -488,7 +566,22 @@ khatri a b =
488566
489567infixl 4 ><
490568
491−(><) :: forall e m p n q. (Num e, KhatriP1 e m n, KhatriP2 e m n, KhatriP1 e p q, KhatriP2 e p q) => Matrix e m p -> Matrix e n q -> Matrix e (Normalize (m, n)) (Normalize (p, q))
569+-- | Matrix product functor also known as kronecker product
570+(><) ::
571+ forall e m p n q.
572+ ( Num e,
573+ KnownNat (Count m),
574+ KnownNat (Count n),
575+ KnownNat (Count p),
576+ KnownNat (Count q),
577+ KnownNat (Count (Normalize (m, n))),
578+ FromLists e (Normalize (m, n)) m,
579+ FromLists e (Normalize (m, n)) n,
580+ KnownNat (Count (Normalize (p, q))),
581+ FromLists e (Normalize (p, q)) p,
582+ FromLists e (Normalize (p, q)) q
583+ )
584+ => Matrix e m p -> Matrix e n q -> Matrix e (Normalize (m, n)) (Normalize (p, q))
492585(><) a b =
493586 let kp1' = kp1 @e @m @n
494587 kp2' = kp2 @e @m @n
@@ -496,6 +589,11 @@ infixl 4 ><
496589
497590-- Matrix abide Junc Split
498591
592+-- | Matrix "abiding" followin the 'Junc'-'Split' abide law.
593+--
594+-- @
595+-- 'Junc' ('Split' a c) ('Split' b d) == 'Split' ('Junc' a b) ('Junc' c d)
596+-- @
499597abideJS :: Matrix e cols rows -> Matrix e cols rows
500598abideJS (Junc (Split a c) (Split b d)) = Split (Junc (abideJS a) (abideJS b)) (Junc (abideJS c) (abideJS d)) -- Junc-Split abide law
501599abideJS Empty = Empty
@@ -505,6 +603,11 @@ abideJS (Split a b) = Split (abideJS a) (abideJS b)
505603
506604-- Matrix abide Split Junc
507605
606+-- | Matrix "abiding" followin the 'Split'-'Junc' abide law.
607+--
608+-- @
609+-- 'Split' ('Junc' a b) ('Junc' c d) == 'Junc' ('Split' a c) ('Split' b d)
610+-- @
508611abideSJ :: Matrix e cols rows -> Matrix e cols rows
509612abideSJ (Split (Junc a b) (Junc c d)) = Junc (Split (abideSJ a) (abideSJ c)) (Split (abideSJ b) (abideSJ d)) -- Split-Junc abide law
510613abideSJ Empty = Empty
@@ -514,6 +617,7 @@ abideSJ (Split a b) = Split (abideSJ a) (abideSJ b)
514617
515618-- Matrix transposition
516619
620+-- | Matrix transposition.
517621tr :: Matrix e cols rows -> Matrix e rows cols
518622tr Empty = Empty
519623tr (One e) = One e
@@ -522,6 +626,8 @@ tr (Split a b) = Junc (tr a) (tr b)
522626
523627-- Selective 'select' operator
524628
629+-- | Selective functors 'select' operator equivalent inspired by the
630+-- ArrowMonad solution presented in the paper.
525631select :: (Bounded a1, Bounded b, Enum a1, Enum b, Num e, Ord e,
526632 KnownNat (Count a2), KnownNat (Count rows), FromLists e rows a2,
527633 FromLists e rows rows, Eq b) =>
@@ -532,13 +638,13 @@ select m y =
532638
533639-- McCarthy's Conditional
534640
641+-- | McCarthy's Conditional expresses probabilistic choice.
535642cond ::
536643 ( cols ~ FromNat (Count cols),
537644 KnownNat (Count cols),
538645 FromLists e () cols,
539646 FromLists e cols (),
540647 FromLists e cols cols,
541− KhatriP2 e () cols,
542648 Bounded a,
543649 Enum a,
544650 Num e,
@@ -554,7 +660,6 @@ grd ::
554660 FromLists e () q,
555661 FromLists e q (),
556662 FromLists e q q,
557− KhatriP2 e () q,
558663 Bounded a,
559664 Enum a,
560665 Num e,
@@ -571,7 +676,6 @@ corr ::
571676 FromLists e () q,
572677 FromLists e q (),
573678 FromLists e q q,
574− KhatriP2 e () q,
575679 Bounded a,
576680 Enum a,
577681 Num e,
@@ -602,6 +706,7 @@ prettyAux (h : t) l = "│ " ++ fill (unwords $ map show h) ++ " │\n" ++
602706 widest = maximum $ fmap length v
603707 fill str = replicate (widest - length str - 2) ' ' ++ str
604708
709+-- | Matrix pretty printer
605710pretty :: (KnownNat (Count cols), Show e) => Matrix e cols rows -> String
606711pretty m = "┌ " ++ unwords (replicate (columns m) blank) ++ " ┐\n" ++
607712 prettyAux (toLists m) (toLists m) ++
@@ -612,5 +717,6 @@ pretty m = "┌ " ++ unwords (replicate (columns m) blank) ++ " ┐\n" ++
612717 fill str = replicate (widest - length str) ' ' ++ str
613718 blank = fill ""
614719
720+-- | Matrix pretty printer
615721prettyPrint :: (KnownNat (Count cols), Show e) => Matrix e cols rows -> IO ()
616722prettyPrint = putStrLn . pretty
src/Utils.hs+97−7
@@ -1,9 +1,99 @@
1−module Utils
2− ( Natural(..),
3− nat,
4− coerceNat,
5− coerceNat2
6− )
7− where
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Utils
4+-- Copyright : (c) Armando Santos 2019-2020
5+-- Maintainer : armandoifsantos@gmail.com
6+-- Stability : experimental
7+--
8+-- __LAoP__ is a library for algebraic (inductive) construction and manipulation of matrices
9+-- in Haskell. See <https://github.com/bolt12/master-thesis my Msc Thesis> for the
10+-- motivation behind the library, the underlying theory, and implementation details.
11+--
12+-- This module provides the 'Natural' data type.
13+-- The semantic associated with this data type is that
14+-- it's meant to be a restricted 'Int' value.
15+--
16+-----------------------------------------------------------------------------
17+
18+module Utils
19+ ( -- | Utility module that provides the 'Natural' data type.
20+ -- The semantic associated with this data type is that
21+ -- it's meant to be a restricted 'Int' value. For example
22+ -- the type @Natural 6@ can only be instanciated with @Nat n@
23+ -- where @0 <= n <= 6@. Why, You might ask, because with normal
24+ -- 'Int's it is not possible to have a decent @Enum (Int, Int)@
25+ -- instance. See the following probabilistic programming model as and
26+ -- example:
27+ --
28+ -- We want to calculate the probability of the sum of two dice throws.
29+ -- To do this we start by defining the sample space:
30+ --
31+ -- @
32+ -- type SampleSpace = Int -- We think 'Int' are enough
33+ --
34+ -- die :: Dist Int 6
35+ -- die = unifrom [1..6]
36+ --
37+ -- -- Promote 'Int' addition to a matrix
38+ -- addM = fromF (uncurry (+)) -- Impossible
39+ -- @
40+ --
41+ -- The last line is impossible because @(Int, Int)@ does not have
42+ -- a good 'Enum' instance: @[(0, 1), (0, 2), .. (0, maxBound), (1, 0),
43+ -- ..]@. And we'd like the addition matrix to be of 36 columns by 12
44+ -- rows but limited to integers up to @6@!
45+ --
46+ -- One way to solve this issue is by defining and auxilary data type to
47+ -- represent the sample space:
48+ --
49+ -- @
50+ -- data SampleSpace = S1 | S2 | S3 | S4 | S5 | S6
51+ -- deriving (Show, Eq, Enum, Bounded) -- Enum and Bounded are
52+ -- important
53+ -- @
54+ --
55+ -- And write the sample space addition function:
56+ --
57+ -- @
58+ -- ssAdd :: SampleSpace -> SampleSpace -> Int
59+ -- ssAdd a b = (fromEnum a + 1) + (fromEnum b + 1)
60+ -- @
61+ --
62+ -- And then promote that function to matrix and everything is alright:
63+ --
64+ -- @
65+ -- ssAddM = fromF' (uncurry ssAdd)
66+ --
67+ -- dieSumProb = ssAddM `comp` (khatri die die)
68+ -- @
69+ --
70+ -- This is a nice solution for small sample spaces. But for larger ones
71+ -- it is not feasible to write a data type with hundreds of constructors
72+ -- and then write manipulation functions that need to deal with them.
73+ -- To mitigate this limitation the 'Natural' type comes a long way and
74+ -- allows one to model the sample in an easier way. See for instance:
75+ --
76+ -- @
77+ -- ssAdd :: Natural 6 -> Natural 6 -> Natural 12
78+ -- ssAdd = coerceNat (+)
79+ --
80+ -- ssAddM = fromF' (uncurry sumSS)
81+ --
82+ -- die :: Dist (Natural 6) 6
83+ -- die = uniform [nat @6 1 .. nat 6]
84+ --
85+ -- dieSumProb = ssAddM `comp` (khatri die die)
86+ -- @
87+ --
88+ -- * 'Natural' data type
89+ Natural,
90+ nat,
91+
92+ -- * Coerce auxiliar functions to help promote 'Int' typed functions to
93+ -- 'Natural' typed functions.
94+ coerceNat,
95+ coerceNat2,
96+ )
97+where
898
999import Utils.Internal
src/Utils/Internal.hs+113−22
@@ -1,47 +1,136 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
21{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE KindSignatures #-}
42{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE KindSignatures #-}
55{-# LANGUAGE ScopedTypeVariables #-}
66
77module Utils.Internal
8− ( Natural(..),
9− nat,
10− coerceNat,
11− coerceNat2
12− )
13− where
8+ ( -- | This is an internal module and it is not meant to be imported.
9+ --
10+ -- Utility module that provides the 'Natural' data type.
11+ -- The semantic associated with this data type is that
12+ -- it's meant to be a restricted 'Int' value. For example
13+ -- the type @Natural 6@ can only be instanciated with @Nat n@
14+ -- where @0 <= n <= 6@. Why, You might ask, because with normal
15+ -- 'Int's it is not possible to have a decent @Enum (Int, Int)@
16+ -- instance. See the following probabilistic programming model as and
17+ -- example:
18+ --
19+ -- We want to calculate the probability of the sum of two dice throws.
20+ -- To do this we start by defining the sample space:
21+ --
22+ -- @
23+ -- type SampleSpace = Int -- We think 'Int' are enough
24+ --
25+ -- die :: Dist Int 6
26+ -- die = unifrom [1..6]
27+ --
28+ -- -- Promote 'Int' addition to a matrix
29+ -- addM = fromF (uncurry (+)) -- Impossible
30+ -- @
31+ --
32+ -- The last line is impossible because @(Int, Int)@ does not have
33+ -- a good 'Enum' instance: @[(0, 1), (0, 2), .. (0, maxBound), (1, 0),
34+ -- ..]@. And we'd like the addition matrix to be of 36 columns by 12
35+ -- rows but limited to integers up to @6@!
36+ --
37+ -- One way to solve this issue is by defining and auxilary data type to
38+ -- represent the sample space:
39+ --
40+ -- @
41+ -- data SampleSpace = S1 | S2 | S3 | S4 | S5 | S6
42+ -- deriving (Show, Eq, Enum, Bounded) -- Enum and Bounded are
43+ -- important
44+ -- @
45+ --
46+ -- And write the sample space addition function:
47+ --
48+ -- @
49+ -- ssAdd :: SampleSpace -> SampleSpace -> Int
50+ -- ssAdd a b = (fromEnum a + 1) + (fromEnum b + 1)
51+ -- @
52+ --
53+ -- And then promote that function to matrix and everything is alright:
54+ --
55+ -- @
56+ -- ssAddM = fromF' (uncurry ssAdd)
57+ --
58+ -- dieSumProb = ssAddM `comp` (khatri die die)
59+ -- @
60+ --
61+ -- This is a nice solution for small sample spaces. But for larger ones
62+ -- it is not feasible to write a data type with hundreds of constructors
63+ -- and then write manipulation functions that need to deal with them.
64+ -- To mitigate this limitation the 'Natural' type comes a long way and
65+ -- allows one to model the sample in an easier way. See for instance:
66+ --
67+ -- @
68+ -- ssAdd :: Natural 6 -> Natural 6 -> Natural 12
69+ -- ssAdd = coerceNat (+)
70+ --
71+ -- ssAddM = fromF' (uncurry sumSS)
72+ --
73+ -- die :: Dist (Natural 6) 6
74+ -- die = uniform [nat @6 1 .. nat 6]
75+ --
76+ -- dieSumProb = ssAddM `comp` (khatri die die)
77+ -- @
78+ --
79+ -- * 'Natural' data type
80+ Natural,
81+ nat,
82+
83+ -- * Coerce auxiliar functions to help promote 'Int' typed functions to
84+ -- 'Natural' typed functions.
85+ coerceNat,
86+ coerceNat2,
87+ )
88+where
1489
15−import GHC.TypeLits
16−import Data.Proxy
1790import Data.Coerce
91+import Data.Proxy
92+import GHC.TypeLits
1893
94+-- | Wrapper around 'Int's that have a restrictive semantic associated.
95+-- A value of type @'Natural' n@ can only be instanciated with some 'Int'
96+-- @i@ that's @0 <= i <= n@.
1997newtype Natural (nat :: Nat) = Nat Int
2098 deriving (Show, Read, Eq, Num)
2199
22−nat :: forall n . (KnownNat n) => Int -> Natural n
23−nat i = let nat = fromInteger (natVal (Proxy :: Proxy n))
24− in if i <= nat
25− then Nat i
26− else error "Off limits"
100+-- | Natural constructor function. Throws a runtime error if the 'Int'
101+-- value is greater than the corresponding @n@ in the @'Natural' n@ type.
102+nat :: forall n. (KnownNat n) => Int -> Natural n
103+nat i =
104+ let nat = fromInteger (natVal (Proxy :: Proxy n))
105+ in if i <= nat
106+ then Nat i
107+ else error "Off limits"
27108
109+-- | Auxiliary function that promotes binary 'Int' functions to 'Natural'
110+-- binary functions.
28111coerceNat :: (Int -> Int -> Int) -> (Natural a -> Natural b -> Natural c)
29112coerceNat = coerce
30113
114+-- | Auxiliary function that promotes ternary (binary) 'Int' functions to 'Natural'
115+-- functions.
31116coerceNat2 :: ((Int, Int) -> Int -> Int) -> ((Natural a, Natural b) -> Natural c -> Natural d)
32117coerceNat2 = coerce
33118
34119instance KnownNat n => Bounded (Natural n) where
35− minBound = Nat 0
36− maxBound = Nat $ fromInteger (natVal (Proxy :: Proxy n))
120+ minBound = Nat 0
121+ maxBound = Nat $ fromInteger (natVal (Proxy :: Proxy n))
37122
38123instance KnownNat n => Enum (Natural n) where
39− toEnum = nat
40− fromEnum (Nat nat) = let val = fromInteger (natVal (Proxy :: Proxy n))
41− in if nat > val
42− then error "Off limits"
43− else nat
124+ toEnum = nat
125+ -- | Throws a runtime error if the value is off limits
126+ fromEnum (Nat nat) =
127+ let val = fromInteger (natVal (Proxy :: Proxy n))
128+ in if nat > val
129+ then error "Off limits"
130+ else nat
44131
132+-- | Optimized 'Enum' instance for tuples that comply with the given
133+-- constraints.
45134instance
46135 ( Enum a,
47136 Enum b,
@@ -49,12 +138,14 @@ instance
49138 ) =>
50139 Enum (a, b)
51140 where
141+
52142 toEnum i =
53143 let (listB :: [b]) = [minBound .. maxBound]
54144 lengthB = length listB
55145 fstI = div i lengthB
56146 sndI = mod i lengthB
57147 in (toEnum fstI, toEnum sndI)
148+
58149 fromEnum (a, b) =
59150 let (listB :: [b]) = [minBound .. maxBound]
60151 lengthB = length listB