Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/isomorphism/vf2userfunc.py: 23%

48 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1""" 

2 Module to simplify the specification of user-defined equality functions for 

3 node and edge attributes during isomorphism checks. 

4 

5 During the construction of an isomorphism, the algorithm considers two 

6 candidate nodes n1 in G1 and n2 in G2. The graphs G1 and G2 are then 

7 compared with respect to properties involving n1 and n2, and if the outcome 

8 is good, then the candidate nodes are considered isomorphic. NetworkX 

9 provides a simple mechanism for users to extend the comparisons to include 

10 node and edge attributes. 

11 

12 Node attributes are handled by the node_match keyword. When considering 

13 n1 and n2, the algorithm passes their node attribute dictionaries to 

14 node_match, and if it returns False, then n1 and n2 cannot be 

15 considered to be isomorphic. 

16 

17 Edge attributes are handled by the edge_match keyword. When considering 

18 n1 and n2, the algorithm must verify that outgoing edges from n1 are 

19 commensurate with the outgoing edges for n2. If the graph is directed, 

20 then a similar check is also performed for incoming edges. 

21 

22 Focusing only on outgoing edges, we consider pairs of nodes (n1, v1) from 

23 G1 and (n2, v2) from G2. For graphs and digraphs, there is only one edge 

24 between (n1, v1) and only one edge between (n2, v2). Those edge attribute 

25 dictionaries are passed to edge_match, and if it returns False, then 

26 n1 and n2 cannot be considered isomorphic. For multigraphs and 

27 multidigraphs, there can be multiple edges between (n1, v1) and also 

28 multiple edges between (n2, v2). Now, there must exist an isomorphism 

29 from "all the edges between (n1, v1)" to "all the edges between (n2, v2)". 

30 So, all of the edge attribute dictionaries are passed to edge_match, and 

31 it must determine if there is an isomorphism between the two sets of edges. 

32""" 

33 

34from . import isomorphvf2 as vf2 

35 

36__all__ = ["GraphMatcher", "DiGraphMatcher", "MultiGraphMatcher", "MultiDiGraphMatcher"] 

37 

38 

39def _semantic_feasibility(self, G1_node, G2_node): 

40 """Returns True if mapping G1_node to G2_node is semantically feasible.""" 

41 # Make sure the nodes match 

42 if self.node_match is not None: 

43 nm = self.node_match(self.G1.nodes[G1_node], self.G2.nodes[G2_node]) 

44 if not nm: 

45 return False 

46 

47 # Make sure the edges match 

48 if self.edge_match is not None: 

49 # Cached lookups 

50 G1nbrs = self.G1_adj[G1_node] 

51 G2nbrs = self.G2_adj[G2_node] 

52 core_1 = self.core_1 

53 edge_match = self.edge_match 

54 

55 for neighbor in G1nbrs: 

56 # G1_node is not in core_1, so we must handle R_self separately 

57 if neighbor == G1_node: 

58 if G2_node in G2nbrs and not edge_match( 

59 G1nbrs[G1_node], G2nbrs[G2_node] 

60 ): 

61 return False 

62 elif neighbor in core_1: 

63 G2_nbr = core_1[neighbor] 

64 if G2_nbr in G2nbrs and not edge_match( 

65 G1nbrs[neighbor], G2nbrs[G2_nbr] 

66 ): 

67 return False 

68 # syntactic check has already verified that neighbors are symmetric 

69 

70 return True 

71 

72 

73class GraphMatcher(vf2.GraphMatcher): 

74 """VF2 isomorphism checker for undirected graphs.""" 

75 

76 def __init__(self, G1, G2, node_match=None, edge_match=None): 

77 """Initialize graph matcher. 

78 

79 Parameters 

80 ---------- 

81 G1, G2: graph 

82 The graphs to be tested. 

83 

84 node_match: callable 

85 A function that returns True iff node n1 in G1 and n2 in G2 

86 should be considered equal during the isomorphism test. The 

87 function will be called like:: 

88 

89 node_match(G1.nodes[n1], G2.nodes[n2]) 

90 

91 That is, the function will receive the node attribute dictionaries 

92 of the nodes under consideration. If None, then no attributes are 

93 considered when testing for an isomorphism. 

94 

95 edge_match: callable 

96 A function that returns True iff the edge attribute dictionary for 

97 the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should be 

98 considered equal during the isomorphism test. The function will be 

99 called like:: 

100 

101 edge_match(G1[u1][v1], G2[u2][v2]) 

102 

103 That is, the function will receive the edge attribute dictionaries 

104 of the edges under consideration. If None, then no attributes are 

105 considered when testing for an isomorphism. 

106 

107 """ 

108 vf2.GraphMatcher.__init__(self, G1, G2) 

109 

110 self.node_match = node_match 

111 self.edge_match = edge_match 

112 

113 # These will be modified during checks to minimize code repeat. 

114 self.G1_adj = self.G1.adj 

115 self.G2_adj = self.G2.adj 

116 

117 semantic_feasibility = _semantic_feasibility 

118 

119 

120class DiGraphMatcher(vf2.DiGraphMatcher): 

121 """VF2 isomorphism checker for directed graphs.""" 

122 

123 def __init__(self, G1, G2, node_match=None, edge_match=None): 

124 """Initialize graph matcher. 

125 

126 Parameters 

127 ---------- 

128 G1, G2 : graph 

129 The graphs to be tested. 

130 

131 node_match : callable 

132 A function that returns True iff node n1 in G1 and n2 in G2 

133 should be considered equal during the isomorphism test. The 

134 function will be called like:: 

135 

136 node_match(G1.nodes[n1], G2.nodes[n2]) 

137 

138 That is, the function will receive the node attribute dictionaries 

139 of the nodes under consideration. If None, then no attributes are 

140 considered when testing for an isomorphism. 

141 

142 edge_match : callable 

143 A function that returns True iff the edge attribute dictionary for 

144 the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should be 

145 considered equal during the isomorphism test. The function will be 

146 called like:: 

147 

148 edge_match(G1[u1][v1], G2[u2][v2]) 

149 

150 That is, the function will receive the edge attribute dictionaries 

151 of the edges under consideration. If None, then no attributes are 

152 considered when testing for an isomorphism. 

153 

154 """ 

155 vf2.DiGraphMatcher.__init__(self, G1, G2) 

156 

157 self.node_match = node_match 

158 self.edge_match = edge_match 

159 

160 # These will be modified during checks to minimize code repeat. 

161 self.G1_adj = self.G1.adj 

162 self.G2_adj = self.G2.adj 

163 

164 def semantic_feasibility(self, G1_node, G2_node): 

165 """Returns True if mapping G1_node to G2_node is semantically feasible.""" 

166 

167 # Test node_match and also test edge_match on successors 

168 feasible = _semantic_feasibility(self, G1_node, G2_node) 

169 if not feasible: 

170 return False 

171 

172 # Test edge_match on predecessors 

173 self.G1_adj = self.G1.pred 

174 self.G2_adj = self.G2.pred 

175 feasible = _semantic_feasibility(self, G1_node, G2_node) 

176 self.G1_adj = self.G1.adj 

177 self.G2_adj = self.G2.adj 

178 

179 return feasible 

180 

181 

182# The "semantics" of edge_match are different for multi(di)graphs, but 

183# the implementation is the same. So, technically we do not need to 

184# provide "multi" versions, but we do so to match NetworkX's base classes. 

185 

186 

187class MultiGraphMatcher(GraphMatcher): 

188 """VF2 isomorphism checker for undirected multigraphs.""" 

189 

190 

191class MultiDiGraphMatcher(DiGraphMatcher): 

192 """VF2 isomorphism checker for directed multigraphs."""