packages feed

elynx-tree-0.5.0: test/ELynx/Tree/PhylogenySpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

-- |
-- Module      :  ELynx.Tree.PhylogenySpec
-- Description :  Unit tests for ELynx.Tree.Phylogeny
-- Copyright   :  (c) Dominik Schrempf, 2020
-- License     :  GPL-3.0-or-later
--
-- Maintainer  :  dominik.schrempf@gmail.com
-- Stability   :  unstable
-- Portability :  portable
--
-- Creation date: Wed Jul 15 11:05:32 2020.
module ELynx.Tree.PhylogenySpec
  ( spec,
  )
where

import Data.Either
import qualified Data.Set as S
import ELynx.Tree
import ELynx.Tree.Arbitrary ()
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck hiding (labels)

simpleTree :: Tree () String
simpleTree = Node () "i" [Node () "j" [Node () "x" [], Node () "y" []], Node () "z" []]

simpleSol :: Forest () String
simpleSol =
  [ Node () "i" [Node () "j" [Node () "x" [], Node () "y" []], Node () "z" []],
    Node () "i" [Node () "j" [Node () "z" [], Node () "y" []], Node () "x" []],
    Node () "i" [Node () "j" [Node () "z" [], Node () "x" []], Node () "y" []]
  ]

instance Splittable () where
  split = id

-- Skip leaves and trees with multifurcating root nodes.
prop_roots :: Tree () a -> Bool
prop_roots t@(Node _ _ [_, _])
  | length (leaves t) == 2 = (length <$> roots t) == Right 1
  | otherwise = (length <$> roots t) == Right (length (labels t) - 2)
prop_roots _ = True

-- -- Skip leaves and trees with multifurcating root nodes.
-- prop_connect :: a -> Tree () a -> Tree () a -> Bool
-- prop_connect n l@(Node _ _ [_, _]) r@(Node _ _ [_, _])
--   | length (leaves l) < 3 = (length <$> connect n l r) == Right (length (flatten r) - 2)
--   | length (leaves r) < 3 = (length <$> connect n l r) == Right (length (flatten l) - 2)
--   | otherwise =
--     (length <$> connect n l r)
--       == (Right $ (length (flatten l) - 2) * (length (flatten r) - 2))
-- prop_connect _ _ _ = True

-- -- | Determine compatibility between a bipartition and a set.
-- --
-- -- If both subsets of the bipartition share elements with the given set, the
-- -- bipartition is incompatible with this subset. If all elements of the subset
-- -- are either not in the bipartition or mapping to one of the two subsets of the
-- -- bipartition, the bipartition and the subset are compatible.
-- --
-- -- See also 'ELynx.Tree.Partition.compatible'.
-- bipartitionCompatible :: (Show a, Ord a) => Either String (Bipartition a) -> Set a -> Bool
-- -- compatible (Bipartition (l, r)) ss = sintersection l ss `sdisjoint` sintersection r ss
-- bipartitionCompatible (Left _) _ = False
-- bipartitionCompatible (Right p) s = S.null lOverlap || S.null rOverlap
--   where
--     (l, r) = fromBipartition p
--     lOverlap = S.intersection l s
--     rOverlap = S.intersection r s

-- compatibleAll :: (Show a, Ord a) => Tree e a -> [Set a] -> Bool
-- compatibleAll (Node _ _ [l, r]) cs =
--   all (bipartitionCompatible (bipartition l)) cs && all (bipartitionCompatible (bipartition r)) cs
-- compatibleAll _ _ = error "Tree is not bifurcating."

-- compatibleWith ::
--   (Show b, Ord b) => (a -> b) -> [Set a] -> Tree e a -> Bool
-- compatibleWith f cs t = compatibleAll (fmap f t) (map (S.map f) cs)

-- -- Get groups induced by multifurcations. Collect the leaves of all trees
-- -- induced by multifurcations.
-- multifurcatingGroups :: Tree e a -> [[a]]
-- multifurcatingGroups (Node _ _ []) = []
-- multifurcatingGroups (Node _ _ [x]) = multifurcatingGroups x
-- multifurcatingGroups (Node _ _ [x, y]) = multifurcatingGroups x ++ multifurcatingGroups y
-- multifurcatingGroups t = leaves t : concatMap multifurcatingGroups (forest t)

-- -- TODO.
-- prop_bifurcating_tree
--   :: (Ord a, Measurable a, Named a, Supported a) => Tree a -> Bool
-- prop_bifurcating_tree t = partitions (resolve t) == empty

prop_roots_total_length :: Tree Length a -> Bool
prop_roots_total_length t@(Node _ _ [_, _]) =
  all (\b -> abs (b - l) < 1e-8) $
    map totalBranchLength $
      either error id $
        roots t
  where
    l = totalBranchLength t
prop_roots_total_length _ = True

spec :: Spec
spec = do
  -- TODO: describe "Resolve"

  describe "roots" $ do
    it "correctly handles leaves and cherries" $ do
      let tleaf = Node () 0 [] :: Tree () Int
          tcherry = Node () 0 [Node () 1 [], Node () 2 []] :: Tree () Int
      roots tleaf `shouldSatisfy` isLeft
      roots tcherry `shouldBe` Right [tcherry]
    it "correctly handles simple trees" $
      either error id (roots simpleTree) `shouldBe` simpleSol
    modifyMaxSize (* 100) $
      it "returns the correct number of rooted trees for arbitrary trees" $
        property (prop_roots :: (Tree () Int -> Bool))
  describe "rootAt" $
    modifyMaxSize (* 100) $
      it "correctly handles simple trees" $
        do
          let p = either error id $ bipartition simpleTree
          rootAt p simpleTree `shouldBe` Right simpleTree
          let l = S.singleton "x"
              r = S.fromList ["y", "z"]
              p' = either error id $ bp l r
          either error id (rootAt p' simpleTree) `shouldSatisfy` (`equal` (simpleSol !! 1))
  describe "rootsWithBranch" $
    modifyMaxSize (* 100) $
      it "does not change the tree height" $
        property (prop_roots_total_length :: Tree Length Int -> Bool)

-- -- TODO: Move this test to the executable.
-- describe "connect" $
--   modifyMaxSize (* 100) $ do
--     it "returns the correct number of rooted trees for arbitrary trees" $
--       property (prop_connect :: Int -> Tree () Int -> Tree () Int -> Bool)
--     it "correctly connects sample trees without and with constraints" $ do
--       a <- parseFileWith (oneNewick Standard) "data/ConnectA.tree"
--       b <- parseFileWith (oneNewick Standard) "data/ConnectB.tree"
--       c <- parseFileWith (someNewick Standard) "data/ConnectConstraints.tree"
--       let ts =
--             either error id $
--               connect "ROOT" (first (const ()) a) (first (const ()) b)
--           cs =
--             map S.fromList $
--               concatMap (multifurcatingGroups . first (const ())) c ::
--               [Set ByteString]
--           ts' = filter (compatibleWith getName cs) ts
--       length ts `shouldBe` 63
--       length ts' `shouldBe` 15