Skip to content

Commit 74afa3d

Browse files
committed
First changes towards allowing empty modules to appear during
modularity updates. The empty modules will be allowed without raising an exception, instead they will be immediately removed with a renaming of the remaining labels.
1 parent ea3e805 commit 74afa3d

File tree

2 files changed

+43
-24
lines changed

2 files changed

+43
-24
lines changed

brainx/modularity.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ def empty_module(msg='Empty module'):
4343
#-----------------------------------------------------------------------------
4444

4545
class GraphPartition(object):
46-
"""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."""
4753

4854
def __init__(self, graph, index):
4955
"""New partition, given a graph and a dict of module->nodes.
@@ -346,20 +352,16 @@ def apply_module_split(self, m, n1, n2, split_modules, e_new, a_new):
346352
#Otherwise need to create a new function to update/recompute mod_e and
347353
#mod_a.
348354

349-
# This checks whether there is an empty module. If so, renames the keys.
350-
#if len(self.index[m1])<1:
351-
# self.index.pop(m1)
352-
# rename_keys(self.index,m1)
353-
# This checks whether there is an empty module. If so, renames the keys.
354-
#if len(self.index[m2])<1:
355-
# self.index.pop(m2)
356-
# rename_keys(self.index,m2)
357-
358-
# 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
359357
if len(self.index[m1])<1:
360-
empty_module('Empty module after module split, old mod')
358+
self.index.pop(m1)
359+
rename_keys(self.index, m1)
360+
return
361361
if len(self.index[m2])<1:
362-
empty_module('Empty module after module split, new mod')
362+
self.index.pop(m2)
363+
rename_keys(self.index, m2)
364+
return
363365

364366
def node_update(self, n, m1, m2):
365367
"""Moves a single node within or between modules
@@ -448,6 +450,12 @@ def apply_node_update(self, n, m1, m2, node_moved_mods, e_new, a_new):
448450
The module that n used to belong to.
449451
m2 : module identifier
450452
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
451459
452460
Returns
453461
-------
@@ -459,18 +467,27 @@ def apply_node_update(self, n, m1, m2, node_moved_mods, e_new, a_new):
459467
self.index[m1] = node_moved_mods[0]
460468
self.index[m2] = node_moved_mods[1]
461469

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+
462473
# This checks whether there is an empty module. If so, renames the keys.
463-
if len(self.index[m1])<1:
464-
empty_module('Empty module after node move')
465-
#self.index.pop(m1)
466-
#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()
467484
#if m1 < m2:
468485
# m2 = m2 - 1 #only need to rename this index if m1 is before m2
469-
470-
self.mod_e[m1] = e_new[0]
471-
self.mod_a[m1] = a_new[0]
472-
self.mod_e[m2] = e_new[1]
473-
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]
474491

475492
return m2
476493

@@ -1425,9 +1442,13 @@ def adjust_partition(g, partition, max_iter=None):
14251442
Partition with higher modularity.
14261443
14271444
"""
1445+
# Static copy of the entire list of nodes in the graph
14281446
nodes = g.nodes()
1447+
# Set of module labels in the initial partition
14291448
P = set(range(len(partition)))
14301449

1450+
# Create a dict that maps nodes to the partition label they belong to.
1451+
# This is effectively a reverse of the partition.index.
14311452
node_map = {}
14321453
for p in P:
14331454
for node in partition.index[p]:

brainx/tests/test_modularity.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,8 +655,6 @@ def test_apply_module_split():
655655

656656

657657
def test_apply_node_move():
658-
#if 1:
659-
660658
"""Test the GraphPartition operation that moves a single node so that it
661659
returns a change in modularity that reflects the difference between the
662660
modularity of the new and old parititions"""

0 commit comments

Comments
 (0)