def test_diff_move(): intraslice = ig.Graph.Read_Ncol("multilayer_SBM_interslice_edges.csv", directed=False) n = intraslice.vcount() layer_vec = [0] * n membership = list(range(n)) part_rbc = louvain.RBConfigurationVertexPartition( intraslice, resolution_parameter=1.0, initial_membership=membership) part_weighted_layers = louvain.RBConfigurationVertexPartitionWeightedLayers( intraslice, resolution_parameter=1.0, layer_vec=layer_vec, initial_membership=membership) # check diff_move() - quality() consistency across 100 random moves for repeat in range(100): v = randint(0, n - 1) c = randint(0, n - 1) old_quality = part_weighted_layers.quality() wl_diff = part_weighted_layers.diff_move(v, c) part_weighted_layers.move_node(v, c) true_diff = part_weighted_layers.quality() - old_quality rbc_diff = part_rbc.diff_move(v, c) part_rbc.move_node(v, c) assert isclose( wl_diff, true_diff ), "WeightedLayers diff_move() inconsistent with quality()" assert isclose( wl_diff, rbc_diff ), "WeightedLayers diff_move() inconsistent with single-layer" assert isclose(part_weighted_layers.quality(), part_rbc.quality( )), "WeightedLayers quality() inconsistent with single-layer" # check rng consistency between RBConfigurationVertexPartition and its WeightedLayers variant # with various seeds and intraslice resolution parameters for gamma in np.linspace(0.5, 1.5, 10): shared_seed = randint(-1 << 31, (1 << 31) - 1) # random int32 louvain.set_rng_seed(shared_seed) part_weighted_layers = louvain.RBConfigurationVertexPartitionWeightedLayers( intraslice, resolution_parameter=gamma, layer_vec=layer_vec) opt = louvain.Optimiser() opt.optimise_partition(partition=part_weighted_layers) louvain.set_rng_seed(shared_seed) part_rbc = louvain.RBConfigurationVertexPartition( intraslice, resolution_parameter=gamma) opt = louvain.Optimiser() opt.optimise_partition(partition=part_rbc) quality_weighted_layers = part_weighted_layers.quality( resolution_parameter=gamma) quality_rbc = part_rbc.quality(resolution_parameter=gamma) assert isclose( quality_weighted_layers, quality_rbc ), "Intra-layer optimisation inconsistent with single-layer"
def multilayer_louvain(G_intralayer, G_interlayer, layer_vec, gamma, omega, optimiser=None, return_partition=False): # RBConfigurationVertexPartitionWeightedLayers implements a multilayer version of "standard" modularity (i.e. # the Reichardt and Bornholdt's Potts model with configuration null model). check_multilayer_louvain_capabilities() if 'weight' not in G_intralayer.es: G_intralayer.es['weight'] = [1.0] * G_intralayer.ecount() if 'weight' not in G_interlayer.es: G_interlayer.es['weight'] = [1.0] * G_interlayer.ecount() if optimiser is None: optimiser = louvain.Optimiser() intralayer_part = louvain.RBConfigurationVertexPartitionWeightedLayers( G_intralayer, layer_vec=layer_vec, weights='weight', resolution_parameter=gamma) interlayer_part = louvain.CPMVertexPartition(G_interlayer, resolution_parameter=0.0, weights='weight') optimiser.optimise_partition_multiplex([intralayer_part, interlayer_part], layer_weights=[1, omega]) if return_partition: return intralayer_part else: return tuple(intralayer_part.membership)
def multilayer_louvain(G_intralayer, G_interlayer, layer_vec, gamma, omega, optimiser=None, return_partition=False): r"""Run the Louvain modularity maximization algorithm at a single (:math:`\gamma, \omega`) value. :param G_intralayer: intralayer graph of interest :type G_intralayer: igraph.Graph :param G_interlayer: interlayer graph of interest :type G_interlayer: igraph.Graph :param layer_vec: list of each vertex's layer membership :type layer_vec: list[int] :param gamma: gamma (intralayer resolution parameter) to run Louvain at :type gamma: float :param omega: omega (interlayer resolution parameter) to run Louvain at :type omega: float :param optimiser: if not None, use passed-in (potentially custom) louvain optimiser :type optimiser: louvain.Optimiser :param return_partition: if True, return a louvain partition. Otherwise, return a community membership tuple :type return_partition: bool :return: partition from louvain :rtype: tuple[int] or louvain.RBConfigurationVertexPartitionWeightedLayers """ # RBConfigurationVertexPartitionWeightedLayers implements a multilayer version of "standard" modularity (i.e. # the Reichardt and Bornholdt's Potts model with configuration null model). check_multilayer_louvain_capabilities() if 'weight' not in G_intralayer.es: G_intralayer.es['weight'] = [1.0] * G_intralayer.ecount() if 'weight' not in G_interlayer.es: G_interlayer.es['weight'] = [1.0] * G_interlayer.ecount() if optimiser is None: optimiser = louvain.Optimiser() intralayer_part = louvain.RBConfigurationVertexPartitionWeightedLayers( G_intralayer, layer_vec=layer_vec, weights='weight', resolution_parameter=gamma) interlayer_part = louvain.CPMVertexPartition(G_interlayer, resolution_parameter=0.0, weights='weight') optimiser.optimise_partition_multiplex([intralayer_part, interlayer_part], layer_weights=[1, omega]) if return_partition: return intralayer_part else: return tuple(intralayer_part.membership)
def maximize_modularity(intralayer_resolution, interlayer_resolution): # RBConfigurationVertexPartitionWeightedLayers implements a multilayer version of "standard" modularity (i.e. # the Reichardt and Bornholdt's Potts model with configuration null model). G_interlayer.es['weight'] = interlayer_resolution intralayer_part = \ louvain.RBConfigurationVertexPartitionWeightedLayers(G_intralayer, layer_vec=layer_vec, weights='weight', resolution_parameter=intralayer_resolution) interlayer_part = louvain.CPMVertexPartition(G_interlayer, resolution_parameter=0.0, weights='weight') optimiser.optimise_partition_multiplex( [intralayer_part, interlayer_part]) return intralayer_part
def multilayer_louvain_part(G_intralayer, G_interlayer, layer_membership): if 'weight' not in G_intralayer.es: G_intralayer.es['weight'] = [1.0] * G_intralayer.ecount() if 'weight' not in G_interlayer.es: G_interlayer.es['weight'] = [1.0] * G_interlayer.ecount() intralayer_part = louvain.RBConfigurationVertexPartitionWeightedLayers( G_intralayer, layer_vec=layer_membership, weights='weight') interlayer_part = louvain.CPMVertexPartition(G_interlayer, resolution_parameter=0.0, weights='weight') return intralayer_part, interlayer_part
def plot_manual_CHAMP(G_intralayer, G_interlayer, layer_vec, partitions): """Run an inefficient method to plot optimal modularity partititions across the (gamma, omega) plane to check consistency of the CHAMP implementation""" if 'weight' not in G_intralayer.es: G_intralayer.es['weight'] = [1.0] * G_intralayer.ecount() if 'weight' not in G_interlayer.es: G_interlayer.es['weight'] = [1.0] * G_interlayer.ecount() def part_color(membership): membership_val = hash(sorted_tuple(membership)) return tuple((membership_val / x) % 1.0 for x in [157244317, 183849443, 137530733]) denser_gammas = np.linspace(0, GAMMA_END, 250) denser_omegas = np.linspace(0, OMEGA_END, 250) intralayer_part = louvain.RBConfigurationVertexPartitionWeightedLayers( G_intralayer, layer_vec=layer_vec, weights='weight') G_interlayer.es['weight'] = [1.0] * G_interlayer.ecount() interlayer_part = louvain.CPMVertexPartition(G_interlayer, resolution_parameter=0.0, weights='weight') # best_partitions = List(quality, partition, gamma, omega) best_partitions = [[(-inf, ) * 4] * len(denser_omegas) for _ in range(len(denser_gammas))] for p in partitions: intralayer_part.set_membership(p) interlayer_part.set_membership(p) interlayer_base_quality = interlayer_part.quality( ) # interlayer quality at omega=1.0 for g_index, gamma in enumerate(denser_gammas): intralayer_quality = intralayer_part.quality( resolution_parameter=gamma) for o_index, omega in enumerate(denser_omegas): # omega * interlayer.quality() matches CPMVertexPartition.quality() # with omega as interlayer edge weights (as of 7/2) Q = intralayer_quality + omega * interlayer_base_quality if Q > best_partitions[g_index][o_index][0]: best_partitions[g_index][o_index] = (Q, p, gamma, omega) gammas, omegas, colors = zip(*[(x[2], x[3], part_color(x[1])) for row in best_partitions for x in row]) plt.scatter(gammas, omegas, color=colors, s=1, marker='s') plt.xlabel("gamma") plt.ylabel("omega")
def test_multilayer_louvain(): intraslice = ig.Graph.Read_Ncol("multilayer_SBM_intraslice_edges.csv", directed=False) interslice = ig.Graph.Read_Ncol("multilayer_SBM_interslice_edges.csv", directed=False) n_layers = 4 n = intraslice.vcount() // n_layers layer_vec = np.array([i // n for i in range(n * n_layers)]) intraslice.es['weight'] = 1.0 intralayer_part = louvain.RBConfigurationVertexPartitionWeightedLayers( intraslice, resolution_parameter=1.0, layer_vec=layer_vec, weights='weight') for omega in np.linspace(0.5, 1.5, 10): interslice.es['weight'] = omega interlayer_part = louvain.RBConfigurationVertexPartition( interslice, resolution_parameter=0.0, weights='weight') opt = louvain.Optimiser() opt.optimise_partition_multiplex( partitions=[intralayer_part, interlayer_part]) louvain_mod = intralayer_part.quality( resolution_parameter=1.0) + interlayer_part.quality() A = np.array(intraslice.get_adjacency()._get_data()) C = omega * np.array(interslice.get_adjacency()._get_data()) P = np.zeros((n_layers * n, n_layers * n)) for i in range(n_layers): c_degrees = np.array( intraslice.degree(list(range(n * i, n * i + n)))) c_inds = np.where(layer_vec == i)[0] P[np.ix_(c_inds, c_inds)] = np.outer( c_degrees, c_degrees.T) / (1.0 * np.sum(c_degrees)) membership = np.array(intralayer_part.membership) true_mod = sum( calculate_coefficient(membership, X) for X in (A, -P, C)) assert isclose( louvain_mod, true_mod ), "WeightedLayers quality() inconsistent with alternate calculation"
def run_louvain_multilayer(intralayer_graph, interlayer_graph, layer_vec, weight='weight', resolution=1.0, omega=1.0, nruns=1): logging.debug('Shuffling node ids') t = time() mu = np.sum(intralayer_graph.es[weight]) + interlayer_graph.ecount() use_RBCweighted = hasattr(louvain, 'RBConfigurationVertexPartitionWeightedLayers') outparts = [] for run in range(nruns): rand_perm = list(np.random.permutation(interlayer_graph.vcount())) # rand_perm = list(range(interlayer_graph.vcount())) rperm = rev_perm(rand_perm) interslice_layer_rand = interlayer_graph.permute_vertices(rand_perm) rlayer_vec = permute_vector(rand_perm, layer_vec) rintralayer_graph = intralayer_graph.permute_vertices(rand_perm) # if use_RBCweighted: rlayers = [ intralayer_graph ] # one layer representing all intralayer connections here else: rlayers = _create_multilayer_igraphs_from_super_adj_igraph( rintralayer_graph, layer_vec=rlayer_vec) logging.debug('time: {:.4f}'.format(time() - t)) t = time() #create the partition objects layer_partition_objs = [] logging.debug('creating partition objects') t = time() for i, layer in enumerate( rlayers): #these are the shuffled igraph slice objects try: res = resolution[i] except: res = resolution if use_RBCweighted: cpart = louvain.RBConfigurationVertexPartitionWeightedLayers( layer, layer_vec=rlayer_vec, weights=weight, resolution_parameter=res) else: #This creates individual VertexPartition for each layer. Much slower to optimize. cpart = louvain.RBConfigurationVertexPartition( layer, weights=weight, resolution_parameter=res) layer_partition_objs.append(cpart) coupling_partition = louvain.RBConfigurationVertexPartition( interslice_layer_rand, weights=weight, resolution_parameter=0) all_layer_partobjs = layer_partition_objs + [coupling_partition] optimiser = louvain.Optimiser() logging.debug('time: {:.4f}'.format(time() - t)) logging.debug('running optimiser') t = time() layer_weights = [1] * len(rlayers) + [omega] improvement = optimiser.optimise_partition_multiplex( all_layer_partobjs, layer_weights=layer_weights) #the membership for each of the partitions is tied together. finalpartition = permute_vector(rperm, all_layer_partobjs[0].membership) reversed_partobj = [] #go back and reverse the graphs associated with each of the partobj. this allows for properly calculating exp edges with partobj #This is not ideal. Could we just reverse the permutation? for layer in layer_partition_objs: if use_RBCweighted: reversed_partobj.append( louvain.RBConfigurationVertexPartitionWeightedLayers( graph=layer.graph.permute_vertices(rperm), initial_membership=finalpartition, weights=weight, layer_vec=layer_vec, resolution_parameter=layer.resolution_parameter)) else: reversed_partobj.append( louvain.RBConfigurationVertexPartition( graph=layer.graph.permute_vertices(rperm), initial_membership=finalpartition, weights=weight, resolution_parameter=layer.resolution_parameter)) coupling_partition_rev = louvain.RBConfigurationVertexPartition( graph=coupling_partition.graph.permute_vertices(rperm), initial_membership=finalpartition, weights=weight, resolution_parameter=0) #use only the intralayer part objs A = _get_sum_internal_edges_from_partobj_list(reversed_partobj, weight=weight) if use_RBCweighted: #should only one partobj here representing all layers P = get_expected_edges_ml(reversed_partobj[0], layer_vec=layer_vec, weight=weight) else: P = _get_sum_expected_edges_from_partobj_list(reversed_partobj, weight=weight) C = get_sum_internal_edges(coupling_partition_rev, weight=weight) outparts.append({'partition': np.array(finalpartition), 'resolution': resolution, 'coupling':omega, 'orig_mod': (.5/mu)*(_get_modularity_from_partobj_list(reversed_partobj)\ +omega*coupling_partition_rev.quality()), 'int_edges': A, 'exp_edges': P, 'int_inter_edges':C}) logging.debug('time: {:.4f}'.format(time() - t)) return outparts