From b0b41bbaf882bb9f269e33da43ba06d17356299c Mon Sep 17 00:00:00 2001 From: Geoff Sims Date: Thu, 18 Apr 2019 21:24:04 +1000 Subject: [PATCH 1/3] Added ability to seed the FA2 class so as to get reproducible visuals --- fa2/forceatlas2.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fa2/forceatlas2.py b/fa2/forceatlas2.py index b1ad3a5..c941c44 100644 --- a/fa2/forceatlas2.py +++ b/fa2/forceatlas2.py @@ -63,6 +63,9 @@ def __init__(self, strongGravityMode=False, gravity=1.0, + # Random seed + seed=None, + # Log verbose=True): assert linLogMode == adjustSizes == multiThreaded == False, "You selected a feature that has not been implemented yet..." @@ -77,11 +80,14 @@ def __init__(self, self.strongGravityMode = strongGravityMode self.gravity = gravity self.verbose = verbose + self.seed = seed def init(self, G, # a graph in 2D numpy ndarray format (or) scipy sparse matrix format pos=None # Array of initial positions ): + print(self.seed) + random.seed(a=self.seed) isSparse = False if isinstance(G, numpy.ndarray): # Check our assumptions From b5a4613309216f96bea936dde9f12e106efd7f42 Mon Sep 17 00:00:00 2001 From: Geoff Sims Date: Thu, 18 Apr 2019 21:25:12 +1000 Subject: [PATCH 2/3] Remove debugging stage --- fa2/forceatlas2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fa2/forceatlas2.py b/fa2/forceatlas2.py index c941c44..520c07e 100644 --- a/fa2/forceatlas2.py +++ b/fa2/forceatlas2.py @@ -86,7 +86,6 @@ def init(self, G, # a graph in 2D numpy ndarray format (or) scipy sparse matrix format pos=None # Array of initial positions ): - print(self.seed) random.seed(a=self.seed) isSparse = False if isinstance(G, numpy.ndarray): From dd2856cee14e97e7e577a92bf4d77dd08b448001 Mon Sep 17 00:00:00 2001 From: Geoff Sims Date: Thu, 18 Apr 2019 22:40:41 +1000 Subject: [PATCH 3/3] Added functionality to allow the fa2 function to preserve the historical positions of the nodes and return them --- fa2/forceatlas2.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/fa2/forceatlas2.py b/fa2/forceatlas2.py index 520c07e..19ca271 100644 --- a/fa2/forceatlas2.py +++ b/fa2/forceatlas2.py @@ -154,7 +154,8 @@ def init(self, def forceatlas2(self, G, # a graph in 2D numpy ndarray format (or) scipy sparse matrix format pos=None, # Array of initial positions - iterations=100 # Number of times to iterate the main loop + iterations=100, # Number of times to iterate the main loop + keep_history=False # Whether or not to return the historical values while fa2 is running ): # Initializing, initAlgo() # ================================================================ @@ -183,6 +184,7 @@ def forceatlas2(self, niters = range(iterations) if self.verbose: niters = tqdm(niters) + history = [] for _i in niters: for n in nodes: n.old_dx = n.dx @@ -224,6 +226,11 @@ def forceatlas2(self, speedEfficiency = values['speedEfficiency'] applyforces_timer.stop() + # Add current positions to history + if keep_history: + positions = [(n.x, n.y) for n in nodes] + history.append(positions) + if self.verbose: if self.barnesHutOptimize: barneshut_timer.display() @@ -232,23 +239,29 @@ def forceatlas2(self, attraction_timer.display() applyforces_timer.display() # ================================================================ - return [(n.x, n.y) for n in nodes] + if not keep_history: + return [(n.x, n.y) for n in nodes] + else: + return positions, history # A layout for NetworkX. # # This function returns a NetworkX layout, which is really just a # dictionary of node positions (2D X-Y tuples) indexed by the node name. - def forceatlas2_networkx_layout(self, G, pos=None, iterations=100): + def forceatlas2_networkx_layout(self, G, pos=None, iterations=100, keep_history=False): import networkx assert isinstance(G, networkx.classes.graph.Graph), "Not a networkx graph" assert isinstance(pos, dict) or (pos is None), "pos must be specified as a dictionary, as in networkx" M = networkx.to_scipy_sparse_matrix(G, dtype='f', format='lil') if pos is None: - l = self.forceatlas2(M, pos=None, iterations=iterations) + l = self.forceatlas2(M, pos=None, iterations=iterations, keep_history=keep_history) else: poslist = numpy.asarray([pos[i] for i in G.nodes()]) - l = self.forceatlas2(M, pos=poslist, iterations=iterations) - return dict(zip(G.nodes(), l)) + l = self.forceatlas2(M, pos=poslist, iterations=iterations, keep_history=keep_history) + if not keep_history: + return dict(zip(G.nodes(), l)) + else: + return [dict(zip(G.nodes(), i)) for i in l[1]] # A layout for igraph. #