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 -- | Bac
76 -- | coocurrences graph computation
77 cooc2graphWith :: PartitionMethod
80 -> HashMap (NgramsTerm, NgramsTerm) Int
82 cooc2graphWith Louvain = undefined
83 cooc2graphWith Spinglass = cooc2graphWith' (spinglass 1)
84 -- cooc2graphWith Bac = cooc2graphWith' (\x -> pure $ BAC.defaultClustering x)
87 cooc2graphWith' :: ToComId a
91 -> HashMap (NgramsTerm, NgramsTerm) Int
93 cooc2graphWith' doPartitions distance threshold myCooc = do
95 (distanceMap, diag, ti) = doDistanceMap distance threshold myCooc
100 (as, bs) = List.unzip $ Map.keys distanceMap
101 n' = Set.size $ Set.fromList $ as <> bs
104 saveAsFileDebug "debug/distanceMap" distanceMap
105 printDebug "similarities" similarities
108 partitions <- if (Map.size distanceMap > 0)
109 then doPartitions distanceMap
110 else panic "Text.Flow: DistanceMap is empty"
113 bridgeness' = bridgeness (fromIntegral nodesApprox) partitions distanceMap
115 confluence' = confluence (Map.keys bridgeness') 3 True False
117 pure $ data2graph (Map.toList $ Map.mapKeys unNgramsTerm ti)
118 diag bridgeness' confluence' partitions
121 doDistanceMap :: Distance
123 -> HashMap (NgramsTerm, NgramsTerm) Int
124 -> ( Map (Int,Int) Double
125 , Map (Index, Index) Int
126 , Map NgramsTerm Index
128 doDistanceMap Distributional threshold myCooc = (distanceMap, toIndex ti diag, ti)
131 (diag, theMatrix) = Map.partitionWithKey (\(x,y) _ -> x == y)
133 $ HashMap.toList myCooc
135 (ti, _it) = createIndices theMatrix
139 matCooc = case distance of -- Shape of the Matrix
140 Conditional -> map2mat Triangle 0 tiSize
141 Distributional -> map2mat Square 0 tiSize
142 $ toIndex ti theMatrix
143 similarities = measure distance matCooc
146 similarities = measure Distributional
147 $ map2mat Square 0 tiSize
148 $ toIndex ti theMatrix
150 links = round (let n :: Double = fromIntegral tiSize in n * log n)
152 distanceMap = Map.fromList
157 $ Map.filter (> threshold)
158 $ mat2map similarities
160 doDistanceMap Conditional _threshold myCooc = (distanceMap, toIndex ti myCooc', ti)
162 myCooc' = Map.fromList $ HashMap.toList myCooc
163 (ti, _it) = createIndices myCooc'
164 -- tiSize = Map.size ti
166 -- links = round (let n :: Double = fromIntegral tiSize in n * log n)
168 distanceMap = toIndex ti
173 -- $ HashMap.filter (> threshold)
178 ----------------------------------------------------------
179 -- | From data to Graph
181 type Occurrences = Map (Int, Int) Int
183 data2graph :: ToComId a
186 -> Map (Int, Int) Double
187 -> Map (Int, Int) Double
190 data2graph labels occurences bridge conf partitions = Graph { _graph_nodes = nodes
191 , _graph_edges = edges
192 , _graph_metadata = Nothing
196 community_id_by_node_id = Map.fromList
197 $ map nodeId2comId partitions
199 nodes = map (setCoord ForceAtlas labels bridge)
200 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) occurences)
201 , node_type = Terms -- or Unknown
202 , node_id = cs (show n)
207 Attributes { clust_default = maybe 0 identity
208 (Map.lookup n community_id_by_node_id) }
209 , node_children = [] }
212 , Set.member n $ Set.fromList
214 $ map (\((s,t),d) -> if d > 0 && s /=t then [s,t] else [])
218 edges = [ Edge { edge_source = cs (show s)
219 , edge_target = cs (show t)
220 , edge_weight = weight
221 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
222 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
223 , edge_id = cs (show i)
225 | (i, ((s,t), weight)) <- zip ([0..]::[Integer] )
232 ------------------------------------------------------------------------
234 data Layout = KamadaKawai | ACP | ForceAtlas
237 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
238 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
244 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> Node
245 setCoord l labels m (n,node) = node { node_x_coord = x
249 (x,y) = getCoord l labels m n
255 -> Map (Int, Int) Double
258 getCoord KamadaKawai _ _m _n = undefined -- layout m n
260 getCoord ForceAtlas _ _ n = (sin d, cos d)
264 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
266 $ pcaReduceTo (Dimension 2)
269 to2d :: Vec.Vector Double -> (Double, Double)
272 ds = take 2 $ Vec.toList v
276 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
277 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
281 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
282 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
283 ------------------------------------------------------------------------
285 -- | KamadaKawai Layout
286 -- TODO TEST: check labels, nodeId and coordinates
287 layout :: Map (Int, Int) Double -> Int -> Gen -> (Double, Double)
288 layout m n gen = maybe (panic "") identity $ Map.lookup n $ coord
290 coord :: (Map Int (Double,Double))
291 coord = Map.fromList $ List.zip (Igraph.nodes g) $ (Layout.layout g p gen)
292 --p = Layout.defaultLGL
293 p = Layout.kamadaKawai
294 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m
296 -----------------------------------------------------------------------------
298 cooc2graph'' :: Ord t => Distance
301 -> Map (Index, Index) Double
302 cooc2graph'' distance threshold myCooc = neighbourMap
304 (ti, _) = createIndices myCooc
305 myCooc' = toIndex ti myCooc
306 matCooc = map2mat Triangle 0 (Map.size ti) $ Map.filter (> 1) myCooc'
307 distanceMat = measure distance matCooc
308 neighbourMap = filterByNeighbours threshold
309 $ mat2map distanceMat
312 filterByNeighbours :: Double -> Map (Index, Index) Double -> Map (Index, Index) Double
313 filterByNeighbours threshold distanceMap = filteredMap
316 indexes = List.nub $ List.concat $ map (\(idx,idx') -> [idx,idx'] ) $ Map.keys distanceMap
317 filteredMap :: Map (Index, Index) Double
318 filteredMap = Map.fromList
321 let selected = List.reverse
325 $ Map.filterWithKey (\(from,_) _ -> idx == from) distanceMap
326 in List.take (round threshold) selected