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 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 orbit_name(node, net, nets, invs, auts, allowed_aspects='all'):
    '''
    finds the name of the orbit given node and net
    '''

    ci = str(
        pymnet.get_complete_invariant(net, allowed_aspects=allowed_aspects))
    i, j = invs[ci]
    net_i = nets[i][j]
    iso = pymnet.get_isomorphism(net,
                                 net_i,
                                 allowed_aspects=allowed_aspects,
                                 include_fixed=True)
    k = auts[i, j, iso[0][node]]

    return (i, j, k)
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 orbit_equations(n, nets, auts, invs, allowed_aspects='all'):
    '''
    Generate orbit equations for up to n nodes

    The equations are in following formatting:
    
    orbits : this represents node orbits of graphlets
    
    Parameters
    ----------
    n : int
        maximum number of nodes
    nets : dict (key: n_nodes, value: list of nets)
        graphlets, as returned by graphlets
    auts : dd (key: (n_nodes, net_index, node), value: node)
        automorphisms, as returned by automorphism_orbits
    invs : dict (key: str(complete invariant), value: tuple(n_nodes, net_index in nets))
        complete invariants of the graphlets, as returned by graphlets
    allowed_aspects : list, string
        the aspects that can be permutated when computing isomorphisms
    
    Returns
    -------
    orbit_eqs : dict (key: orbits, value: dict (key: orbit, value: coefficient))
        
    '''

    orbit_eqs = dd()

    orbit_lists = list_orbits(auts)

    for n_nodes1 in range(2, min(n + 1, 4)):
        for orbit in orbit_lists[n_nodes1]:
            node1 = orbit[2]
            for n_nodes in orbit_lists:
                comb_n_nodes = n_nodes1 + n_nodes - 1
                if comb_n_nodes <= n:
                    for orbit2 in orbit_lists[n_nodes]:
                        if ((orbit, 1), (orbit2, 1)) in orbit_eqs:
                            continue

                        new_nets = {}
                        new_orbits = set()
                        comb_nets = combine_orbits(
                            orbit,
                            orbit2,
                            nets,
                            allowed_aspects=allowed_aspects)
                        if comb_nets == None:
                            continue

                        for k in range(len(comb_nets)):
                            comb_nets[k] = (comb_nets[k], [node1])
                        merge_nets = {}
                        min_nodes = max([n_nodes1, n_nodes]) + 1
                        merge_nets[comb_n_nodes] = comb_nets
                        for m in range(
                                comb_n_nodes, min_nodes, -1
                        ):  #TODO: make this merge thingy smarter, use combinations
                            merge_nets[m - 1] = []
                            for m_net in merge_nets[m]:
                                merge_nets[m - 1] += merge_nodes(
                                    m_net[1],
                                    m_net[0],
                                    allowed_aspects=allowed_aspects)

                            comb_nets += merge_nets[m - 1]

                        add_e_nets = []
                        for k in range(len(comb_nets)):
                            c_net = comb_nets[k][0]
                            both_orbit_nodes = comb_nets[k][1]
                            nets_e = add_possible_edges(
                                both_orbit_nodes, c_net)
                            for net_e in nets_e:
                                add_e_nets.append((net_e, both_orbit_nodes))

                        comb_nets += add_e_nets
                        for comb_net in comb_nets:
                            ci_comb = str(
                                pymnet.get_complete_invariant(
                                    comb_net[0],
                                    allowed_aspects=allowed_aspects))
                            iso_net = invs[ci_comb]
                            iso = pymnet.get_isomorphism(
                                comb_net[0],
                                nets[iso_net[0]][iso_net[1]],
                                allowed_aspects=allowed_aspects)
                            if node1 in iso[0]:
                                node_o = iso[0][node1]
                            else:
                                node_o = node1

                            new_orbit = (iso_net[0], iso_net[1],
                                         auts[iso_net[0], iso_net[1], node_o])
                            if not new_orbit in new_orbits:
                                new_orbits.add(new_orbit)
                                new_nets[new_orbit] = comb_net

                        if orbit == orbit2:
                            times = 2
                            key = ((orbit, times))
                        else:
                            times = 1
                            key = ((orbit2, 1), (orbit, 1))

                        orbit_eqs[key] = {}
                        for i in new_orbits:
                            coef = coefficient(node1,
                                               new_nets[i][1],
                                               orbit,
                                               orbit2,
                                               new_nets[i][0],
                                               nets,
                                               auts,
                                               invs,
                                               allowed_aspects=allowed_aspects)
                            if coef > 0:
                                orbit_eqs[key][i] = coef

    return orbit_eqs
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