def threshold_multilayer_network(M, intralayer_density=0.05, interlayer_density=0.05, replace_interlayer_weights_with_ones=True): layers = sorted(list(M.get_layers())) assert (layers == list(range(min(layers), max(layers) + 1))) #pairwise_layersets = zip(*(range(len(layers))[ii:] for ii in range(2))) # threshold first layer in network: first_layer = layers[0] ordered_edges = sorted(pn.subnet(M, M.iter_nodes(), [first_layer]).edges, key=lambda w: w[4], reverse=True) assert (all([w[4] is not np.nan for w in ordered_edges])) max_edges = (len(list(M.iter_nodes(first_layer))) * (len(list(M.iter_nodes(first_layer))) - 1)) / 2.0 thresholded_edges = ordered_edges[ 0:int(np.floor(max_edges * intralayer_density))] for edge in ordered_edges: if edge in thresholded_edges: M[edge[0], edge[1], edge[2], edge[3]] = 1 else: M[edge[0], edge[1], edge[2], edge[3]] = 0 # threshold subsequent layers and intralayer networks between current layer and the previous one del (layers[0]) for layer in layers: ordered_edges = sorted(pn.subnet(M, M.iter_nodes(), [layer]).edges, key=lambda w: w[4], reverse=True) assert (all([w[4] is not np.nan for w in ordered_edges])) max_edges = (len(list(M.iter_nodes(layer))) * (len(list(M.iter_nodes(layer))) - 1)) / 2.0 thresholded_edges = ordered_edges[ 0:int(np.floor(max_edges * intralayer_density))] for edge in ordered_edges: if edge in thresholded_edges: M[edge[0], edge[1], edge[2], edge[3]] = 1 else: M[edge[0], edge[1], edge[2], edge[3]] = 0 interlayer_ordered_edges = sorted([ edge for edge in pn.subnet(M, M.iter_nodes(), [layer, layer - 1]).edges if edge[2] != edge[3] ], key=lambda w: w[4], reverse=True) interlayer_max_edges = len(list(M.iter_nodes(layer))) * len( list(M.iter_nodes(layer - 1))) interlayer_thresholded_edges = interlayer_ordered_edges[ 0:int(np.floor(interlayer_max_edges * interlayer_density))] for edge in interlayer_ordered_edges: if edge in interlayer_thresholded_edges: if replace_interlayer_weights_with_ones: M[edge[0], edge[1], edge[2], edge[3]] = 1 else: M[edge[0], edge[1], edge[2], edge[3]] = 0 return M
def subtrahend(orbit1, orbit2, nets, auts, invs, allowed_aspects='all'): ''' Returns the subtrahend for orbit2 in orbit equations (the value that is substracted in the upper part of the binomial coefficient on theleft sides of the orbit equation) Parameters ---------- orbit1, orbit2 : tuple (n_nodes, net_index, node_orbit_index) These can extract from the output of orbit_equations, n_nodex, net_index, and node_orbit_index should match those of parameter auts nets : dict (key: n_nodes, value: list of networks) graphlets, as produced by graphlets auts : dd (key: (n_nodes, net_index, node), value: node_orbit_index) as produced by automorphism_orbits Returns ------- sub_max : int Notes ----- assumes orbit2 has at most the same number of nodes as orbit1 ''' sub_max = 0 n_nodes1 = orbit1[0] n_nodes2 = orbit2[0] net1 = nets[n_nodes1][orbit1[1]] net2 = nets[n_nodes2][orbit2[1]] the_node = orbit1[2] layers = net1.slices[1] nodes = net1.slices[0] - set([the_node]) for partition in partitions_with_remainder(nodes, n_nodes2 - 1): sub = 0 for nodes_s in partition: nodes_g = set(nodes_s) | set([the_node]) sub_net = pymnet.subnet(net1, nodes_g, layers) ci_sub = str( pymnet.get_complete_invariant(sub_net, allowed_aspects=allowed_aspects)) if ci_sub in invs and invs[ci_sub] == (n_nodes2, orbit2[1]): iso = pymnet.get_isomorphism(sub_net, net2, allowed_aspects=allowed_aspects) if the_node in iso[0]: iso_node = iso[0][the_node] else: iso_node = the_node if auts[n_nodes2, orbit2[1], iso_node] == orbit2[2]: sub += 1 sub_max = max(sub_max, sub) return sub_max
def add_possible_edges(both_orbit_nodes, net): ''' Adds all possible edge combinations between nodes from different orbits Parameters ---------- both_orbit_nodes : list of nodes nodes that belong to both orbits net : network Returns ------- nets : list of networks Notes ----- assumes negative nodes belong to the same orbit returned networks can be isomorphic ''' nodes = net.slices[0] - set(both_orbit_nodes) nodes1 = [] nodes2 = [] for node in nodes: if node < 0: nodes2.append(node) else: nodes1.append(node) edges = list(itertools.product(nodes1, nodes2)) n_edges = len(edges) net_nodes = net.slices[0] net_layers = net.slices[1] layer_combs = layer_combinations(net_layers) nets = [] for n_e in range(1, n_edges + 1): for edge_comb in itertools.combinations(edges, n_e): edge_layers = [layer_combs] * n_e for edge_layer_comb in itertools.product(*edge_layers): new_net = pymnet.subnet(net, net_nodes, net_layers) for edge_i in range(n_e): edge = edge_comb[edge_i] for layer in edge_layer_comb[edge_i]: new_net[edge[0], edge[1], layer] = 1 nets.append(new_net) return nets
def save_subnet_with_complete_invariant(M, nodes, layers, writefile, examples_dict, allowed_aspects): if allowed_aspects != 'all': # relabel layers to 0...k sorted_layers = tuple(sorted(layers)) relabeling = dict() for ii, orig_layer in enumerate(sorted_layers): relabeling[orig_layer] = ii subnet = pn.transforms.relabel(pn.subnet(M, nodes, layers), layerNames=relabeling) complete_invariant = pn.get_complete_invariant( subnet, allowed_aspects=allowed_aspects) else: subnet = pn.subnet(M, nodes, layers) complete_invariant = pn.get_complete_invariant( subnet, allowed_aspects=allowed_aspects) writefile.write( str(nodes) + '\t' + str(layers) + '\t' + str(complete_invariant) + '\n') if isinstance(examples_dict, dict): if complete_invariant not in examples_dict: examples_dict[complete_invariant] = subnet return
def orbit_counts(n, node0, net, nets, orbits, auts, invs, orbit_list, allowed_aspects='all'): ''' Computes the orbit counts for node0 in net Parameters ---------- node0 : node net : network nets : dict (key: n_nodes, value: list of networks) graphlets orbits : dd (key: (node, orbit), value: count) dictionary where the counts will be stored auts : dd (key: (n_nodes, net_index, node), value: node) automorphism orbits invs : dict (key: str(complete invariant), value: tuple(n_nodes, net_index in nets)) complete invariants of the graphlets orbit_list : list of orbits allowed_aspects : list, string the aspects that can be permutated when computing isomorphisms ''' for orbit in orbit_list: orbits[node0, orbit] = 0 layers = net.slices[1] node_sets = touching_orbit_nodes(node0, net, n) for nodes_s in node_sets: sub_net = pymnet.subnet(net, nodes_s, layers) ci_sub = str( pymnet.get_complete_invariant(sub_net, allowed_aspects=allowed_aspects)) i = invs[ci_sub][1] n_nodes = invs[ci_sub][0] nw = nets[n_nodes][i] iso = pymnet.get_isomorphism(sub_net, nw, allowed_aspects=allowed_aspects) if node0 in iso[0]: orbits[node0, (n_nodes, i, auts[n_nodes, i, iso[0][node0]])] += 1 else: orbits[node0, (n_nodes, i, auts[n_nodes, i, node0])] += 1
def add_subnet_to_aggregated_dict(M, nodes, layers, aggregated_dict, examples_dict, allowed_aspects): # modifies aggregated_dict directly sorted_layers = tuple(sorted(layers)) if allowed_aspects != 'all': # relabel layers to 0...k relabeling = dict() for ii, orig_layer in enumerate(sorted_layers): relabeling[orig_layer] = ii subnet = pn.transforms.relabel(pn.subnet(M, nodes, layers), layerNames=relabeling) complete_invariant = pn.get_complete_invariant( subnet, allowed_aspects=allowed_aspects) else: subnet = pn.subnet(M, nodes, layers) complete_invariant = pn.get_complete_invariant( subnet, allowed_aspects=allowed_aspects) aggregated_dict[complete_invariant][ sorted_layers] = aggregated_dict[complete_invariant].get( sorted_layers, 0) + 1 if isinstance(examples_dict, dict): if complete_invariant not in examples_dict: examples_dict[complete_invariant] = subnet return
def default_check_reqs(network, nodelist, layerlist, sizes, intersections, nnodes=None, nlayers=None, intersection_type="strict"): u"""Checks whether a multilayer induced subgraph of the form [nodelist][layerlist] is connected, whether it has no empty layers or nodes, and whether it fulfills the given sizes and intersections requirements. Works on one-aspect multilayer networks. Parameters ---------- network : MultilayerNetwork A one-aspect multilayer network containing the induced subgraph. nodelist : list of node names The nodes in the induced subgraph. layerlist : list of layer names The layers in the induced subgraph. sizes : list of ints > 0 How many nodes should be on each layer of an acceptable induced subgraph. One integer for each layer of an acceptable subgraph. intersections : list of ints >= 0 or Nones How many nodes should be shared between sets of layers in an acceptable induced subgraph. If an entry in the list is None, any number of shared nodes is accepted. The order of the intersections is taken to follow the order of layers in sizes, with two-layer intersections being listed first, then three-layer intersections, etc. For more details, see section "Constructing the requirements". nnodes : int How many nodes an acceptable subgraph should have. If not provided, it will be calculated based on the sizes and intersections parameters. Required if there are Nones in intersections or if intersection_type is not "strict". If you cannot guarantee the correctness of this number, do not use this parameter. Can be used in scripts to bypass calculating the correct number of nodes based on the sizes and intersections parameters. nlayers : int How many layers an acceptable subgraph should have. If not provided, it will be calculated based on the sizes and intersections parameters. Required if there are Nones in intersections. If you cannot guarantee the correctness of this number, do not use this parameter. Can be used in scripts to bypass calculating the correct number of layers based on the sizes and intersections parameters. intersection_type : string, "strict" or "less_or_equal" If intersection_type is "strict", all intersections must be exactly equal to entries in the intersections parameter. If intersection_type is "less_or_equal", an intersection is allowed to be less than or equal to the corresponding entry in the intersections parameter. Usage is case-sensitive. Returns ------- True if the requirements are fulfilled, the induced subgraph has no empty nodes or layers, and the induced subgraph is connected. False otherwise. Empty nodes or layers --------------------- The phrase 'does not contain any empty layers or nodes' means that for each layer, there is at least one nodelayer in the induced subgraph, and that for each node, there is at least one nodelayer in the induced subgraph. In other words, each node in the nodelist and each layer in the layerlist appears at least once as the node identity or the layer identity, respectively, among the nodelayers present in the induced subgraph. Constructing the requirements ----------------------------- The sizes list contains the desired number of nodes on each layer in any order. This means that the layers in the actual found sub-network can be in any order. However, the order of entries in sizes determines the order of entries in intersections. The intersections list is constructed as follows: First, think of each size in the size list as having a specific role: the first entry in sizes corresponds to layer role A, the second to role B, the third to role C, and so on. This order determines how intersections in the intersections list are interpreted when checking if the requirements are fulfilled. Then, construct the intersections list so that first all size-two intersections are listed, then size-three intersections, and so on, until the intersection between all layers is reached. The entry for each intersection can be an integer or None. An integer signifies the number of nodes in the intersection (the cardinality of the intersection set), and it can be followed strictly or less strictly, depending on the intersection_type parameter. The value None signifies that the intersection in question can have any size in an acceptable induced subgraph. If intersections contains Nones, the nnodes and nlayers parameters must also be specified. The order of listing size-n intersections is such that the closer the role is to the beginning of the size list, the later it is iterated over. More specifically, this is the order that itertools.combinations() uses to iterate. Since we signify the roles by letters A, B, C and so on, this means that the intersections are listed in "alphabetical order" within each size category. For example, suppose we have a length-four sizes list. Now, we think of the first size entry as layer (role) A, the second as layer B, the third as layer C, and the fourth as layer D. The intersections list is then assumed to contain the intersections in the following order: intersections = [A∩B, A∩C, A∩D, B∩C, B∩D, C∩D, A∩B∩C, A∩B∩D, A∩C∩D, B∩C∩D, A∩B∩C∩D] When checking whether the size and intersection requirements are fulfilled, each possible set of role assginments to the actual layers is checked. If even one is suitable, the function returns True. Continuing from the example above, if the actual induced subgraph has layers [X,Y,Z,W], all possible role assignment combinations are checked (X takes role from the set {A,B,C,D}, Y takes role from {A,B,C,D} minus {role(X)}, Z takes role from {A,B,C,D} minus {role(X),role(Y)}, W takes role from {A,B,C,D} minus {role(X),role(Y),role(Z)}). The role assignment is iterated until an acceptable role assignment is found -- at which point the function returns True -- or until all possible role assignments have been considered without success -- at which point the function returns False. This also means that the orderings of the [nodelist] and [layerlist] of the induced subgraph to be tested do not matter (i.e calling this function with nodelist = [1,2] and layerlist = ['X','Y'] gives the exact same return value as nodelist = [2,1] and layerlist = ['Y','X'], etc.). Using Nones ----------- If we only care about the cardinalities of some specific intersections, we can set the rest to None. For example, calling >>> default_check_reqs(some_network,some_nodelist,some_layerlist,[1,2,3],[None,None,2,None],nnodes=4,nlayers=3) means that the algorithm will find the induced subgraphs which are connected, have 4 nodes and 3 layers, have no empty layers or nodes, have one node on one layer, two nodes on another layer, three nodes on the third layer, and have a cardinality-2 (size-2) intersection between the layer that has two nodes and the layer that has three nodes (with no constraints on the cardinalities of the other intersections). When using Nones, nnodes and nlayers have to be specified, since if all intersection cardinalities are not unambiguous, the nnodes and nlayers cannot be calculated based on the sizes and intersections alone. It is up to the user to provide nnodes and nlayers that are sensible (for example, nnodes cannot sensibly be more than the sum of all sizes). It is also up to the user to not give contradictory requirements. Technically, only nnodes would be required, but both have to be given to make the function call more explicit and more intuitive to read. Example ------- Suppose we have the multilayer network N: (1,'X')----(2,'X') (3,'X') | | (2,'Y') where (a,b) are nodelayer tuples with a = node identity and b = layer identity. Now, >>> default_check_reqs(N,[1,2],['X','Y'],[1,2],[1]) returns True, as do >>> default_check_reqs(N,[2,1],['Y','X'],[1,2],[1]) and >>> default_check_reqs(N,[1,2],['Y','X'],[2,1],[1]) (and naturally so do also all the other ways of defining the same induced subgraph and the same requirements). On the other hand, >>> default_check_reqs(N,[2,3],['X','Y'],[1,2],[1]) returns False, since the induced subgraph is not connected. """ if intersection_type == "strict": if nnodes != None and nlayers != None: req_nodelist_len = nnodes req_layerlist_len = nlayers else: if None in intersections: raise TypeError( "Please provide nnodes and nlayers when including Nones in intersections" ) try: req_nodelist_len, req_layerlist_len = default_calculate_required_lengths( sizes, intersections) except AssertionError: raise elif intersection_type == "less_or_equal": assert nnodes != None, "Please provide nnodes when using less_or_equal intersection type" if nlayers != None: req_nodelist_len = nnodes req_layerlist_len = nlayers else: if None in intersections: raise TypeError( "Please provide nnodes and nlayers when including Nones in intersections" ) req_nodelist_len = nnodes try: _, req_layerlist_len = default_calculate_required_lengths( sizes, intersections) except AssertionError: raise else: raise TypeError( "Please specify either strict or less_or_equal as intersection type" ) assert len(nodelist) == req_nodelist_len, "Wrong number of nodes" assert len(layerlist) == req_layerlist_len, "Wrong number of layers" assert all(i >= 1 for i in sizes), "Inappropriate sizes" induced_graph = pymnet.subnet(network, nodelist, layerlist) try: graph_is_connected = nx.is_connected( pymnet.transforms.get_underlying_graph(induced_graph)) except nx.networkx.NetworkXPointlessConcept: return False if graph_is_connected: # check for empty nodes or layers nls = set(induced_graph.iter_node_layers()) for layer in layerlist: no_nodelayers = True for node in nodelist: if (node, layer) in nls: no_nodelayers = False break if no_nodelayers: return False for node in nodelist: no_nodelayers = True for layer in layerlist: if (node, layer) in nls: no_nodelayers = False break if no_nodelayers: return False d = dict() # keys: layers, values: nodes for nodelayer in nls: d.setdefault(nodelayer[1], []) d[nodelayer[1]].append(nodelayer[0]) if len(d) != req_layerlist_len: return False d_isect = dict() # layer intersections, roles 0,1,2,... indexer = 0 for jj in range(2, len(layerlist) + 1): for combination in list( itertools.combinations(list(range(0, len(layerlist))), jj)): d_isect[combination] = intersections[indexer] indexer = indexer + 1 for permutation in list(itertools.permutations(layerlist)): # index in permutation determines role of layer goto_next_perm = False # check all intersections and sizes for ii in range(1, len(layerlist) + 1): # A, B etc. AB, AC etc. for combination in list(itertools.combinations( permutation, ii)): # try all role combinations assert len( combination ) >= 1, "Empty combination list, this shouldn't happen" if len(combination) == 1: # check sizes if len(d[combination[0]]) != sizes[permutation.index( combination[0])]: goto_next_perm = True break elif len(combination) > 1: rolelist = [] nodeset = set(d[combination[0]]) for layer in combination: rolelist.append(permutation.index(layer)) nodeset = nodeset & set(d[layer]) rolelist.sort() if intersection_type == "strict": if d_isect[tuple(rolelist)] != None and len( nodeset) != d_isect[tuple(rolelist)]: goto_next_perm = True break elif intersection_type == "less_or_equal": if d_isect[tuple(rolelist)] != None and len( nodeset) > d_isect[tuple(rolelist)]: goto_next_perm = True break if goto_next_perm: break if not goto_next_perm: return True return False
def relaxed_check_reqs(network, nodelist, layerlist): """Checks whether a multilayer induced subgraph of the form [nodelist][layerlist] is connected and does not contain any empty layers or nodes. Works on one-aspect multilayer networks. See section Details for more information. Parameters ---------- network : MultilayerNetwork A one-aspect multilayer network containing the induced subgraph. nodelist : list of node names The nodes in the induced subgraph. layerlist : list of layer names The layers in the induced subgraph. Returns ------- True if the induced subgraph is connected and does not contain empty layers or nodes, False otherwise. Details ------- The phrase 'does not contain any empty layers or nodes' means that for each layer, there is at least one nodelayer in the induced subgraph, and that for each node, there is at least one nodelayer in the induced subgraph. In other words, each node in the nodelist and each layer in the layerlist appears at least once as the node identity or the layer identity, respectively, among the nodelayers present in the induced subgraph. Example ------- Suppose we have the multilayer network N: (1,'X')----(2,'X') | | (2,'Y') Calling >>> relaxed_check_reqs(N,[1,2],['X','Y']) returns True, but calling >>> relaxed_check_reqs(N,[1,2],['Y']) returns False, because node 1 is empty. """ induced_graph = pymnet.subnet(network, nodelist, layerlist) try: graph_is_connected = nx.is_connected( pymnet.transforms.get_underlying_graph(induced_graph)) except nx.networkx.NetworkXPointlessConcept: return False if graph_is_connected: nls = set(induced_graph.iter_node_layers()) for layer in layerlist: no_nodelayers = True for node in nodelist: if (node, layer) in nls: no_nodelayers = False break if no_nodelayers: return False for node in nodelist: no_nodelayers = True for layer in layerlist: if (node, layer) in nls: no_nodelayers = False break if no_nodelayers: return False return True return False
def sample_multilayer_subgraphs_esu(network,results,sizes=None,intersections=None,nnodes=None,nlayers=None,p=None,seed=None,intersection_type="strict",copy_network=True,custom_check_function=None): u"""A one-aspect multilayer version of the Rand-EnumerateSubgraphs (Rand-ESU) algorithm introduced by Wernicke [1]. Uniformly samples induced subgraphs of the form [nodelist][layerlist] which fulfill the given requirements. Each subgraph is sampled at probability product(p), where p is the parameter p. If all entries in p are 1, all such induced subgraphs in the network are found. Parameters ---------- Multiple parameters can be given by the user, some of which are always required, some of which are sometimes required, and some of which are mutually exclusive. There are multiple functionalities to this function, and the choice is done based on the parameters passed by the user. For a description of all of them, see section Usage. network : MultilayerNetwork The multilayer network to be analyzed. results : list or callable The method of outputting the found induced subgraphs. If a list, then the induced subgraphs are appended to it as ([nodelist],[layerlist]) tuples. The results list or the [nodelist] or [layerlist] lists are not guaranteed to be in any specific order. If a callable, when an acceptable induced subgraph is found, this callable is called with the argument ([nodelist],[layerlist]) (that is, one argument which is a tuple of two lists). The callable should therefore take only one required parameter in the form of a tuple. If you want to pass more parameters to the callable, do so via e.g. an anonymous function. sizes : list of ints > 0 How many nodes should be on each layer of an acceptable induced subgraph. One integer for each layer of an acceptable subgraph. intersections : list of ints >= 0 or Nones, or int How many nodes should be shared between sets of layers in an acceptable induced subgraph. If list, if an entry in the list is None, any number of shared nodes is accepted. The order of the intersections is taken to follow the order of layers in sizes, with two-layer intersections being listed first, then three-layer intersections, etc. If int, then this is taken to mean the intersection between ALL layers, and the other intersections can be anything. If this is an int with value x, it is equivalent to being a list with [None,None,...,None,x]. For more details, see section "Constructing the requirements" in the documentation of the function default_check_reqs. nnodes : int How many nodes an acceptable subgraph should have. If not provided and sizes and intersections are provided, it will be calculated based on the sizes and intersections parameters. Required if there are Nones in intersections or if intersection_type is not "strict". If you cannot guarantee the correctness of this number, do not use this parameter. nlayers : int How many layers an acceptable subgraph should have. If not provided and sizes and intersections are provided, it will be calculated based on the sizes and intersections requirements. If you cannot guarantee the correctness of this number, do not use this parameter. p : list of floats 0 <= p <= 1 List of sampling probabilities at each depth. If None, p = 1 for each depth is used. The probability of sampling a given induced subgraph is the product of the elements of p. It is up to the user to provide a p of correct length to match the depth at which desired induced subgraphs are found. If you know how many nodes and layers an acceptable induced subgraph should have (nnodes and nlayers, respectively), you can calculate the length of p by: len(p) = nnodes - 1 + nlayers - 1 + 1. This formula follows from the fact that finding an induced subgraph starts from a nodelayer (the + 1), and then each node and each layer have to be added one at a time to the nodelist and layerlist, respectively (nnodes - 1 and nlayers - 1, respectively). Starting from a nodelayer means that both nodelist and layerlist are of length 1 when the expansion of the subgraph is started, hence the - 1's. seed : int, str, bytes or bytearray Seed for Rand-ESU. intersection_type : string, "strict" or "less_or_equal" If intersection_type is "strict", all intersections must be exactly equal to entries in the intersections parameter. If intersection_type is "less_or_equal", an intersection is allowed to be less than or equal to the corresponding entry in the intersections parameter. Usage is case-sensitive. copy_network : boolean Determines whether the network is copied at the beginning of execution. If True (default), the network is copied and the copy is modified during the execution (the original network is not modified). If False, the network is not copied, and the network is NOT modified during the execution. The copying takes more memory but results in faster execution times - the default value (True) is the recommended setting. The modification of the copy does not affect the edges in the induced subgraphs that are passed to the check function. During the execution, if this parameter is True, as starting nodelayers are iterated through in their numerical order, after a nodelayer has been iterated over all edges leading to it are removed (at this point, it is impossible to reach the nodelayer from subsequent starting nodelayers in any case). Therefore, if you use a custom_check_function whose return value depends also on the edges OUTSIDE the induced subgraph to be tested, set this parameter to False. custom_check_function : callable or None If not None, this will be used to determine whether an induced subgraph is okay or not (instead of one of the built-in check functions). The algorithm finds induced subgraphs which have the given nnodes and nlayers, and which have a path spanning the induced subgraph (but are not necessarily connected). The algorithm then passes these to the check function, which determines whether the subgraph is acceptable or not. The arguments that are passed to your custom check function are the network, the nodelist of the induced subgraph, and the layerlist of the induced subgraph (three parameters, in this order). Your check function should therefore accept exactly three parameters. If you want to pass more parameters to the check function, do so via e.g. an anonymous function. If copy_network is True, the passed network is the copy of the network, which might have edges removed OUTSIDE of the induced subgraph (the edges inside the induced subgraph are identical to the original network's). The function should return True or False (the subgraph is acceptable or not acceptable, respectively). When this parameter is not None, you must also specify nnodes and nlayers. Usage ----- There are multiple functionalities built-in, and determining which is used is done by checking which parameters have been given by the user. If you want to find induced subgraphs (ISs) that have a specified number of nodes on each layer and have specific intersections between layers, provide: - network - results - sizes - intersections as list without Nones If you want to find ISs that have a specific number of nodes on each layer and have some specific intersections between layers, and some intersections that can be of any cardinality, provide: - network - results - sizes - intersections as list with Nones in the elements where intersection cardinalities can be anything (even all elements can be Nones) - nnodes If you want to find ISs that have a specific number of nodes on each layer and have intersections that have at most specific cardinalities, provide: - network - results - sizes - intersections as list without Nones - nnodes - intersection_type = "less_or_equal" If you want to find ISs that have a specific number of nodes on each layer and have some specific intersections that have at most specific cardinalities, and some intersections that can be of any cardinality, provide: - network - results - sizes - intersections as list with Nones in the elements where intersection cardinalities can be anything (even all intersections can be Nones) - nnodes - intersection_type = "less_or_equal" If you want to find ISs that have a specific number of nodes on each layer and have a specific intersection between ALL layers (the other intersections can be anything), provide: - network - results - sizes - nnodes - intersections as int If you want to find ISs that have a specific number of nodes and a specific number of layers, provide: - network - results - nnodes - nlayers If you want to define your own function to determine when an IS is acceptable, provide: - network - results - nnodes - nlayers - custom_check_function For all of the above uses, if you don't want to find all ISs but only sample a portion of them, also provide: - p Of the above uses, the first five use the default_check_reqs function for checking subgraph validity, the sixth uses the relaxed_check_reqs function, and the seventh uses the user-supplied checking function. Example ------- Suppose we have the multilayer network N: (1,'X')----(2,'X')----(3,'X') | | (2,'Y') where (a,b) are nodelayer tuples with a = node identity and b = layer identity. After calling >>> results = [] >>> sample_multilayer_subgraphs_esu(N,results,[2,1],[1]) the results list looks like [([1,2],['X','Y']),([2,3],['X','Y'])] (or some other order of tuples and [nodelist] and [layerlist] inside the tuples, since the output is not guaranteed to be in any specific order). After calling >>> results = [] >>> sample_multilayer_subgraphs_esu(N,results,nnodes=3,nlayers=1) the results list looks like [([1,2,3],['X'])] (or again, some other ordering). Further reading --------------- The documentation of the functions default_check_reqs, default_calculate_required_lengths and relaxed_check_reqs offer more insight into what are considered acceptable induced subgraphs in different cases in the functionalities described in the Usage section. You should read these if you are not sure what you want to do or how to do it after reading this documentation. References ---------- [1] "A Faster Algorithm for Detecting Network Motifs", S. Wernicke, WABI. Vol. 3692, pp. 165-177. Springer 2005. """ if copy_network == True: network_copy = pymnet.subnet(network,network.get_layers(aspect=0),network.get_layers(aspect=1),newNet=pymnet.MultilayerNetwork(aspects=1,fullyInterconnected=False)) else: network_copy = network if seed == None: random.seed() else: random.seed(seed) check_function = None assert (sizes != None and intersections != None) or (nnodes != None and nlayers != None), "Please provide either sizes and intersections or nnodes and nlayers" if custom_check_function != None: assert nnodes != None and nlayers != None, "Please provide nnodes and nlayers when using a custom check function" req_nodelist_len = nnodes req_layerlist_len = nlayers check_function = custom_check_function if sizes != None and intersections != None and check_function == None: if isinstance(intersections,list): if None in intersections: assert nnodes != None, "Please provide nnodes if including Nones in intersections" req_nodelist_len = nnodes req_layerlist_len = len(sizes) else: if intersection_type == "strict": assert nnodes == None and nlayers == None, "You cannot provide both sizes and intersections and nnodes and nlayers, if intersections is a list" req_nodelist_len, req_layerlist_len = default_calculate_required_lengths(sizes,intersections) elif intersection_type == "less_or_equal": assert nnodes != None and nlayers == None, "please provide nnodes (and not nlayers) if using less_or_equal intersection type" req_nodelist_len = nnodes req_layerlist_len = len(sizes) check_function = lambda x,y,z: default_check_reqs(x,y,z,sizes,intersections,req_nodelist_len,req_layerlist_len,intersection_type) elif isinstance(intersections,int): assert intersections >= 0, "Please provide nonnegative common intersection size" assert nnodes != None and nlayers == None, "When requiring only common intersection size, please provide nnodes (and not nlayers)" req_nodelist_len = nnodes req_layerlist_len = len(sizes) intersections_as_list = [None]*(2**len(sizes)-len(sizes)-1) intersections_as_list[-1] = intersections check_function = lambda x,y,z: default_check_reqs(x,y,z,sizes,intersections_as_list,req_nodelist_len,req_layerlist_len,intersection_type) if nnodes != None and nlayers != None and check_function == None: assert sizes == None and intersections == None, "You cannot provide both sizes and intersections and nnodes and nlayers, if intersections is a list" req_nodelist_len = nnodes req_layerlist_len = nlayers assert isinstance(req_nodelist_len,int) and isinstance(req_layerlist_len,int), "Non-integer nnodes or nlayers" assert req_nodelist_len > 0 and req_layerlist_len > 0, "Nonpositive nnodes or nlayers" check_function = relaxed_check_reqs assert check_function != None, "Please specify a valid combination of parameters to determine method of subgraph validity checking" if p == None: p = [1] * (req_nodelist_len-1 + req_layerlist_len-1 + 1) depth = 0 numberings = dict() inverse_numberings = dict() for index,nodelayer in enumerate(network_copy.iter_node_layers()): numberings[nodelayer] = index for nodelayer in numberings: inverse_numberings[numberings[nodelayer]] = nodelayer for indexnumber in range(len(numberings)): v = inverse_numberings[indexnumber] if random.random() < p[depth]: start_node = v[0] start_layer = v[1] V_extension_nodes = set() V_extension_layers = set() for neighbor in network_copy[v]: if numberings[neighbor] > numberings[v]: no_node_conflicts = True no_layer_conflicts = True node = neighbor[0] layer = neighbor[1] if (node,start_layer) in numberings and numberings[(node,start_layer)] < numberings[v]: no_node_conflicts = False if (start_node,layer) in numberings and numberings[(start_node,layer)] < numberings[v]: no_layer_conflicts = False if (node != start_node and no_node_conflicts and node not in V_extension_nodes): V_extension_nodes.add(node) if (layer != start_layer and no_layer_conflicts and layer not in V_extension_layers): V_extension_layers.add(layer) _extend_subgraph(network_copy,[start_node],[start_layer],check_function,V_extension_nodes,V_extension_layers,numberings,v,req_nodelist_len,req_layerlist_len,depth+1,p,results) if copy_network == True: for neighbor in list(network_copy[v]): network_copy[neighbor][v] = 0
def graphlets(n, layers, n_l=None, couplings=None, allowed_aspects="all"): ''' Generate graphlets up to n nodes Parameters ---------- n : int maximum number of nodes layers : list of layers n_l : int Number of layers in the generated graphlets, can be smaller than or equal to the number of elements in layers couplings : list, str, tuple, None, MultilayerNetwork Parameter determining how the layers are coupled, i.e. what inter-layer edges are present. allowed_aspects : list, string the aspects that can be permutated when computing isomorphisms Returns ------- nets : dict (key: n_nodes, value: list of MultiplexNetwork objects) graphlets invariants : dict (key: str(complete invariant), value: tuple(index in 'nets': n_nodes, index in the list of multiplex networks)) complete invariants of the graphlets, the values can be used to match the graphlets in 'nets' ''' if n_l == None: n_l = len(layers) nets = {} invariants = {} nets2 = [] for net_layers in itertools.combinations(layers, n_l): layer_combs = layer_combinations(net_layers) for layer_comb in layer_combs: net = pymnet.MultiplexNetwork(couplings=couplings, fullyInterconnected=True) for layer in layer_comb: net[0, 1, layer] = 1 for layer in net_layers: net.add_layer(layer) ci = pymnet.get_complete_invariant(net, allowed_aspects=allowed_aspects) ci_s = str(ci) if not ci_s in invariants: invariants[ci_s] = (2, len(nets2)) nets2.append(net) nets[2] = nets2 for i in range(2, n): nets_i = nets[i] nets_i_1 = [] for net in nets_i: net_nodes = net.slices[0] net_layers = list(net.slices[1]) net_layers.sort() layer_combs = layer_combinations(net_layers) for n_n in range(1, i + 1): for node_comb in itertools.combinations(range(i), n_n): node_layers = [layer_combs] * n_n for node_layer_comb in itertools.product(*node_layers): new_net = pymnet.subnet(net, net_nodes, net_layers) for node_i in range(n_n): node = node_comb[node_i] for layer in node_layer_comb[node_i]: new_net[node, i, layer] = 1 for layer in net_layers: new_net.add_layer(layer) # check if isomorphic with a previous graph & add only if not isomorphic ci = pymnet.get_complete_invariant( new_net, allowed_aspects=allowed_aspects) ci_s = str(ci) if not ci_s in invariants: invariants[ci_s] = (i + 1, len(nets_i_1)) nets_i_1.append(new_net) nets[i + 1] = nets_i_1 return nets, invariants
def coefficient_help(nodes, the_node, both_orbit_nodes, orbit1, orbit2, net, nets, auts, invs, allowed_aspects='all'): ''' helper function for coefficient ''' coef = 0 nodes_a = net.slices[0] layers = net.slices[1] net1 = nets[orbit1[0]][orbit1[1]] net2 = nets[orbit2[0]][orbit2[1]] n_nodes1 = len(net1.slices[0]) for node_comb in itertools.combinations(nodes, n_nodes1 - len(both_orbit_nodes)): nodes_s2 = nodes_a - set(node_comb) nodes_s1 = (nodes_a - nodes_s2) | set(both_orbit_nodes) sub1 = pymnet.subnet(net, nodes_s1, layers) sub2 = pymnet.subnet(net, nodes_s2, layers) ci_sub1 = str( pymnet.get_complete_invariant(sub1, allowed_aspects=allowed_aspects)) ci_sub2 = str( pymnet.get_complete_invariant(sub2, allowed_aspects=allowed_aspects)) if not ci_sub1 in invs or not ci_sub2 in invs: continue if invs[ci_sub1] == (orbit1[0], orbit1[1]) and invs[ci_sub2] == (orbit2[0], orbit2[1]): iso1 = pymnet.get_isomorphism(sub1, net1, allowed_aspects=allowed_aspects) iso2 = pymnet.get_isomorphism(sub2, net2, allowed_aspects=allowed_aspects) if the_node in iso1[0]: iso_node1 = iso1[0][the_node] else: iso_node1 = the_node if the_node in iso2[0]: iso_node2 = iso2[0][the_node] else: iso_node2 = the_node if auts[orbit1[0], orbit1[1], iso_node1] == orbit1[2] and auts[orbit2[0], orbit2[1], iso_node2] == orbit2[2]: coef += 1 return coef
def merge_nodes(both_orbit_nodes, net, allowed_aspects='all'): ''' Merges nodes from different orbits, each returned network has different combination of nodes merged, merges only one pair of nodes Parameters ---------- both_orbit_nodes : list of nodes net : network allowed_aspects : list, string the aspects that can be permutated when computing isomorphisms Returns ------- new_nets_and_nodes : list of tuples (net, both_orbit_nodes) Notes ----- assumes negative nodes belong to the same orbit works for both multiplex and multilayer networks (1 aspect) ''' nodes_a = net.slices[0] nodes = nodes_a - set(both_orbit_nodes) nodes1 = [] nodes2 = [] for node in nodes: if node < 0: nodes2.append(node) else: nodes1.append(node) layers = net.slices[1] new_nets_and_nodes = [] new_nets = [] new_invs = set() for comb in itertools.product(nodes1, nodes2): node1 = comb[0] node2 = comb[1] l1 = set() l2 = set() for nl in net.iter_node_layers(): if nl[0] == node1: l1.add(nl[1]) elif nl[0] == node2: l2.add(nl[1]) if not (l1 <= l2 and l1 >= l2): #nodes not present in the same layers continue nodes_s1 = both_orbit_nodes + [node1] nodes_s2 = both_orbit_nodes + [node2] sub1 = pymnet.subnet(net, nodes_s1, layers) sub2 = pymnet.subnet(net, nodes_s2, layers) if not pymnet.is_isomorphic( sub1, sub2, allowed_aspects=[0]): #not same links to the_node continue nodes_s = nodes_a - set([node2]) new_net = pymnet.subnet(net, nodes_s, layers) for layer in layers: node2_o = net.__getitem__((node2, layer)) for neighbor in node2_o.iter_total(): if neighbor[0] != node2: new_net[node1, neighbor[0], layer, neighbor[1]] = 1 ci = str( pymnet.get_complete_invariant(new_net, allowed_aspects=allowed_aspects)) if not ci in new_invs: new_nets.append(new_net) new_nets_and_nodes.append((new_net, both_orbit_nodes + [node1])) new_invs.add(ci) return new_nets_and_nodes
def combine_orbits(orbit1, orbit2, nets, allowed_aspects='all'): ''' Combines orbits orbit1 and orbit2 Parameters ---------- orbit1, orbit2: tuple (n_nodes, net_index, node) nets: dict (key: n_nodes, value: list of networks) graphlets allowed_aspects : list, string the aspects that can be permutated when computing isomorphisms Returns ------- new_nets: list of networks, None networks obtained by combining the orbits (no links added, no merging of nodes), returns None if orbits cannot be combined (graphlets have different layers) Notes ----- atm works only with node-layer isomorphisms, now also vertex isomorphisms node1 will be the_node ''' net1 = nets[orbit1[0]][orbit1[1]] net2 = nets[orbit2[0]][orbit2[1]] node1 = orbit1[2] node2 = orbit2[2] nodeNames = {} layerNames = {} nodeNames[node2] = node1 nodes2 = net2.slices[0] n_nodes2 = len(nodes2) nodes1 = net1.slices[0] layers1 = net1.slices[1] layers2 = net2.slices[1] if layers1 != layers2: return None for (nodeName, newName) in zip( nodes2 - set([node2]), range(-2, -n_nodes2 - 1, -1)): # could be changed to range(-1,-n_nodes2,-1)? nodeNames[nodeName] = newName new_nets = [] new_invs = set() new_net = pymnet.subnet(net1, nodes1, layers1) net2_r = pymnet.transforms.relabel(net2, nodeNames, layerNames) for e in net2_r.edges: if e[0] != e[1]: new_net[e[0], e[1], e[2], e[3]] = e[4] new_nets.append(new_net) ci = str( pymnet.get_complete_invariant(new_net, allowed_aspects=allowed_aspects)) new_invs.add(ci) if allowed_aspects != [0]: for perm in itertools.permutations(layers2, len(layers2)): for i in range(len(perm)): layerNames[perm[i - 1]] = perm[i] new_net = pymnet.subnet(net1, nodes1, layers1) net2_r = pymnet.transforms.relabel(net2, nodeNames, layerNames) for e in net2_r.edges: if e[0] != e[1]: new_net[e[0], e[1], e[2], e[3]] = e[4] ci = str( pymnet.get_complete_invariant(new_net, allowed_aspects=allowed_aspects)) if not ci in new_invs: new_nets.append(new_net) new_invs.add(ci) return new_nets
def orbit_counts_all(net, n, nets, invs, auts, orbit_list, allowed_aspects='all'): ''' Computes the orbit counts for all the nodes in net Parameters ---------- net : network n : int max number of nodes nets : dict (key: n_nodes, value: list of networks) Graphlets, as produced by graphlets invs : dict (key: str(complete invariant), value: tuple(n_nodes, net_index in nets)) complete invariants of the graphlets, as produced by graphlet auts : dd (key: (n_nodes, net_index, node), value: node) automorphisms, as produced by automorphism_orbits orbit_list : list of orbits as returned by ordered_orbit_list allowed_aspects : list, string the aspects that can be permutated when computing isomorphisms Returns ------- orbits : dd (key: (node, orbit), value: count) Orbit counts for all the nodes Notes ----- Should be faster than orbit_counts if the counts are computed for all (/ most of) the nodes ''' nodes = net.slices[0] layers = net.slices[1] orbits = dd() for node in nodes: for orbit in orbit_list: orbits[node, orbit] = 0 processed = set() for node0 in nodes: node_sets = set() set_p = set([frozenset([node0])]) for _ in range(n - 1): set_c = set() for p in set_p: for node_p in p: for layer in layers: node_o = net.__getitem__((node_p, layer)) for neighbor in node_o.iter_total(): if not (neighbor[0] in p or neighbor[0] in processed): set_n = frozenset(p | set([neighbor[0]])) set_c.add(set_n) node_sets = node_sets.union(set_c) set_p = set_c.copy() processed.add(node0) for node_comb in node_sets: sub_net = pymnet.subnet(net, node_comb, layers) ci_sub = str( pymnet.get_complete_invariant(sub_net, allowed_aspects=allowed_aspects)) if ci_sub not in invs: pymnet.draw(sub_net) print(len(invs)) i = invs[ci_sub][0] j = invs[ci_sub][1] nw = nets[i][j] iso = pymnet.get_isomorphism(sub_net, nw, allowed_aspects=allowed_aspects) for node in node_comb: if node in iso[0]: orbits[node, (i, j, auts[i, j, iso[0][node]])] += 1 else: orbits[node, (i, j, auts[i, j, node])] += 1 for layer in layers: nls = list(net[node0, :, layer]) for node1 in nls: net[node0, node1[0], layer] = 0 #remove edges return orbits