elynx-tree-0.2.1: src/ELynx/Data/Tree/Multipartition.hs
{- |
Module : ELynx.Data.Tree.Multipartition
Description : Multipartitions on rose trees
Copyright : (c) Dominik Schrempf 2020
License : GPL-3.0-or-later
Maintainer : dominik.schrempf@gmail.com
Stability : unstable
Portability : portable
Creation date: Thu Dec 12 12:58:49 2019.
A multifurcation induces a 'Multipartition', similar to branches inducing
'ELynx.Data.Tree.Bipartition's.
-}
module ELynx.Data.Tree.Multipartition
(
-- * The 'Multipartition' data type.
Multipartition()
, mps
, mp
, mpmap
, mphuman
, fromBipartition
-- * Working with 'Multipartition's.
, multipartitions
, findSubset
, mpcompatible
)
where
import Data.List ( find
, foldl'
, intercalate
)
import Data.Maybe ( fromMaybe )
import qualified Data.Set as S
import Data.Tree
import ELynx.Data.Tree.Bipartition ( Bipartition
, bps
, sshow
)
import ELynx.Data.Tree.Tree
-- | Each branch of a bifurcating tree partitions the leaves of the tree into
-- three subsets, see 'ELynx.Data.Tree.Bipartition'. In a similar way, each
-- internal node induces a tripartition. Tripartitions are not yet implemented
-- (December 2019) because it is usually sufficient to work with bipartitions.
-- If, however, the tree is multifurcating and a specific node has more than two
-- children, the number of subsets induced by this node is larger than three,
-- and a 'Multipartition'. Multipartitions are interesting in that we can use
-- them for calculating incompatible splits, see 'ELynx.Data.Tree.Distance'. The
-- order of the partitions within a multipartition is unimportant, .
newtype Multipartition a = Multipartition { mps :: S.Set (S.Set a) -- ^ Set of partitions
}
deriving (Show, Read)
-- | Show a multipartition in a human readable form. Use a provided function to
-- extract the valuable information.
mphuman :: (a -> String) -> Multipartition a -> String
mphuman f (Multipartition xs) =
"(" ++ intercalate "|" (map (sshow f) (S.toList xs)) ++ ")"
-- | Create a multipartition.
mp :: Ord a => [S.Set a] -> Multipartition a
mp = mp' . filter (not . S.null)
mp' :: Ord a => [S.Set a] -> Multipartition a
mp' xs = Multipartition (S.fromList xs)
-- | Map a function over all elements in the multipartitions.
mpmap :: (Ord a, Ord b) => (a -> b) -> Multipartition a -> Multipartition b
mpmap f (Multipartition xs) = Multipartition $ S.map (S.map f) xs
instance (Eq a) => Eq (Multipartition a) where
Multipartition xs == Multipartition ys = xs == ys
instance (Ord a) => Ord (Multipartition a) where
Multipartition xs `compare` Multipartition ys = xs `compare` ys
-- | Convert bipartition to multipartition.
fromBipartition :: Ord a => Bipartition a -> Multipartition a
fromBipartition bp = mp [l, r] where (l, r) = bps bp
-- | Get all multipartitions of a tree.
multipartitions :: Ord a => Tree a -> S.Set (Multipartition a)
multipartitions t = if S.size (S.fromList lvs) == length lvs
then multipartitionsUnsafe S.empty (partitionTree t)
else error "multipartitions: The tree contains duplicate leaves."
where lvs = leaves t
-- | See 'multipartitions', but do not check if leaves are unique.
multipartitionsUnsafe
:: Ord a => S.Set a -> Tree (S.Set a) -> S.Set (Multipartition a)
multipartitionsUnsafe _ (Node _ [] ) = S.empty
multipartitionsUnsafe xs (Node _ [x] ) = multipartitionsUnsafe xs x
multipartitionsUnsafe xs (Node _ [x, y]) = S.union l r
where
l = multipartitionsUnsafe (S.union xs (rootLabel x)) y
r = multipartitionsUnsafe (S.union xs (rootLabel y)) x
multipartitionsUnsafe xs t@(Node _ ys) =
S.unions
$ S.singleton (mp (xs : map rootLabel ys))
: zipWith multipartitionsUnsafe lvsOthers ys
where lvsOthers = subForestGetSubsets xs t
-- | Find the subset of a multipartition containing a given element.
findSubset :: Ord a => a -> Multipartition a -> S.Set a
findSubset l m = -- Return the empty subset if nothing is found. This corresponds
-- to having no information about the leaf in question.
fromMaybe S.empty (find (S.member l) ss)
where ss = mps m
-- Add the subset of a bipartition which contains a given element.
addSubset
:: Ord a => Multipartition a -> S.Set (S.Set a) -> a -> S.Set (S.Set a)
addSubset m ss l = if not $ S.null s then s `S.insert` ss else ss
where s = findSubset l m
-- Each subset overlaps with a number of subsets of a bipartition which are
-- returned by this function.
overlap :: Ord a => Multipartition a -> S.Set a -> S.Set (S.Set a)
overlap m = foldl' (addSubset m) S.empty
-- | Multipartitions are compatible if they do not contain conflicting
-- information. This function checks if two multipartitions are compatible with
-- each other. Thereby, following algorithm is used:
--
-- 1. Take each subset of the first multipartition.
--
-- 2a. Determine the overlap: For each leaf of the chosen subset, add the subset
-- of the second multipartition containing the leaf. The result is a set of
-- subsets, which is the union of the added subsets.
--
-- The data type "set of subsets" is actually the same data type as a
-- multipartition. However, it is not a partition, because it may and will not
-- span the whole set of leaves, and so, I use @S.Set (S.Set a)@. One could
-- define a multiset data type to improve comprehensibility.
--
-- 2b. Collect the set of subsets from point 1.
--
-- 3. Each set of subsets needs to be either equal or disjoint with any other
-- set of subsets in the collection. If so, the first multipartition is
-- compatible with the second.
--
-- 4. Exchange the first with the second multipartition and go through steps 1
-- to 3.
--
-- See also 'ELynx.Data.Tree.Bipartition.compatible'.
mpcompatible :: (Ord a, Show a) => Multipartition a -> Multipartition a -> Bool
mpcompatible l r =
and
$ [ x `S.disjoint` y | x <- lOverlaps, y <- lOverlaps, x /= y ]
++ [ x `S.disjoint` y | x <- rOverlaps, y <- rOverlaps, x /= y ]
where
ls = S.toList $ mps l
rs = S.toList $ mps r
-- The subsets on the left multipartition overlap the subsets of the
-- right multipartition.
lOverlaps = map (overlap r) ls
-- The subsets on the left multipartition overlap the subsets of the
-- right multipartition.
rOverlaps = map (overlap l) rs