def refineColors(G: graph):
    a = dict()
    aPrev = dict()
    neighbours = generateNeighbourList(G)

    # define a number that gives a color that has not yet been chosen
    nextColor = 0
    # Fill a with colors related to the degree of the vertex
    for v in G.V():
        a[v] = len(neighbours[v])
        nextColor = max(a[v] + 1, nextColor)

    # while the colors still change after an iteration
    while aPrev != a:
        aPrev = a
        a = dict()
        nbColors = getNeighbourColors(neighbours, aPrev)
        # for each vertex u in G.V()
        for i in range(len(G.V())):
            u = G.V()[i]

            # initialize nc, the new color of u
            nc = a.get(u, aPrev[u])

            # create a set "same" that will contain all vertices v that are "equal" to the current vertex u
            same = set()

            # for every unchecked vertex v
            for v in G.V()[i + 1 :]:

                # if the vertices were "equal" and not exactly the same
                if u != v and aPrev[u] == aPrev[v]:

                    # Check if they are still "equal"
                    if not haveSameNeighbours(u, v, nbColors):

                        # they are not equal anymore, if we haven't updated nc already, do it now
                        if nc == aPrev[u]:
                            nc = nextColor
                            nextColor += 1
                    else:
                        # they are still equal, add the vertex to our "same" set
                        same.add(v)

            # update the a[v] of every v in same
            for v in same:

                # If nc and a[v] (or the previous a[v]) differ, change a[v]
                if nc != a.get(v, aPrev[v]):
                    a[v] = nc
            a[u] = nc

    # set the colornums
    for v in a:
        v.colornum = a[v]

    return a
def countTreeAutomorphismsRS(G, visualize=False):
    p = generatePartitions(G)
    n = generateNeighbourList(G)
    degrees = dict()

    for v in G.V():
        degree = len(n[v])
        degreeSet = degrees.get(degree, 0)
        if degreeSet:
            degreeSet.add(v)
        else:
            degrees[degree] = {v}

    colorOf = dict()
    for color in p:
        for v in color:
            colorOf[v] = color

    queue = []
    done = set()
    automorphisms = 1
    for c in p:
        if len(c) == 1:
            queue.append((pickFromSet(c), None))
            break
    if not queue:
        for c in p:
            if len(c) == 2:
                r, l = c

                if True or l in n[r]:
                    queue.append((r, None))
                    break
    while queue:
        vertex, parent = queue[-1]
        color = colorOf[vertex]
        del queue[-1]
        for v in n[vertex] - done:
            if v not in done:
                queue.append((v, vertex))
                done |= colorOf[v]
        if parent:
            automorphisms *= math.factorial(len(n[parent] & color))**(len(colorOf[parent]))
        else:
            automorphisms *= math.factorial(len(color))
        if visualize:
            drawProgress(G, vertex, color, done, queue)
        done |= color
    if visualize:
        drawProgress(G, None, set(), done, queue)
    return automorphisms
def generatePartitions(G, usecolors=False):
    neighbours = generateNeighbourList(G)
    p = []
    pSplit = []
    degrees = dict()
    for v in G.V():
        degree = len(neighbours[v])
        if degrees.get(degree, -1) == -1:
            degrees[degree] = {v}
        else:
            degrees[degree].add(v)

    if usecolors:
        p = generatePfromColors(G)
        pSplit = generatePfromColors(G)
    else:
        for k in degrees:
            p.append(degrees[k])
            pSplit.append(degrees[k])

    w = set(range(len(p)))
    while w:
        #print("w: ", w)
        #print("wl: ", [(c, len(p[c])) for c in w])
        aN = w.pop()
        a = p[aN]
        nbs = set()
        for va in a:
            nbs |= neighbours[va]
        # print("nbs: ", nbs)
        for color in pSplit:
            x = nbs & color
            # print("nbs & color: ", x)
            if x:
                for yN in range(len(p)):
                    if len(p[yN]) > 1:
                        y = p[yN]
                        both = x & y
                        ynotx = y - x
                        if both and ynotx:
                            p[yN] = both
                            p.append(ynotx)
                            if yN in w:
                                w.add(len(p) - 1)
                            else:
                                if len(both) <= len(ynotx):
                                    w.add(yN)
                                else:
                                    w.add(len(p) - 1)
    return p
def refineColorsv2(G: graph, useColornums=False):

    # a is a dict that contains a vertex as key and a color as value
    a = dict()

    # the old a, from the previous iteration
    aPrev = dict()

    # aRev (a reversed) contains a color as key and a set of vertices that have that color as value
    aRev = dict()

    # the a reversed dict of the previous iteration
    aRevPrev = dict()

    # generate a dict of vertex -> vertices that contains all the neighbours of the key vertex
    neighbours = generateNeighbourList(G)

    # define a number that gives a color that has not yet been chosen
    nextColor = 0

    # Fill a with colors related to the degree of the vertex
    for v in G.V():

        # color of v = the degree of v (number of neighbours)
        if not useColornums:
            a[v] = len(neighbours[v])
        else:
            a[v] = v.colornum

        addToRevDict(aRev, a[v], v)

        # make sure nextColor is always higher than the highest color that was already used
        nextColor = max(a[v] + 1, nextColor)

    # while the colors still change after an iteration
    while aPrev != a:

        # set the Prev dicts to the previous values of a and aRev and reset a and aRev
        aPrev = a
        aRevPrev = aRev
        a = dict()
        aRev = dict()

        nbColors = getNeighbourColors(neighbours, aPrev)

        # pus all the vertices that we have already refined in the set done so we won't have to check them again
        done = set()
        # for each vertex u in G.V()
        for u in G.V():

            # if u has already been refined earlier in this iteration, skip it now
            if u in done:
                continue

            # initialize nc, the new color of u
            nc = a.get(u, aPrev[u])

            # create a set "same" that will contain all vertices v that are "equal" to the current vertex u
            same = set()

            # for every vertex v that was equal
            for v in aRevPrev[aPrev[u]]:

                # if the vertices were "equal" and not exactly the same
                if u != v:

                    # Check if they are still "equal"
                    if not haveSameNeighbours(u, v, nbColors):

                        # they are not equal anymore, if we haven't updated nc already, do it now
                        if nc == aPrev[u]:
                            nc = nextColor
                            nextColor += 1
                    else:
                        # they are still equal, add the vertex to our "same" set
                        same.add(v)

            # update the a[v] of every v in same
            for v in same:

                # If nc and a[v] (or the previous a[v]) differ, change a[v]
                if nc != a.get(v, aPrev[v]):
                    a[v] = nc
                    addToRevDict(aRev, nc, v)
                    done.add(v)

            a[u] = nc
            addToRevDict(aRev, nc, u)

    # set the colornums
    for v in a:
        v.colornum = a[v]
    return a
def countTreeAutomorphismsLS(G, visualize=False):
    p = generatePartitions(G)
    n = generateNeighbourList(G)
    degrees = dict()

    for v in G.V():
        degree = len(n[v])
        degreeSet = degrees.get(degree, 0)
        if degreeSet:
            degreeSet.add(v)
        else:
            degrees[degree] = {v}

    colorOf = dict()
    for color in p:
        for v in color:
            colorOf[v] = color

    queue = []
    done = set()

    for color in p:
        if len(color) > 1 and color <= degrees[1]:
            queue.append(color)
            done = done | color

    automorphisms = 1

    while queue: # is not empty
        newQueue = []
        for color in queue:
            v = pickFromSet(color)
            if visualize:
                drawProgress(G, v, color, done, queue)
            parent = None
            for neighbour in n[v]:
                if len(n[neighbour] & color) > 1 :
                    parent = neighbour
                    break

            if parent is None:
                unvisitedNeighbours = n[v] - done
                if len(unvisitedNeighbours) == 1:
                    parent = unvisitedNeighbours.pop()
                elif len(unvisitedNeighbours) > 1:
                    newQueue.append(color)
                    continue

            if parent:
                if len(colorOf[parent]) > 1 and parent not in done:
                    newQueue.append(colorOf[parent])
                automorphisms *= math.factorial(len(n[parent] & color))**(len(colorOf[parent]))
                done |= color
            else:
                if len(color) == 2 and len(color&n[v]) == 1:
                    automorphisms *= 2
        queue = newQueue
        for c in queue:
            done |= c

    if visualize:
        drawProgress(G, None, set(), done, queue)
    return automorphisms