1 # SPDX-License-Identifier: GPL-2.0-or-later
3 __author__
= "Nutti <nutti.metro@gmail.com>"
4 __status__
= "production"
6 __date__
= "22 Apr 2022"
10 def __init__(self
, key
, value
=None):
16 return len(self
.edges
)
18 def connected_nodes(self
):
19 return [e
.other(self
) for e
in self
.edges
]
23 def __init__(self
, node_1
, node_2
):
27 def other(self
, node
):
28 if self
.node_1
== node
and self
.node_2
== node
:
29 raise RuntimeError("Loop edge in {} is not supported."
31 if node
not in (self
.node_1
, self
.node_2
):
32 raise RuntimeError("Node {} does not belong to this edge."
34 if self
.node_1
== node
:
44 def add_node(self
, node
):
45 if node
.key
in self
.nodes
:
46 raise RuntimeError("Node '{}' is already registered."
48 self
.nodes
[node
.key
] = node
50 def add_edge(self
, node_1
, node_2
):
51 if node_1
.key
not in self
.nodes
:
52 raise RuntimeError("Node '{}' is not registered."
54 if node_2
.key
not in self
.nodes
:
55 raise RuntimeError("Node '{}' is not registered."
58 edge
= Edge(node_1
, node_2
)
59 self
.edges
.append(edge
)
60 node_1
.edges
.append(edge
)
61 node_2
.edges
.append(edge
)
63 def get_node(self
, key
):
64 return self
.nodes
[key
]
67 def dump_graph(graph
):
69 for _
, node
in graph
.nodes
.items():
70 print("Key: {}, Value {}".format(node
.key
, node
.value
))
73 for edge
in graph
.edges
:
74 print("{} - {}".format(edge
.node_1
.key
, edge
.node_2
.key
))
78 # Ref: https://stackoverflow.com/questions/8176298/
79 # vf2-algorithm-steps-with-example
80 # Ref: https://github.com/satemochi/saaaaah/blob/master/geometric_misc/
82 def graph_is_isomorphic(graph_1
, graph_2
):
83 def is_iso(pairs
, matching_node
, new_node
):
85 # 1. The degree is same (It's faster).
86 # 2. The connected node is same.
87 if matching_node
.degree() != new_node
.degree():
90 matching_connected
= [c
.key
for c
in matching_node
.connected_nodes()]
91 new_connected
= [c
.key
for c
in new_node
.connected_nodes()]
96 if n1
in matching_connected
and n2
not in new_connected
:
98 if n1
not in matching_connected
and n2
in new_connected
:
103 def dfs(graph_1
, graph_2
):
104 def generate_pair(g1
, g2
, pairs
):
105 remove_1
= [p
[0] for p
in pairs
]
106 remove_2
= [p
[1] for p
in pairs
]
108 keys_1
= sorted(list(set(g1
.nodes
.keys()) - set(remove_1
)))
109 keys_2
= sorted(list(set(g2
.nodes
.keys()) - set(remove_2
)))
115 stack
= [generate_pair(graph_1
, graph_2
, pairs
)]
118 k1
, k2
= next(stack
[-1])
119 n1
= graph_1
.get_node(k1
)
120 n2
= graph_2
.get_node(k2
)
121 if is_iso(pairs
, n1
, n2
):
122 pairs
.append([k1
, k2
])
123 stack
.append(generate_pair(graph_1
, graph_2
, pairs
))
124 if len(pairs
) == len(graph_1
.nodes
):
126 except StopIteration:
128 diff
= len(pairs
) - len(stack
)
129 for _
in range(diff
):
134 # First, check simple condition.
135 if len(graph_1
.nodes
) != len(graph_2
.nodes
):
137 if len(graph_1
.edges
) != len(graph_2
.edges
):
140 is_isomorphic
, pairs
= dfs(graph_1
, graph_2
)
146 node_1
= graph_1
.get_node(n1
)
147 node_2
= graph_2
.get_node(n2
)
148 node_pairs
[node_1
] = node_2
150 return is_isomorphic
, node_pairs