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
18 import Data.HashMap.Strict (HashMap)
20 import Data.Maybe (fromMaybe)
21 import Data.Swagger hiding (items)
22 import GHC.Float (sin, cos)
23 import GHC.Generics (Generic)
24 import Gargantext.API.Ngrams.Types (NgramsTerm(..))
25 import Gargantext.Core.Methods.Distances (Distance(..), measure)
26 import Gargantext.Core.Methods.Distances.Conditional (conditional)
27 import Gargantext.Core.Methods.Graph.BAC.Proxemy (confluence)
28 import Gargantext.Core.Statistics
29 import Gargantext.Core.Viz.Graph
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.Utils (edgesFilter)
34 import Gargantext.Prelude
35 import Graph.Types (ClusterNode)
36 import IGraph.Random -- (Gen(..))
37 import Test.QuickCheck (elements)
38 import Test.QuickCheck.Arbitrary
39 import qualified Data.HashMap.Strict as HashMap
40 import qualified Data.List as List
41 import qualified Data.Map as Map
42 import qualified Data.Set as Set
43 import qualified Data.Vector.Storable as Vec
44 import qualified Graph.BAC.ProxemyOptim as BAC
45 import qualified IGraph as Igraph
46 import qualified IGraph.Algorithms.Layout as Layout
49 data PartitionMethod = Spinglass | Confluence
50 deriving (Generic, Eq, Ord, Enum, Bounded, Show)
51 instance FromJSON PartitionMethod
52 instance ToJSON PartitionMethod
53 instance ToSchema PartitionMethod
54 instance Arbitrary PartitionMethod where
55 arbitrary = elements [ minBound .. maxBound ]
58 -------------------------------------------------------------
59 defaultClustering :: Map (Int, Int) Double -> IO [ClusterNode]
60 -- defaultClustering x = pure $ BAC.defaultClustering x
61 defaultClustering x = spinglass 1 x
63 -------------------------------------------------------------
64 type Threshold = Double
67 cooc2graph' :: Ord t => Distance
70 -> Map (Index, Index) Double
71 cooc2graph' distance threshold myCooc
72 = Map.filter (> threshold)
76 Conditional -> map2mat Triangle 0 tiSize
77 Distributional -> map2mat Square 0 tiSize
78 $ Map.filter (> 1) myCooc'
81 (ti, _) = createIndices myCooc
83 myCooc' = toIndex ti myCooc
87 -- coocurrences graph computation
88 cooc2graphWith :: PartitionMethod
91 -> HashMap (NgramsTerm, NgramsTerm) Int
93 cooc2graphWith Spinglass = cooc2graphWith' (spinglass 1)
94 cooc2graphWith Confluence= cooc2graphWith' (\x -> pure $ BAC.defaultClustering x)
97 cooc2graphWith' :: ToComId a
101 -> HashMap (NgramsTerm, NgramsTerm) Int
103 cooc2graphWith' doPartitions distance threshold myCooc = do
105 (distanceMap, diag, ti) = doDistanceMap distance threshold myCooc
108 saveAsFileDebug "/tmp/distanceMap" distanceMap
109 saveAsFileDebug "/tmp/distanceMap.keys" (List.length $ Map.keys distanceMap)
110 -- printDebug "similarities" similarities
113 partitions <- if (Map.size distanceMap > 0)
114 then doPartitions distanceMap
115 else panic "Text.Flow: DistanceMap is empty"
121 (as, bs) = List.unzip $ Map.keys distanceMap
122 n' = Set.size $ Set.fromList $ as <> bs
123 bridgeness' = bridgeness (fromIntegral nodesApprox) partitions distanceMap
124 confluence' = confluence (Map.keys bridgeness') 3 True False
126 pure $ data2graph ti diag bridgeness' confluence' partitions
129 doDistanceMap :: Distance
131 -> HashMap (NgramsTerm, NgramsTerm) Int
132 -> ( Map (Int,Int) Double
133 , Map (Index, Index) Int
134 , Map NgramsTerm Index
136 doDistanceMap Distributional threshold myCooc = (distanceMap, toIndex ti diag, ti)
139 (diag, theMatrix) = Map.partitionWithKey (\(x,y) _ -> x == y)
141 $ HashMap.toList myCooc
143 (ti, _it) = createIndices theMatrix
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)^(2::Int))
152 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'
166 links = round (let n :: Double = fromIntegral (Map.size ti) in n * log n)
168 distanceMap = toIndex ti
173 $ HashMap.filter (> threshold)
176 ----------------------------------------------------------
177 -- | From data to Graph
179 type Occurrences = Int
181 data2graph :: ToComId a
182 => Map NgramsTerm Int
183 -> Map (Int, Int) Occurrences
184 -> Map (Int, Int) Double
185 -> Map (Int, Int) Double
188 data2graph labels' occurences bridge conf partitions = Graph { _graph_nodes = nodes
189 , _graph_edges = edges
190 , _graph_metadata = Nothing
194 nodes = map (setCoord ForceAtlas labels bridge)
195 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) occurences)
196 , node_type = Terms -- or Unknown
197 , node_id = cs (show n)
198 , node_label = unNgramsTerm l
201 , node_attributes = Attributes { clust_default = fromMaybe 0
202 (Map.lookup n community_id_by_node_id)
208 , Set.member n nodesWithScores
211 edges = [ Edge { edge_source = cs (show s)
212 , edge_target = cs (show t)
213 , edge_weight = weight
214 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
215 , edge_id = cs (show i)
217 | (i, ((s,t), weight)) <- zip ([0..]::[Integer] ) $ Map.toList bridge
222 community_id_by_node_id = Map.fromList
223 $ map nodeId2comId partitions
225 labels = Map.toList labels'
227 nodesWithScores = Set.fromList
229 $ map (\((s,t),d) -> if d > 0 && s/=t then [s,t] else [])
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