29
29
# Our own modules
30
30
import util
31
31
32
+ #-----------------------------------------------------------------------------
33
+ # Functions
34
+ #-----------------------------------------------------------------------------
35
+ def empty_module (msg = 'Empty module' ):
36
+ """Raise an exception if an empty module is found"""
37
+
38
+ raise ValueError (msg )
39
+
40
+
32
41
#-----------------------------------------------------------------------------
33
42
# Class declarations
34
43
#-----------------------------------------------------------------------------
35
44
36
45
class GraphPartition (object ):
37
- """Represent a graph partition."""
46
+ """Represent a graph partition.
47
+
48
+ The main object keeping track of the data is the .index attribute, a dict
49
+ that maps integer module labels to node sets. This dict's labels are
50
+ always assumed to start at 0 and not to point ever to empty modules. If
51
+ empty modules are created during any module manipulations, they will be
52
+ removed from the index and the remaining modules will be relabeled."""
38
53
39
54
def __init__ (self , graph , index ):
40
55
"""New partition, given a graph and a dict of module->nodes.
@@ -337,20 +352,16 @@ def apply_module_split(self, m, n1, n2, split_modules, e_new, a_new):
337
352
#Otherwise need to create a new function to update/recompute mod_e and
338
353
#mod_a.
339
354
340
- # This checks whether there is an empty module. If so, renames the keys.
341
- #if len(self.index[m1])<1:
342
- # self.index.pop(m1)
343
- # rename_keys(self.index,m1)
344
- # This checks whether there is an empty module. If so, renames the keys.
345
- #if len(self.index[m2])<1:
346
- # self.index.pop(m2)
347
- # rename_keys(self.index,m2)
348
-
349
- # For now, rather than renumbering, just check if this ever happens
355
+ # If there are empty modules after the operation, remove them from the
356
+ # index and rename the partition labels
350
357
if len (self .index [m1 ])< 1 :
351
- raise ValueError ('Empty module after module split, old mod' )
358
+ self .index .pop (m1 )
359
+ rename_keys (self .index , m1 )
360
+ return
352
361
if len (self .index [m2 ])< 1 :
353
- raise ValueError ('Empty module after module split, new mod' )
362
+ self .index .pop (m2 )
363
+ rename_keys (self .index , m2 )
364
+ return
354
365
355
366
def node_update (self , n , m1 , m2 ):
356
367
"""Moves a single node within or between modules
@@ -439,6 +450,12 @@ def apply_node_update(self, n, m1, m2, node_moved_mods, e_new, a_new):
439
450
The module that n used to belong to.
440
451
m2 : module identifier
441
452
The module that n will now belong to.
453
+ node_moved_mods : tuple
454
+ The two sets of modules for modules m1 and m2.
455
+ e_new : 2-tuple of arrays
456
+ The E arrays for m1 and m2
457
+ a_new : 2-tuple of arrays
458
+ The A arrays for m1 and m2
442
459
443
460
Returns
444
461
-------
@@ -450,18 +467,27 @@ def apply_node_update(self, n, m1, m2, node_moved_mods, e_new, a_new):
450
467
self .index [m1 ] = node_moved_mods [0 ]
451
468
self .index [m2 ] = node_moved_mods [1 ]
452
469
470
+ # If we end up with an empty module, we need to remove it from the
471
+ # partition, and store the information only for the new one.
472
+
453
473
# This checks whether there is an empty module. If so, renames the keys.
454
- if len (self .index [m1 ])< 1 :
455
- raise ValueError ('Empty module after node move' )
456
- #self.index.pop(m1)
457
- #rename_keys(self.index,m1)
474
+ if len (self .index [m1 ]) < 1 :
475
+ #empty_module('Empty module after node move')
476
+ self .index .pop (m1 )
477
+ rename_keys (self .index , m1 )
478
+ # Once the index structure changes, the labeling of E and A arrays
479
+ # will need to be recomputed (we could propagate the changes
480
+ # throughout, but it's extremely brittle and easy to make a very
481
+ # hard to debug error. Safer to just recompute the arrays in this
482
+ # case).
483
+ self .mod_e , self .mod_a = self ._edge_info ()
458
484
#if m1 < m2:
459
485
# m2 = m2 - 1 #only need to rename this index if m1 is before m2
460
-
461
- self .mod_e [m1 ] = e_new [0 ]
462
- self .mod_a [m1 ] = a_new [0 ]
463
- self .mod_e [m2 ] = e_new [1 ]
464
- self .mod_a [m2 ] = a_new [1 ]
486
+ else :
487
+ self .mod_e [m1 ] = e_new [0 ]
488
+ self .mod_a [m1 ] = a_new [0 ]
489
+ self .mod_e [m2 ] = e_new [1 ]
490
+ self .mod_a [m2 ] = a_new [1 ]
465
491
466
492
return m2
467
493
@@ -1005,9 +1031,9 @@ def mutual_information(d1, d2):
1005
1031
# the entire partitions, we look for this problem at this stage, and bail
1006
1032
# if there was an empty module.
1007
1033
## if (nsum_row==0).any():
1008
- ## raise ValueError ("Empty module in second partition.")
1034
+ ## empty_module ("Empty module in second partition.")
1009
1035
## if (nsum_col==0).any():
1010
- ## raise ValueError ("Empty module in first partition.")
1036
+ ## empty_module ("Empty module in first partition.")
1011
1037
1012
1038
# nn is the total number of nodes
1013
1039
nn = nsum_row .sum ()
@@ -1136,7 +1162,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
1136
1162
graph_partition .modularity (), 11 )
1137
1163
for mod in graph_partition .index :
1138
1164
if len (graph_partition .index [mod ]) < 1 :
1139
- raise ValueError ('Empty module after module %s,SA' % (movetype ))
1165
+ empty_module ('Empty module after module %s,SA' % (movetype ))
1140
1166
1141
1167
1142
1168
#maybe store the best one here too?
@@ -1206,7 +1232,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
1206
1232
1207
1233
for mod in graph_partition .index :
1208
1234
if len (graph_partition .index [mod ]) < 1 :
1209
- raise ValueError ('Empty module after ndoe move,SA' )
1235
+ empty_module ('Empty module after ndoe move,SA' )
1210
1236
1211
1237
1212
1238
#else:
@@ -1264,7 +1290,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
1264
1290
1265
1291
for mod in graph_part_final .index :
1266
1292
if len (graph_part_final .index [mod ]) < 1 :
1267
- raise ValueError ('LAST CHECK: Empty module after module %s,SA' % (movetype ))
1293
+ empty_module ('LAST CHECK: Empty module after module %s,SA' % (movetype ))
1268
1294
1269
1295
if extra_info :
1270
1296
extra_dict = dict (energy = energy_array , temp = temp_array )
@@ -1416,9 +1442,13 @@ def adjust_partition(g, partition, max_iter=None):
1416
1442
Partition with higher modularity.
1417
1443
1418
1444
"""
1445
+ # Static copy of the entire list of nodes in the graph
1419
1446
nodes = g .nodes ()
1447
+ # Set of module labels in the initial partition
1420
1448
P = set (range (len (partition )))
1421
1449
1450
+ # Create a dict that maps nodes to the partition label they belong to.
1451
+ # This is effectively a reverse of the partition.index.
1422
1452
node_map = {}
1423
1453
for p in P :
1424
1454
for node in partition .index [p ]:
0 commit comments