2 Module : Gargantext.Core.Viz.Graph.Bridgeness
3 Description : Bridgeness filter
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 Let be a graph Bridgeness filters inter-communities links in two ways.
11 If the partitions are known, filtering is uniform to expose the communities clearly for the beginners.
16 filters inter-communities links.
18 TODO use Map LouvainNodeId (Map LouvainNodeId)
21 {-# LANGUAGE BangPatterns #-}
23 module Gargantext.Core.Viz.Graph.Bridgeness -- (bridgeness)
26 import Data.Map.Strict (Map, fromListWith, lookup, toList, mapWithKey, elems)
27 import Data.Maybe (catMaybes)
28 import Data.Ord (Down(..))
30 import Data.Tuple.Extra (swap)
31 import Debug.Trace (trace)
32 import Gargantext.Core.Methods.Similarities (Similarity(..))
33 import Gargantext.Prelude
34 import Graph.Types (ClusterNode(..))
35 import qualified Data.List as List
36 import qualified Data.Map.Strict as Map
37 import qualified Data.Set as Set
38 import qualified Data.Tuple.Extra as Tuple
39 import qualified Data.IntMap as Dico
40 ----------------------------------------------------------------------
42 type Partitions = Map (Int, Int) Double -> IO [ClusterNode]
43 ----------------------------------------------------------------------
44 nodeId2comId :: ClusterNode -> (NodeId, CommunityId)
45 nodeId2comId (ClusterNode i1 i2) = (i1, i2)
48 type CommunityId = Int
50 ----------------------------------------------------------------------
51 ----------------------------------------------------------------------
52 -- recursiveClustering : get get more granularity of a given clustering
53 -- tested with spinglass clustering only (WIP)
54 recursiveClustering :: Partitions -> Map (Int, Int) Double -> IO [ClusterNode]
55 recursiveClustering f mp = do
58 n = fromIntegral $ Set.size
59 $ Set.unions $ List.concat
60 $ map (\(k1,k2) -> map Set.singleton [k1, k2])
64 t = round $ (n / 2) * (sqrt n) / 100
66 (toSplit,others) <- List.span (\a -> Set.size a > t) <$> clusterNodes2sets <$> f mp
67 cls' <- mapM f $ map (\s -> removeNodes s mp) toSplit
68 pure $ setNodes2clusterNodes $ others <> (List.concat $ map clusterNodes2sets cls')
70 setNodes2clusterNodes :: [Set NodeId] -> [ClusterNode]
71 setNodes2clusterNodes ns = List.concat $ map (\(n,ns') -> toCluster n ns') $ zip [1..] ns
73 toCluster :: CommunityId -> Set NodeId -> [ClusterNode]
74 toCluster cId setNodeId = map (\n -> ClusterNode n cId) (Set.toList setNodeId)
76 removeNodes :: Set NodeId
77 -> Map (NodeId, NodeId) Double
78 -> Map (NodeId, NodeId) Double
79 removeNodes s = Map.filterWithKey (\(n1,n2) _v -> Set.member n1 s && Set.member n2 s)
82 clusterNodes2sets :: [ClusterNode] -> [Set NodeId]
83 clusterNodes2sets = Dico.elems
84 . Dico.fromListWith (<>)
85 . (map ((Tuple.second Set.singleton) . swap . nodeId2comId))
87 ----------------------------------------------------------------------
88 ----------------------------------------------------------------------
89 data Bridgeness = Bridgeness_Basic { bridgeness_partitions :: [ClusterNode]
90 , bridgeness_filter :: Double
92 | Bridgeness_Advanced { bridgeness_similarity :: Similarity
93 , bridgness_confluence :: Confluence
96 type Confluence = Map (NodeId, NodeId) Double
99 bridgeness :: Bridgeness
100 -> Map (NodeId, NodeId) Double
101 -> Map (NodeId, NodeId) Double
102 bridgeness (Bridgeness_Advanced sim c) m = Map.fromList
103 $ List.filter (\x -> if sim == Conditional then snd x > 0.2 else snd x > 0.02)
104 $ map (\(ks, (v1,_v2)) -> (ks,v1))
105 -- $ List.take (if sim == Conditional then 2*n else 3*n)
106 -- $ List.sortOn (Down . (snd . snd))
108 $ trace ("bridgeness3 m c" <> show (m,c))
109 $ Map.intersectionWithKey
110 (\k v1 v2 -> trace ("intersectionWithKey " <> (show (k, v1, v2))) (v1, v2)) m c
116 !n = trace ("bridgeness m size: " <> (show $ List.length m'))
118 $ (fromIntegral $ List.length m') / (log $ fromIntegral nodesNumber :: Double)
121 nodesNumber = Set.size $ Set.fromList $ as <> bs
123 (as, bs) = List.unzip $ Map.keys m
127 bridgeness (Bridgeness_Basic ns b) m = Map.fromList
131 $ groupEdges (Map.fromList $ map nodeId2comId ns) m
133 groupEdges :: (Ord a, Ord b1)
136 -> Map (a, a) [((b1, b1), b2)]
137 groupEdges m = fromListWith (<>)
141 n1n2_m = (,) <$> lookup n1 m <*> lookup n2 m
142 n1n2_d = Just [((n1,n2),d)]
143 in (,) <$> n1n2_m <*> n1n2_d
147 -- | TODO : sortOn Confluence
148 filterComs :: (Ord n1, Eq n2)
150 -> Map (n2, n2) [(a3, n1)]
151 -> Map (n2, n2) [(a3, n1)]
152 filterComs _b m = Map.filter (\n -> length n > 0) $ mapWithKey filter' m
157 | otherwise = take (2*n) $ List.sortOn (Down . snd) a
160 n = round $ 100 * a' / t
161 a'= fromIntegral $ length a
163 t = fromIntegral $ length $ List.concat $ elems m
165 --------------------------------------------------------------
168 map2intMap :: Map (Int, Int) a -> IntMap (IntMap a)
169 map2intMap m = IntMap.fromListWith (<>)
170 $ map (\((k1,k2), v) -> if k1 < k2
171 then (k1, IntMap.singleton k2 v)
172 else (k2, IntMap.singleton k1 v)
176 look :: (Int,Int) -> IntMap (IntMap a) -> Maybe a
177 look (k1,k2) m = if k1 < k2
178 then case (IntMap.lookup k1 m) of
179 Just m' -> IntMap.lookup k2 m'
185 Compute the median of a list
186 From: https://hackage.haskell.org/package/dsp-0.2.5.1/docs/src/Numeric.Statistics.Median.html
187 Compute the center of the list in a more lazy manner
188 and thus halves memory requirement.
191 median :: (Ord a, Fractional a) => [a] -> a
192 median [] = panic "medianFast: empty list has no median"
194 let recurse (x0:_) (_:[]) = x0
195 recurse (x0:x1:_) (_:_:[]) = (x0+x1)/2
196 recurse (_:xs) (_:_:ys) = recurse xs ys
198 panic "median: this error cannot occur in the way 'recurse' is called"