Skip to content

Commit a9fd05a

Browse files
author
cindeem
committed
Merge pull request #3 from fperez/empty-modules
Remove old parametric testing from test suite, add functionality for dealing with empty partitions in modularity.py
2 parents d807bd3 + 516ae19 commit a9fd05a

File tree

6 files changed

+135
-407
lines changed

6 files changed

+135
-407
lines changed

brainx/modularity.py

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,27 @@
2929
# Our own modules
3030
import util
3131

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+
3241
#-----------------------------------------------------------------------------
3342
# Class declarations
3443
#-----------------------------------------------------------------------------
3544

3645
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."""
3853

3954
def __init__(self, graph, index):
4055
"""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):
337352
#Otherwise need to create a new function to update/recompute mod_e and
338353
#mod_a.
339354

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
350357
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
352361
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
354365

355366
def node_update(self, n, m1, m2):
356367
"""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):
439450
The module that n used to belong to.
440451
m2 : module identifier
441452
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
442459
443460
Returns
444461
-------
@@ -450,18 +467,27 @@ def apply_node_update(self, n, m1, m2, node_moved_mods, e_new, a_new):
450467
self.index[m1] = node_moved_mods[0]
451468
self.index[m2] = node_moved_mods[1]
452469

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+
453473
# 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()
458484
#if m1 < m2:
459485
# 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]
465491

466492
return m2
467493

@@ -1005,9 +1031,9 @@ def mutual_information(d1, d2):
10051031
# the entire partitions, we look for this problem at this stage, and bail
10061032
# if there was an empty module.
10071033
## if (nsum_row==0).any():
1008-
## raise ValueError("Empty module in second partition.")
1034+
## empty_module("Empty module in second partition.")
10091035
## if (nsum_col==0).any():
1010-
## raise ValueError("Empty module in first partition.")
1036+
## empty_module("Empty module in first partition.")
10111037

10121038
# nn is the total number of nodes
10131039
nn = nsum_row.sum()
@@ -1136,7 +1162,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
11361162
graph_partition.modularity(), 11)
11371163
for mod in graph_partition.index:
11381164
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))
11401166

11411167

11421168
#maybe store the best one here too?
@@ -1206,7 +1232,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
12061232

12071233
for mod in graph_partition.index:
12081234
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')
12101236

12111237

12121238
#else:
@@ -1264,7 +1290,7 @@ def simulated_annealing(g, p0=None, temperature = 50, temp_scaling = 0.995, tmin
12641290

12651291
for mod in graph_part_final.index:
12661292
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))
12681294

12691295
if extra_info:
12701296
extra_dict = dict(energy = energy_array, temp = temp_array)
@@ -1416,9 +1442,13 @@ def adjust_partition(g, partition, max_iter=None):
14161442
Partition with higher modularity.
14171443
14181444
"""
1445+
# Static copy of the entire list of nodes in the graph
14191446
nodes = g.nodes()
1447+
# Set of module labels in the initial partition
14201448
P = set(range(len(partition)))
14211449

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

brainx/recarrutil.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ def op(a1, a2, out=None):
131131
# For methods in the array interface that take an axis argument, the pattern is
132132
# always the same: extrude, operate, intrude. So we just auto-generate these
133133
# functions here.
134-
binops = [('add',np.add), ('subtract',np.subtract), ('multiply',np.multiply), ('divide',np.divide) ]
134+
binops = [('add', np.add), ('subtract', np.subtract),
135+
('multiply', np.multiply), ('divide', np.divide) ]
135136
#binops = [('add',np.add), np.subtract, np.multiply, np.divide ]
136137
for name, func in binops:
137138
exec "%s = binop_factory(func)" % name
@@ -176,18 +177,19 @@ def test_reductions():
176177
ymeth = getattr(y, fname)
177178
for axis in [None,0,1,-1,-2]:
178179
zred = reduction(z,axis)
179-
yield(nt.assert_equal, zred.x, xmeth(axis))
180-
yield(nt.assert_equal, zred.y, ymeth(axis))
180+
nt.assert_equal(zred.x, xmeth(axis))
181+
nt.assert_equal(zred.y, ymeth(axis))
181182

182183

183184
def test_binops():
184185
x, y, z, w = mk_xyzw()
186+
binop_names = [n for (n, op) in binops]
185187
for fname in binop_names:
186188
op = eval(fname)
187189
npop = getattr(np, fname)
188190
opres = op(z,w)
189-
yield(nt.assert_equal, opres.x, npop(z.x, w.x) )
190-
yield(nt.assert_equal, opres.y, npop(z.y, w.y) )
191+
nt.assert_equal(opres.x, npop(z.x, w.x))
192+
nt.assert_equal(opres.y, npop(z.y, w.y))
191193

192194
# Test support utilities
193195

0 commit comments

Comments
 (0)