def graph_isomorphism(g: 'Graph', h: 'Graph'):
    # Combine the two graphs to one graph with two components
    combined_graph, division = add_graphs(g, h)
    # Do an initial colouring based on degree and colourize
    colouring_dict, partitions, max_colour = colourize(combined_graph, True)
    # Update the graph labels in the combined graph
    for vertex in combined_graph:
        vertex.label = colouring_dict[vertex]
    # If the graph is not bijective but balanced, we can branch
    while not bijective(partitions) and balanced(partitions, division):
        # Find the first non bijective partition
        # TODO optimize this by doing it in bijective() function, already iterating over it there
        old_colouring_dict = colouring_dict
        non_bijective_colour = ""
        for colour in partitions:
            if len(partitions[colour]) != 2:
                non_bijective_colour = colour
                break
        # Set a variable used for breaking out of two loops at once
        doublebreak = False
        # Iterate over all the vertices in the first found non bijective partition
        for vertex_a in partitions[non_bijective_colour]:
            # If the vertex is in the component of the new graph that previously (before combining) was one graph (g)
            if vertex_a in division[0]:
                g_vertex = vertex_a
                # Iterate over all the vertices in the first found non bijective partition again
                for vertex_b in partitions[non_bijective_colour]:
                    # If the vertex is now in the other graph (h)
                    if vertex_b in division[1]:
                        h_vertex = vertex_b
                        # Change the labels of the g_vertex and h_vertex
                        h_vertex.label, g_vertex.label = max_colour, max_colour
                        # Colourize the new graph
                        new_colouring_dict, new_partitions, new_max_colour = colourize(
                            combined_graph, False)
                        # Update the labels
                        for vertex_c in combined_graph:
                            vertex_c.label = new_colouring_dict[vertex_c]
                        # Check if the new graph is balanced
                        if balanced(new_partitions, division):
                            # If balanced, update variables and break out of for-loops
                            partitions = new_partitions
                            max_colour = new_max_colour
                            colouring_dict = new_colouring_dict
                            doublebreak = True
                            break
                        else:
                            # If not balanced, revert the changes
                            for vertex_d in combined_graph:
                                vertex_d.label = old_colouring_dict[vertex_d]
                if doublebreak:
                    break
    # The graphs are isomorphic if the partitions are balanced and bijective at the end of the loop
    result = bijective(partitions) and balanced(partitions, division)
    return combined_graph, result
def recursion(colouring_dict, combined_graph, division, max_colour,
              partitions):
    if bijective(partitions) and balanced(partitions, division):
        return True, 1
    else:
        old_colouring_dict = colouring_dict
        non_bijective_colour = ""
        for colour in partitions:
            if len(partitions[colour]) != 2:
                non_bijective_colour = colour
                break
        partition_automorphisms = 0
        result = False
        for vertex_a in partitions[non_bijective_colour]:
            if vertex_a in division[0]:
                g_vertex = vertex_a
                for vertex_b in partitions[non_bijective_colour]:
                    if vertex_b in division[1]:
                        h_vertex = vertex_b
                        h_vertex.label, g_vertex.label = max_colour, max_colour
                        new_colouring_dict, new_partitions, new_max_colour = colourize(
                            combined_graph, False)
                        for vertex_c in combined_graph:
                            vertex_c.label = new_colouring_dict[vertex_c]
                        if balanced(new_partitions, division):
                            rec_result, automorphisms = recursion(
                                new_colouring_dict, combined_graph, division,
                                new_max_colour, new_partitions)
                            partition_automorphisms += automorphisms
                            result = result or rec_result
                        for vertex_d in combined_graph:  # revert changes
                            vertex_d.label = old_colouring_dict[vertex_d]

        # The graphs are isomorphic if the partitions are balanced and bijective at the end of the loop
        return result, partition_automorphisms
def graph_isomorphism(g: 'Graph', h: 'Graph'):
    # Combine the two graphs to one graph with two components
    combined_graph, division = add_graphs(g, h)
    # Do an initial colouring based on degree and colourize
    colouring_dict, partitions, max_colour = colourize(combined_graph, True)
    # Update the graph labels in the combined graph
    for vertex in combined_graph:
        vertex.label = colouring_dict[vertex]
    result, total_automorphisms = recursion(colouring_dict, combined_graph,
                                            division, max_colour, partitions)
    return combined_graph, result, int(math.sqrt(total_automorphisms))
def graph_isomorphism(g: 'Graph', h: 'Graph'):
    total_automorphisms = 1
    # Combine the two graphs to one graph with two components
    combined_graph, division = add_graphs(g, h)
    # Do an initial colouring based on degree and colourize
    colouring_dict, partitions, max_colour = colourize(combined_graph, True)
    # Update the graph labels in the combined graph
    for vertex in combined_graph:
        vertex.label = colouring_dict[vertex]
    # If the graph is not bijective but balanced, we can branch
    while not bijective(partitions) and balanced(partitions, division):
        # Find the first non bijective partition
        old_colouring_dict = colouring_dict
        non_bijective_colour = ""
        for colour in partitions:
            if len(partitions[colour]) != 2:
                non_bijective_colour = colour
                break

        last_stable_partitions = None
        last_stable_max_colour = None
        last_stable_colouring_dict = None
        partition_automorphisms = 0

        # Pick a vertex in the non bijective partition of graph g
        g_vertex = partitions[non_bijective_colour][0]
        nr = 1
        while g_vertex not in division[0]:
            g_vertex = partitions[non_bijective_colour][nr]
            nr += 1

        # For every vertex in the non bijective partition of graph h
        for vertex_b in partitions[non_bijective_colour]:
            if vertex_b in division[1]:
                h_vertex = vertex_b

                # Change the labels of the g_vertex and h_vertex
                h_vertex.label, g_vertex.label = max_colour, max_colour
                # Colourize the new graph
                new_colouring_dict, new_partitions, new_max_colour = colourize(
                    combined_graph, False)
                # Update the labels
                for vertex_c in combined_graph:
                    vertex_c.label = new_colouring_dict[vertex_c]

                # Check if the new graph is balanced
                if balanced(new_partitions, division):
                    # If balanced, update variables and break out of for-loops
                    last_stable_partitions = new_partitions
                    last_stable_max_colour = new_max_colour
                    last_stable_colouring_dict = new_colouring_dict
                    partition_automorphisms += 1

                # Revert the changes so we can look for more stable options
                for vertex_d in combined_graph:
                    vertex_d.label = old_colouring_dict[vertex_d]

        # If a stable option was found
        if last_stable_partitions is not None:
            partitions = last_stable_partitions
            max_colour = last_stable_max_colour
            colouring_dict = last_stable_colouring_dict
            # Update the labels
            for vertex_e in combined_graph:
                vertex_e.label = colouring_dict[vertex_e]

            # Update the automorphisms counter
            if partition_automorphisms > 0:
                total_automorphisms = total_automorphisms * partition_automorphisms
        else:
            return combined_graph, False, 0

    # The graphs are isomorphic if the partitions are balanced and bijective at the end of the loop
    result = bijective(partitions) and balanced(partitions, division)
    if not result:
        total_automorphisms = 0

    return combined_graph, result, total_automorphisms