FiniteCategories-0.1.0.0: src/RandomCompositionGraph/RandomCompositionGraph.hs
{-| Module : FiniteCategories
Description : Randomly generated composition graphs.
Copyright : Guillaume Sabbagh 2021
License : GPL-3
Maintainer : guillaumesabbagh@protonmail.com
Stability : experimental
Portability : portable
This module provide functions to generate randomly composition graphs.
It is an easy and fast way to generate a lot of finite categories.
It can be used to test functions, to generate examples or to test hypothesis.
-}
module RandomCompositionGraph.RandomCompositionGraph
(
mkRandomCompositionGraph,
defaultMkRandomCompositionGraph
)
where
import FiniteCategory.FiniteCategory
import CompositionGraph.CompositionGraph (Graph(..), CGMorphism(..), CompositionLaw(..), CompositionGraph(..), Arrow(..), mkCompositionGraph, isGen, isComp)
import System.Random (RandomGen, uniformR)
import Data.Maybe (isNothing, fromJust)
import Utils.AssociationList
import Utils.Sample
import Utils.Tuple
-- | Find first order composites arrows in a composition graph.
compositeMorphisms :: (Eq a, Eq b, Show a) => CompositionGraph a b -> [CGMorphism a b]
compositeMorphisms c = [g @ f | f <- genArrows c, g <- genArFrom c (target f), not (elem (g @ f) (genAr c (source f) (target g)))]
-- | Merge two nodes.
mergeNodes :: (Eq a) => CompositionGraph a b -> a -> a -> CompositionGraph a b
mergeNodes cg@CompositionGraph{graph=g@(objs,ars),law=l} s t
| not (elem s objs) = error "mapped but not in rcg."
| not (elem t objs) = error "mapped to but not in rcg."
| s == t = cg
| otherwise = CompositionGraph {graph=(filter (/=s) objs,replaceArrow <$> ars), law=newLaw}
where
replace x = if x == s then t else x
replaceArrow (s1,t1,l1) = (replace s1, replace t1, l1)
newLaw = (\(k,v) -> (replaceArrow <$> k, replaceArrow <$> v)) <$> l
-- | Merge two morphisms of a composition graph, the morphism mapped should be composite, the morphism mapped to should be a generator.
mergeMorphisms :: (Eq a, Eq b) => CompositionGraph a b -> CGMorphism a b -> CGMorphism a b -> CompositionGraph a b
mergeMorphisms cg@CompositionGraph{graph=g,law=l} s@CGMorphism{path=p1@(s1,rp1,t1),compositionLaw=l1} t@CGMorphism{path=p2@(s2,rp2,t2),compositionLaw=l2}
| (isGen s) = error "Generator at the start of a merge"
| (isComp t) = error "Composite at the end of a merge"
| s1 == t1 = mergeNodes CompositionGraph{graph=g, law=newLaw} (source s) (source t)
| s1 == t2 = mergeNodes (mergeNodes CompositionGraph{graph=g, law=newLaw} (source s) (source t)) (target s) (source t)
| otherwise = mergeNodes (mergeNodes CompositionGraph{graph=g, law=newLaw} (source s) (source t)) (target s) (target t) where
newLaw = ((replaceArrow <$> rp1,replaceArrow <$> rp2):((\(k,v) -> (replaceArrow <$> k, replaceArrow <$> v)) <$> l))
where
replace x = if x == s1 then s2 else (if x == t1 then t2 else x)
replaceArrow (s3,t3,l3) = (replace s3, replace t3, l3)
-- | Checks associativity of a composition graph.
checkAssociativity :: (Eq a, Eq b, Show a) => CompositionGraph a b -> Bool
checkAssociativity cg = foldr (&&) True [checkTriplet (f,g,h) | f <- genArrows cg, g <- genArFrom cg (target f), h <- genArFrom cg (target g)]
where
checkTriplet (f,g,h) = (h @ g) @ f == h @ (g @ f)
-- | Find all composite arrows and try to map them to generating arrows.
identifyCompositeToGen :: (RandomGen g, Eq a, Eq b, Show a) => CompositionGraph a b -> Int -> g -> (Maybe (CompositionGraph a b), g)
identifyCompositeToGen _ 0 rGen = (Nothing, rGen)
identifyCompositeToGen cg n rGen
| not (checkAssociativity cg) = (Nothing, rGen)
| null compositeMorphs = (Just cg, rGen)
| otherwise = if isNothing newCG then identifyCompositeToGen cg (n `div` 2) newGen2 else (newCG, newGen2)
where
compositeMorphs = compositeMorphisms cg
morphToMap = (head compositeMorphs)
(selectedGen,newGen1) = if (source morphToMap == target morphToMap) then pickOne [fs | obj <- ob cg, fs <- (genAr cg obj obj)] rGen else pickOne (genArrows cg) rGen
(newCG,newGen2) = identifyCompositeToGen (mergeMorphisms cg morphToMap selectedGen) n newGen1
-- | Algorithm described in `mkRandomCompositionGraph`.
monoidificationAttempt :: (RandomGen g, Eq a, Eq b, Show a) => CompositionGraph a b -> Int -> g -> (CompositionGraph a b, g, [a])
monoidificationAttempt cg p g = if isNothing result then (cg,finalGen,[]) else (fromJust result, finalGen, [s,t])
where
([s,t],newGen) = if ((length (ob cg)) > 1) then sample (ob cg) 2 g else (ob cg ++ ob cg,g)
newCG = mergeNodes cg s t
(result,finalGen) = identifyCompositeToGen newCG p newGen
-- | Initialize a composition graph with all arrows seperated.
initRandomCG :: Int -> CompositionGraph Int Int
initRandomCG n = CompositionGraph{graph=([0..n+n-1],[((i+i),(i+i+1), i) | i <- [0..n]]),law=[]}
-- | Generates a random composition graph.
--
-- We use the fact that a category is a generalized monoid.
--
-- We try to create a composition law of a monoid greedily.
--
-- To get a category, we begin with a category with all arrows seperated and not composing with each other.
-- It is equivalent to the monoid with an empty composition law.
--
-- Then, a monoidification attempt is the following algorihm :
--
-- 1. Pick two objects, merge them.
-- 2. While there are composite morphisms, try to merge them with generating arrows.
-- 3. If it fails, don't change the composition graph.
-- 4. Else return the new composition graph
--
-- A monoidification attempt takes a valid category and outputs a valid category, furthermore, the number of arrows is constant
-- and the number of objects is decreasing (not strictly).
mkRandomCompositionGraph :: (RandomGen g) => Int -- ^ Number of arrows of the random composition graph.
-> Int -- ^ Number of monoidification attempts, a bigger number will produce more morphisms that will compose but the function will be slower.
-> Int -- ^ Perseverance : how much we pursure an attempt far away to find a law that works, a bigger number will make the attemps more successful, but slower. (When in doubt put 4.)
-> g -- ^ Random generator.
-> (CompositionGraph Int Int, g)
mkRandomCompositionGraph nbAr nbAttempts perseverance gen = attempt (initRandomCG nbAr) nbAttempts perseverance gen
where
attempt cg 0 _ gen = (cg, gen)
attempt cg n p gen = attempt newCG (n-1) p newGen
where
(newCG, newGen,_) = (monoidificationAttempt cg p gen)
-- | Creates a random composition graph with default random values.
--
-- The number of arrows will be in the interval [1, 20].
defaultMkRandomCompositionGraph :: (RandomGen g) => g -> (CompositionGraph Int Int, g)
defaultMkRandomCompositionGraph g1 = mkRandomCompositionGraph nbArrows (min nbAttempts 20) 4 g3
where
(nbArrows, g2) = uniformR (1,20) g1
(nbAttempts, g3) = uniformR (0,nbArrows+nbArrows) g2