Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/algorithms/isomorphism/isomorph.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

77 statements  

1""" 

2Graph isomorphism functions. 

3""" 

4 

5import itertools 

6from collections import Counter 

7 

8import networkx as nx 

9from networkx.exception import NetworkXError 

10 

11__all__ = [ 

12 "could_be_isomorphic", 

13 "fast_could_be_isomorphic", 

14 "faster_could_be_isomorphic", 

15 "is_isomorphic", 

16] 

17 

18 

19@nx._dispatchable(graphs={"G1": 0, "G2": 1}) 

20def could_be_isomorphic(G1, G2, *, properties="dtc"): 

21 """Returns False if graphs are definitely not isomorphic. 

22 True does NOT guarantee isomorphism. 

23 

24 Parameters 

25 ---------- 

26 G1, G2 : graphs 

27 The two graphs `G1` and `G2` must be the same type. 

28 

29 properties : str, default="dct" 

30 Determines which properties of the graph are checked. Each character 

31 indicates a particular property as follows: 

32 

33 - if ``"d"`` in `properties`: degree of each node 

34 - if ``"t"`` in `properties`: number of triangles for each node 

35 - if ``"c"`` in `properties`: number of maximal cliques for each node 

36 

37 Unrecognized characters are ignored. The default is ``"dtc"``, which 

38 compares the sequence of ``(degree, num_triangles, num_cliques)`` properties 

39 between `G1` and `G2`. Generally, ``properties="dt"`` would be faster, and 

40 ``properties="d"`` faster still. See Notes for additional details on 

41 property selection. 

42 

43 Returns 

44 ------- 

45 bool 

46 A Boolean value representing whether `G1` could be isomorphic with `G2` 

47 according to the specified `properties`. 

48 

49 Notes 

50 ----- 

51 The triangle sequence contains the number of triangles each node is part of. 

52 The clique sequence contains for each node the number of maximal cliques 

53 involving that node. 

54 

55 Some properties are faster to compute than others. And there are other 

56 properties we could include and don't. But of the three properties listed here, 

57 comparing the degree distributions is the fastest. The "triangles" property 

58 is slower (and also a stricter version of "could") and the "maximal cliques" 

59 property is slower still, but usually faster than doing a full isomorphism 

60 check. 

61 """ 

62 

63 # Check global properties 

64 if G1.order() != G2.order(): 

65 return False 

66 

67 properties_to_check = set(properties) 

68 G1_props, G2_props = [], [] 

69 

70 def _properties_consistent(): 

71 # Ravel the properties into a table with # nodes rows and # properties columns 

72 G1_ptable = [tuple(p[n] for p in G1_props) for n in G1] 

73 G2_ptable = [tuple(p[n] for p in G2_props) for n in G2] 

74 

75 return sorted(G1_ptable) == sorted(G2_ptable) 

76 

77 # The property table is built and checked as each individual property is 

78 # added. The reason for this is the building/checking the property table 

79 # is in general much faster than computing the properties, making it 

80 # worthwhile to check multiple times to enable early termination when 

81 # a subset of properties don't match 

82 

83 # Degree sequence 

84 if "d" in properties_to_check: 

85 G1_props.append(G1.degree()) 

86 G2_props.append(G2.degree()) 

87 if not _properties_consistent(): 

88 return False 

89 # Sequence of triangles per node 

90 if "t" in properties_to_check: 

91 G1_props.append(nx.triangles(G1)) 

92 G2_props.append(nx.triangles(G2)) 

93 if not _properties_consistent(): 

94 return False 

95 # Sequence of maximal cliques per node 

96 if "c" in properties_to_check: 

97 G1_props.append(Counter(itertools.chain.from_iterable(nx.find_cliques(G1)))) 

98 G2_props.append(Counter(itertools.chain.from_iterable(nx.find_cliques(G2)))) 

99 if not _properties_consistent(): 

100 return False 

101 

102 # All checked conditions passed 

103 return True 

104 

105 

106def graph_could_be_isomorphic(G1, G2): 

107 """ 

108 .. deprecated:: 3.5 

109 

110 `graph_could_be_isomorphic` is a deprecated alias for `could_be_isomorphic`. 

111 Use `could_be_isomorphic` instead. 

112 """ 

113 import warnings 

114 

115 warnings.warn( 

116 "graph_could_be_isomorphic is deprecated, use `could_be_isomorphic` instead.", 

117 category=DeprecationWarning, 

118 stacklevel=2, 

119 ) 

120 return could_be_isomorphic(G1, G2) 

121 

122 

123@nx._dispatchable(graphs={"G1": 0, "G2": 1}) 

124def fast_could_be_isomorphic(G1, G2): 

125 """Returns False if graphs are definitely not isomorphic. 

126 

127 True does NOT guarantee isomorphism. 

128 

129 Parameters 

130 ---------- 

131 G1, G2 : graphs 

132 The two graphs G1 and G2 must be the same type. 

133 

134 Notes 

135 ----- 

136 Checks for matching degree and triangle sequences. The triangle 

137 sequence contains the number of triangles each node is part of. 

138 """ 

139 # Check global properties 

140 if G1.order() != G2.order(): 

141 return False 

142 

143 # Check local properties 

144 d1 = G1.degree() 

145 t1 = nx.triangles(G1) 

146 props1 = [[d, t1[v]] for v, d in d1] 

147 props1.sort() 

148 

149 d2 = G2.degree() 

150 t2 = nx.triangles(G2) 

151 props2 = [[d, t2[v]] for v, d in d2] 

152 props2.sort() 

153 

154 if props1 != props2: 

155 return False 

156 

157 # OK... 

158 return True 

159 

160 

161def fast_graph_could_be_isomorphic(G1, G2): 

162 """ 

163 .. deprecated:: 3.5 

164 

165 `fast_graph_could_be_isomorphic` is a deprecated alias for 

166 `fast_could_be_isomorphic`. Use `fast_could_be_isomorphic` instead. 

167 """ 

168 import warnings 

169 

170 warnings.warn( 

171 "fast_graph_could_be_isomorphic is deprecated, use fast_could_be_isomorphic instead", 

172 category=DeprecationWarning, 

173 stacklevel=2, 

174 ) 

175 return fast_could_be_isomorphic(G1, G2) 

176 

177 

178@nx._dispatchable(graphs={"G1": 0, "G2": 1}) 

179def faster_could_be_isomorphic(G1, G2): 

180 """Returns False if graphs are definitely not isomorphic. 

181 

182 True does NOT guarantee isomorphism. 

183 

184 Parameters 

185 ---------- 

186 G1, G2 : graphs 

187 The two graphs G1 and G2 must be the same type. 

188 

189 Notes 

190 ----- 

191 Checks for matching degree sequences. 

192 """ 

193 # Check global properties 

194 if G1.order() != G2.order(): 

195 return False 

196 

197 # Check local properties 

198 d1 = sorted(d for n, d in G1.degree()) 

199 d2 = sorted(d for n, d in G2.degree()) 

200 

201 if d1 != d2: 

202 return False 

203 

204 # OK... 

205 return True 

206 

207 

208def faster_graph_could_be_isomorphic(G1, G2): 

209 """ 

210 .. deprecated:: 3.5 

211 

212 `faster_graph_could_be_isomorphic` is a deprecated alias for 

213 `faster_could_be_isomorphic`. Use `faster_could_be_isomorphic` instead. 

214 """ 

215 import warnings 

216 

217 warnings.warn( 

218 "faster_graph_could_be_isomorphic is deprecated, use faster_could_be_isomorphic instead", 

219 category=DeprecationWarning, 

220 stacklevel=2, 

221 ) 

222 return faster_could_be_isomorphic(G1, G2) 

223 

224 

225@nx._dispatchable( 

226 graphs={"G1": 0, "G2": 1}, 

227 preserve_edge_attrs="edge_match", 

228 preserve_node_attrs="node_match", 

229) 

230def is_isomorphic(G1, G2, node_match=None, edge_match=None): 

231 """Returns True if the graphs G1 and G2 are isomorphic and False otherwise. 

232 

233 Parameters 

234 ---------- 

235 G1, G2: graphs 

236 The two graphs G1 and G2 must be the same type. 

237 

238 node_match : callable 

239 A function that returns True if node n1 in G1 and n2 in G2 should 

240 be considered equal during the isomorphism test. 

241 If node_match is not specified then node attributes are not considered. 

242 

243 The function will be called like 

244 

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

246 

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

248 for n1 and n2 as inputs. 

249 

250 edge_match : callable 

251 A function that returns True if the edge attribute dictionary 

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

253 be considered equal during the isomorphism test. If edge_match is 

254 not specified then edge attributes are not considered. 

255 

256 The function will be called like 

257 

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

259 

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

261 of the edges under consideration. 

262 

263 Notes 

264 ----- 

265 Uses the vf2 algorithm [1]_. 

266 

267 Examples 

268 -------- 

269 >>> import networkx.algorithms.isomorphism as iso 

270 

271 For digraphs G1 and G2, using 'weight' edge attribute (default: 1) 

272 

273 >>> G1 = nx.DiGraph() 

274 >>> G2 = nx.DiGraph() 

275 >>> nx.add_path(G1, [1, 2, 3, 4], weight=1) 

276 >>> nx.add_path(G2, [10, 20, 30, 40], weight=2) 

277 >>> em = iso.numerical_edge_match("weight", 1) 

278 >>> nx.is_isomorphic(G1, G2) # no weights considered 

279 True 

280 >>> nx.is_isomorphic(G1, G2, edge_match=em) # match weights 

281 False 

282 

283 For multidigraphs G1 and G2, using 'fill' node attribute (default: '') 

284 

285 >>> G1 = nx.MultiDiGraph() 

286 >>> G2 = nx.MultiDiGraph() 

287 >>> G1.add_nodes_from([1, 2, 3], fill="red") 

288 >>> G2.add_nodes_from([10, 20, 30, 40], fill="red") 

289 >>> nx.add_path(G1, [1, 2, 3, 4], weight=3, linewidth=2.5) 

290 >>> nx.add_path(G2, [10, 20, 30, 40], weight=3) 

291 >>> nm = iso.categorical_node_match("fill", "red") 

292 >>> nx.is_isomorphic(G1, G2, node_match=nm) 

293 True 

294 

295 For multidigraphs G1 and G2, using 'weight' edge attribute (default: 7) 

296 

297 >>> G1.add_edge(1, 2, weight=7) 

298 1 

299 >>> G2.add_edge(10, 20) 

300 1 

301 >>> em = iso.numerical_multiedge_match("weight", 7, rtol=1e-6) 

302 >>> nx.is_isomorphic(G1, G2, edge_match=em) 

303 True 

304 

305 For multigraphs G1 and G2, using 'weight' and 'linewidth' edge attributes 

306 with default values 7 and 2.5. Also using 'fill' node attribute with 

307 default value 'red'. 

308 

309 >>> em = iso.numerical_multiedge_match(["weight", "linewidth"], [7, 2.5]) 

310 >>> nm = iso.categorical_node_match("fill", "red") 

311 >>> nx.is_isomorphic(G1, G2, edge_match=em, node_match=nm) 

312 True 

313 

314 See Also 

315 -------- 

316 numerical_node_match, numerical_edge_match, numerical_multiedge_match 

317 categorical_node_match, categorical_edge_match, categorical_multiedge_match 

318 

319 References 

320 ---------- 

321 .. [1] L. P. Cordella, P. Foggia, C. Sansone, M. Vento, 

322 "An Improved Algorithm for Matching Large Graphs", 

323 3rd IAPR-TC15 Workshop on Graph-based Representations in 

324 Pattern Recognition, Cuen, pp. 149-159, 2001. 

325 https://www.researchgate.net/publication/200034365_An_Improved_Algorithm_for_Matching_Large_Graphs 

326 """ 

327 if G1.is_directed() and G2.is_directed(): 

328 GM = nx.algorithms.isomorphism.DiGraphMatcher 

329 elif (not G1.is_directed()) and (not G2.is_directed()): 

330 GM = nx.algorithms.isomorphism.GraphMatcher 

331 else: 

332 raise NetworkXError("Graphs G1 and G2 are not of the same type.") 

333 

334 gm = GM(G1, G2, node_match=node_match, edge_match=edge_match) 

335 

336 return gm.is_isomorphic()