2 Module : Gargantext.Core.Viz.Graph.Tools
3 Description : Tools to build Graph
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# LANGUAGE ScopedTypeVariables #-}
14 module Gargantext.Core.Viz.Graph.Tools
17 -- import Data.Graph.Clustering.Louvain (hLouvain, {-iLouvainMap-})
18 import Data.HashMap.Strict (HashMap)
20 import Data.Text (Text)
21 -- import Debug.Trace (trace)
22 import GHC.Float (sin, cos)
23 import Gargantext.API.Ngrams.Types (NgramsTerm(..))
24 import Gargantext.Core.Methods.Distances.Conditional (conditional)
25 import Gargantext.Core.Methods.Distances (Distance(..), measure)
26 import Gargantext.Core.Methods.Graph.BAC.Proxemy (confluence)
27 import Gargantext.Core.Statistics
28 import Gargantext.Core.Viz.Graph
29 import Gargantext.Core.Viz.Graph.Utils (edgesFilter)
30 import Gargantext.Core.Viz.Graph.Bridgeness (bridgeness, Partitions, ToComId(..))
31 import Gargantext.Core.Viz.Graph.Index (createIndices, toIndex, map2mat, mat2map, Index, MatrixShape(..))
32 import Gargantext.Core.Viz.Graph.Tools.IGraph (mkGraphUfromEdges, spinglass)
33 import Gargantext.Core.Viz.Graph.Types (ClusterNode)
34 import Gargantext.Prelude
35 -- import qualified Graph.BAC.ProxemyOptim as BAC
36 import IGraph.Random -- (Gen(..))
37 import qualified Data.HashMap.Strict as HashMap
38 import qualified Data.List as List
39 import qualified Data.Map as Map
40 import qualified Data.Set as Set
41 import qualified Data.Vector.Storable as Vec
42 import qualified IGraph as Igraph
43 import qualified IGraph.Algorithms.Layout as Layout
46 -------------------------------------------------------------
47 defaultClustering :: Map (Int, Int) Double -> IO [ClusterNode]
48 -- defaultClustering x = pure $ BAC.defaultClustering x
49 defaultClustering x = spinglass 1 x
51 -------------------------------------------------------------
52 type Threshold = Double
55 cooc2graph' :: Ord t => Distance
58 -> Map (Index, Index) Double
59 cooc2graph' distance threshold myCooc
60 = Map.filter (> threshold)
64 Conditional -> map2mat Triangle 0 tiSize
65 Distributional -> map2mat Square 0 tiSize
66 $ Map.filter (> 1) myCooc'
69 (ti, _) = createIndices myCooc
71 myCooc' = toIndex ti myCooc
74 data PartitionMethod = Louvain | Spinglass
77 -- coocurrences graph computation
78 cooc2graphWith :: PartitionMethod
81 -> HashMap (NgramsTerm, NgramsTerm) Int
83 cooc2graphWith Louvain = undefined
84 cooc2graphWith Spinglass = cooc2graphWith' (spinglass 1)
85 -- cooc2graphWith Bac = cooc2graphWith' (\x -> pure $ BAC.defaultClustering x)
88 cooc2graphWith' :: ToComId a
92 -> HashMap (NgramsTerm, NgramsTerm) Int
94 cooc2graphWith' doPartitions distance threshold myCooc = do
96 (distanceMap, diag, ti) = doDistanceMap distance threshold myCooc
101 (as, bs) = List.unzip $ Map.keys distanceMap
102 n' = Set.size $ Set.fromList $ as <> bs
105 saveAsFileDebug "debug/distanceMap" distanceMap
106 printDebug "similarities" similarities
109 partitions <- if (Map.size distanceMap > 0)
110 then doPartitions distanceMap
111 else panic "Text.Flow: DistanceMap is empty"
114 bridgeness' = bridgeness (fromIntegral nodesApprox) partitions distanceMap
116 confluence' = confluence (Map.keys bridgeness') 3 True False
118 pure $ data2graph (Map.toList $ Map.mapKeys unNgramsTerm ti)
119 diag bridgeness' confluence' partitions
122 doDistanceMap :: Distance
124 -> HashMap (NgramsTerm, NgramsTerm) Int
125 -> ( Map (Int,Int) Double
126 , Map (Index, Index) Int
127 , Map NgramsTerm Index
129 doDistanceMap Distributional threshold myCooc = (distanceMap, toIndex ti diag, ti)
132 (diag, theMatrix) = Map.partitionWithKey (\(x,y) _ -> x == y)
134 $ HashMap.toList myCooc
136 (ti, _it) = createIndices theMatrix
140 matCooc = case distance of -- Shape of the Matrix
141 Conditional -> map2mat Triangle 0 tiSize
142 Distributional -> map2mat Square 0 tiSize
143 $ toIndex ti theMatrix
144 similarities = measure distance matCooc
147 similarities = measure Distributional
148 $ map2mat Square 0 tiSize
149 $ toIndex ti theMatrix
151 links = round (let n :: Double = fromIntegral tiSize in n * log n)
153 distanceMap = Map.fromList
158 $ Map.filter (> threshold)
159 $ mat2map similarities
161 doDistanceMap Conditional _threshold myCooc = (distanceMap, toIndex ti myCooc', ti)
163 myCooc' = Map.fromList $ HashMap.toList myCooc
164 (ti, _it) = createIndices myCooc'
165 -- tiSize = Map.size ti
167 -- links = round (let n :: Double = fromIntegral tiSize in n * log n)
169 distanceMap = toIndex ti
174 -- HashMap.filter (> threshold)
179 ----------------------------------------------------------
180 -- | From data to Graph
182 type Occurrences = Map (Int, Int) Int
184 data2graph :: ToComId a
187 -> Map (Int, Int) Double
188 -> Map (Int, Int) Double
191 data2graph labels occurences bridge conf partitions = Graph { _graph_nodes = nodes
192 , _graph_edges = edges
193 , _graph_metadata = Nothing
197 community_id_by_node_id = Map.fromList
198 $ map nodeId2comId partitions
200 nodes = map (setCoord ForceAtlas labels bridge)
201 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) occurences)
202 , node_type = Terms -- or Unknown
203 , node_id = cs (show n)
208 Attributes { clust_default = maybe 0 identity
209 (Map.lookup n community_id_by_node_id) }
210 , node_children = [] }
213 , Set.member n $ Set.fromList
215 $ map (\((s,t),d) -> if d > 0 && s /=t then [s,t] else [])
219 edges = [ Edge { edge_source = cs (show s)
220 , edge_target = cs (show t)
221 , edge_weight = weight
222 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
223 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
224 , edge_id = cs (show i)
226 | (i, ((s,t), weight)) <- zip ([0..]::[Integer] )
233 ------------------------------------------------------------------------
235 data Layout = KamadaKawai | ACP | ForceAtlas
238 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
239 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
245 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> Node
246 setCoord l labels m (n,node) = node { node_x_coord = x
250 (x,y) = getCoord l labels m n
256 -> Map (Int, Int) Double
259 getCoord KamadaKawai _ _m _n = undefined -- layout m n
261 getCoord ForceAtlas _ _ n = (sin d, cos d)
265 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
267 $ pcaReduceTo (Dimension 2)
270 to2d :: Vec.Vector Double -> (Double, Double)
273 ds = take 2 $ Vec.toList v
277 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
278 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
282 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
283 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
284 ------------------------------------------------------------------------
286 -- | KamadaKawai Layout
287 -- TODO TEST: check labels, nodeId and coordinates
288 layout :: Map (Int, Int) Double -> Int -> Gen -> (Double, Double)
289 layout m n gen = maybe (panic "") identity $ Map.lookup n $ coord
291 coord :: (Map Int (Double,Double))
292 coord = Map.fromList $ List.zip (Igraph.nodes g) $ (Layout.layout g p gen)
293 --p = Layout.defaultLGL
294 p = Layout.kamadaKawai
295 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m
297 -----------------------------------------------------------------------------
299 cooc2graph'' :: Ord t => Distance
302 -> Map (Index, Index) Double
303 cooc2graph'' distance threshold myCooc = neighbourMap
305 (ti, _) = createIndices myCooc
306 myCooc' = toIndex ti myCooc
307 matCooc = map2mat Triangle 0 (Map.size ti) $ Map.filter (> 1) myCooc'
308 distanceMat = measure distance matCooc
309 neighbourMap = filterByNeighbours threshold
310 $ mat2map distanceMat
313 filterByNeighbours :: Double -> Map (Index, Index) Double -> Map (Index, Index) Double
314 filterByNeighbours threshold distanceMap = filteredMap
317 indexes = List.nub $ List.concat $ map (\(idx,idx') -> [idx,idx'] ) $ Map.keys distanceMap
318 filteredMap :: Map (Index, Index) Double
319 filteredMap = Map.fromList
322 let selected = List.reverse
326 $ Map.filterWithKey (\(from,_) _ -> idx == from) distanceMap
327 in List.take (round threshold) selected