packages feed

birch-beer-0.1.0.0: src/BirchBeer/LeafGraph.hs

{- BirchBeer.LeafGraph
Gregory W. Schwartz

Functions pertaining to the generation of a graph for each leaf in the
hierarchy.
-}

{-# LANGUAGE BangPatterns #-}

module BirchBeer.LeafGraph
    ( leafToGraph
    , clusterGraphToLeafGraphMap
    ) where

-- Remote
import Data.Maybe (fromMaybe)
import Math.Clustering.Spectral.Sparse (getSimilarityFromB2, B2 (..))
import qualified Control.Foldl as Fold
import qualified Control.Lens as L
import qualified Data.Foldable as F
import qualified Data.Graph.Inductive as G
import qualified Data.Map.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified Data.Sparse.Common as S
import qualified Data.Text as T

-- Local
import BirchBeer.Types
import BirchBeer.Utility

-- | Convert a ClusterGraph to a map of LeafGraphs.
clusterGraphToLeafGraphMap
    :: (TreeItem a, MatrixLike b)
    => MaxWeight
    -> EdgeThreshold
    -> LeafGraphNodes
    -> ClusterGraph a
    -> SimMatrix b
    -> LeafGraphMap T.Text
clusterGraphToLeafGraphMap maxWeight edgeThresh leafGraphNodes (ClusterGraph gr) simMat =
    LeafGraphMap
        . Map.unions
        . fmap (\ (!x, _)
            -> Map.singleton x
                . leafToGraph
                    maxWeight
                    edgeThresh
                    leafGraphNodes
                    (ClusterGraph gr)
                    simMat
                $ x
            )
        . F.toList
        . getGraphLeaves gr
        $ 0

-- | Generate a graph from a node in the ClusterGraph. Determines the edge from
-- either the similarity matrix (trivial) or the B2 matrix from a spectral
-- hierarchical clustering.
leafToGraph
    :: (TreeItem a, MatrixLike b)
    => MaxWeight
    -> EdgeThreshold
    -> LeafGraphNodes
    -> ClusterGraph a
    -> SimMatrix b
    -> G.Node
    -> LeafGraph T.Text
leafToGraph (MaxWeight maxWeight) (EdgeThreshold edgeThresh) (LeafGraphNodes leafGraphNodes) gr simMat n =
    LeafGraph . G.mkGraph nodes $ edges
  where
    edges :: [G.LEdge Double]
    edges = if Set.member n leafGraphNodes || Set.null leafGraphNodes
                then filter (\(i, j, v) -> i /= j && v > edgeThresh)
                   . fmap (L.over L._3 (/ maxWeight))
                   $ (\(i, _) (j, _) -> getEdge simMat i j) <$> nodes <*> nodes -- Ignore self edges.
                else []
    nodes :: [G.LNode (G.Node, T.Text)]
    nodes = fmap (\ (!n, !l) -> (n, (n, l)))
          . flip zip items
          . fmap (fromMaybe (error "No row in matrix found for item in tree."))
          . Fold.fold getIndices
          . getRowNames
          . getMat
          $ simMat
    -- normByMaxWeight = (\m xs -> fmap (L.over L._3 (/ fromMaybe 1 m)) xs) -- Do not normalize here.
    --               <$> Fold.premap (abs . L.view L._3) Fold.maximum
    --               <*> Fold.list
    getIndices = sequenceA $ fmap Fold.elemIndex items
    items      = fmap (unId . getId) . F.toList $ getGraphLeafItems gr n
    getMat (SimilarityMatrix x) = x
    getMat (B2Matrix x)         = x

-- | Get an edge where the nodes are rows of a matrix.
getEdge :: (MatrixLike a) => SimMatrix a -> G.Node -> G.Node -> G.LEdge Double
getEdge (SimilarityMatrix !mat) !i !j =
    (i, j, fromMaybe 0 $ S.lookupSM (getMatrix mat) i j)
getEdge (B2Matrix !mat) !i !j         =
    (i, j, getSimilarityFromB2 (B2 $ getMatrix mat) i j)