patternMatching.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. # coding: utf-8
  2. """
  3. Author: Sten Vercamman
  4. Univeristy of Antwerp
  5. Example code for paper: Efficient model transformations for novices
  6. url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
  7. The main goal of this code is to give an overview, and an understandable
  8. implementation, of known techniques for pattern matching and solving the
  9. sub-graph homomorphism problem. The presented techniques do not include
  10. performance adaptations/optimizations. It is not optimized to be efficient
  11. but rather for the ease of understanding the workings of the algorithms.
  12. The paper does list some possible extensions/optimizations.
  13. It is intended as a guideline, even for novices, and provides an in-depth look
  14. at the workings behind various techniques for efficient pattern matching.
  15. """
  16. import collections
  17. import itertools
  18. class PatternMatching(object):
  19. """
  20. Returns an occurrence of a given pattern from the given Graph
  21. """
  22. def __init__(self, optimize=True):
  23. # store the type of matching we want to use
  24. self.optimize = optimize
  25. def matchNaive(self, pattern, vertices, edges, pattern_vertices=None):
  26. """
  27. Try to find an occurrence of the pattern in the Graph naively.
  28. """
  29. print('matchNaive...')
  30. print('pattern:', pattern)
  31. print('vertices:', vertices)
  32. print('edges:', edges)
  33. print('pattern_vertices:', pattern_vertices)
  34. # allow call with specific arguments
  35. if pattern_vertices == None:
  36. pattern_vertices = pattern.vertices
  37. def visitEdge(pattern_vertices, p_edge, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  38. """
  39. Visit a pattern edge, and try to bind it to a graph edge.
  40. (If the first fails, try the second, and so on...)
  41. """
  42. for g_edge in g_edges:
  43. # only reckon the edge if its in edges and not visited
  44. # (as the graph might be a subgraph of a more complex graph)
  45. if g_edge not in edges.get(g_edge.type, []) or g_edge in visited_g_edges:
  46. continue
  47. if g_edge.type == p_edge.type and g_edge not in visited_g_edges:
  48. visited_p_edges[p_edge] = g_edge
  49. visited_g_edges.add(g_edge)
  50. if inc:
  51. p_vertex = p_edge.src
  52. else:
  53. p_vertex = p_edge.tgt
  54. if visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  55. return True
  56. # remove added edges if they lead to no match, retry with others
  57. del visited_p_edges[p_edge]
  58. visited_g_edges.remove(g_edge)
  59. # no edge leads to a possitive match
  60. return False
  61. def visitEdges(pattern_vertices, p_edges, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  62. """
  63. Visit all edges of the pattern vertex (edges given as argument).
  64. We need to try visiting them for all its permutations, as matching
  65. v -e1-> first and v -e2-> second and v -e3-> third, might not result
  66. in a matching an occurrence of the pattern, but matching v -e2->
  67. first and v -e3-> second and v -e1-> third might.
  68. """
  69. def removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges):
  70. """
  71. Undo the binding of the brevious edge, (the current bindinds do
  72. not lead to an occurrence of the pattern in the graph).
  73. """
  74. for wrong_edge in visitedEdges:
  75. # remove binding (pattern edge to graph edge)
  76. wrong_g_edge = visited_p_edges.get(wrong_edge)
  77. del visited_p_edges[wrong_edge]
  78. # remove visited graph edge
  79. visited_g_edges.remove(wrong_g_edge)
  80. for it in itertools.permutations(p_edges):
  81. visitedEdges = []
  82. foundallEdges = True
  83. for edge in it:
  84. if visited_p_edges.get(edge) == None:
  85. if not visitEdge(pattern_vertices, edge, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  86. # this did not work, so we have to undo all added edges
  87. # (the current edge is not added, as it failed)
  88. # we then can try a different permutation
  89. removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
  90. foundallEdges = False
  91. break # try other order
  92. # add good visited (we know it succeeded)
  93. visitedEdges.append(edge)
  94. else:
  95. # we visited this pattern edge, and have the coressponding graph edge
  96. # if it is an incoming pattern edge, we need to make sure that
  97. # the graph target that is map from the pattern target
  98. # (of this incoming pattern edge, which has to be bound at this point)
  99. # has the graph adge as an incoming edge,
  100. # otherwise the graph is not properly connected
  101. if inc:
  102. if not visited_p_edges[edge] in visited_p_vertices[edge.tgt].incoming_edges:
  103. # did not work
  104. removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
  105. foundallEdges = False
  106. break # try other order
  107. else:
  108. # analog for an outgoing edge
  109. if not visited_p_edges[edge] in visited_p_vertices[edge.src].outgoing_edges:
  110. # did not work
  111. removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
  112. foundallEdges = False
  113. break # try other order
  114. # all edges are good, look no further
  115. if foundallEdges:
  116. break
  117. return foundallEdges
  118. def visitVertex(pattern_vertices, p_vertex, g_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  119. """
  120. Visit a pattern vertex, and try to bind it to the graph vertex
  121. (both are given as argument). A binding is successful if all the
  122. pattern vertex his incoming and outgoing edges can be bound
  123. (to the graph vertex).
  124. """
  125. if g_vertex in visited_g_vertices:
  126. return False
  127. # save visited graph vertex
  128. visited_g_vertices.add(g_vertex)
  129. # map pattern vertex to visited graph vertex
  130. visited_p_vertices[p_vertex] = g_vertex
  131. if visitEdges(pattern_vertices, p_vertex.incoming_edges, True, g_vertex.incoming_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  132. if visitEdges(pattern_vertices, p_vertex.outgoing_edges, False, g_vertex.outgoing_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  133. return True
  134. # cleanup, remove from visited as this does not lead to
  135. # an occurrence of the pttern in the graph
  136. visited_g_vertices.remove(g_vertex)
  137. del visited_p_vertices[p_vertex]
  138. return False
  139. def visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  140. """
  141. Visit a pattern vertex and try to bind a graph vertex to it.
  142. """
  143. # if already matched or if it is a vertex not in the pattern_vertices
  144. # (second is for when you want to match the pattern partionally)
  145. if visited_p_vertices.get(p_vertex) != None or p_vertex not in pattern_vertices.get(p_vertex.type, set()):
  146. return True
  147. # try visiting graph vertices of same type as pattern vertex
  148. for g_vertex in vertices.get(p_vertex.type, []):
  149. if g_vertex not in visited_g_vertices:
  150. if visitVertex(pattern_vertices, p_vertex, g_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  151. return True
  152. return False
  153. visited_p_vertices = {}
  154. visited_p_edges = {}
  155. visited_g_vertices = set()
  156. visited_g_edges = set()
  157. # for loop is need for when pattern consists of multiple not connected structures
  158. allVertices = []
  159. for _, p_vertices in pattern_vertices.items():
  160. allVertices.extend(p_vertices)
  161. foundIt = False
  162. for it_p_vertices in itertools.permutations(allVertices):
  163. foundIt = True
  164. for p_vertex in it_p_vertices:
  165. if not visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
  166. foundIt = False
  167. # reset visited
  168. visited_p_vertices = {}
  169. visited_p_edges = {}
  170. visited_g_vertices = set()
  171. visited_g_edges = set()
  172. break
  173. if foundIt:
  174. break
  175. if foundIt:
  176. return (visited_p_vertices, visited_p_edges)
  177. else:
  178. return None
  179. def createAdjacencyMatrixMap(self, graph, pattern):
  180. """
  181. Return adjacency matrix and the order of the vertices.
  182. """
  183. print('createAdjacencyMatrixMap...')
  184. print('graph:', graph)
  185. print('pattern:', pattern)
  186. matrix = collections.OrderedDict() # { vertex, (index, [has edge from index to pos?]) }
  187. # contains all vertices we'll use for the AdjacencyMatrix
  188. allVertices = []
  189. if self.optimize:
  190. # insert only the vertices from the graph which have a type
  191. # that is present in the pattern
  192. for vertex_type, _ in pattern.vertices.items():
  193. graph_vertices = graph.vertices.get(vertex_type)
  194. if graph_vertices != None:
  195. allVertices.extend(graph_vertices)
  196. else:
  197. # we will not be able to find the pattern
  198. # as the pattern contains a vertex of a certain type
  199. # that is not present in the host graph
  200. return False
  201. else:
  202. # insert all vertices from the graph
  203. for _, vertices in graph.vertices.items():
  204. allVertices.extend(vertices)
  205. # create squared zero matrix
  206. index = 0
  207. for vertex in allVertices:
  208. matrix[vertex] = (index, [False] * len(allVertices))
  209. index += 1
  210. for _, edges in graph.edges.items():
  211. for edge in edges:
  212. if self.optimize:
  213. if edge.tgt not in matrix or edge.src not in matrix:
  214. # skip adding edge if the target or source type
  215. # is not present in the pattern
  216. # (and therefor not added to the matrix)
  217. continue
  218. index = matrix[edge.tgt][0]
  219. matrix[edge.src][1][index] = True
  220. AM = []
  221. vertices_order = []
  222. for vertex, row in matrix.items():
  223. AM.append(row[1])
  224. vertices_order.append(vertex)
  225. return AM, vertices_order
  226. def matchVF2(self, pattern, graph):
  227. print('matchVF2...')
  228. print('pattern:', pattern)
  229. print('graph:', graph)
  230. class VF2_Obj(object):
  231. """
  232. Structor for keeping the VF2 data.
  233. """
  234. def __init__(self, len_graph_vertices, len_pattern_vertices):
  235. # represents if n-the element (h[n] or p[n]) matched
  236. self.core_graph = [False]*len_graph_vertices
  237. self.core_pattern = [False]*len_pattern_vertices
  238. # save mapping from pattern to graph
  239. self.mapping = {}
  240. # preference lvl 1
  241. # ordered set of vertices adjecent to M_graph connected via an outgoing edge
  242. self.N_out_graph = [-1]*len_graph_vertices
  243. # ordered set of vertices adjecent to M_pattern connected via an outgoing edge
  244. self.N_out_pattern = [-1]*len_pattern_vertices
  245. # preference lvl 2
  246. # ordered set of vertices adjecent to M_graph connected via an incoming edge
  247. self.N_inc_graph = [-1]*len_graph_vertices
  248. # ordered set of vertices adjecent to M_pattern connected via an incoming edge
  249. self.N_inc_pattern = [-1]*len_pattern_vertices
  250. # preference lvl 3
  251. # not in the above
  252. def findM(H, P, h, p, VF2_obj, index_M=0):
  253. """
  254. Find an isomorphic mapping for the vertices of P to H.
  255. This mapping is represented by a matrix M if,
  256. and only if M(MH)^T = P^T.
  257. This operates in a simular way as Ullmann. Ullmann has a predefind
  258. order for matching (sorted on most edges first). VF2's order is to
  259. first try to match the adjacency vertices connected via outgoing
  260. edges, then thos connected via incoming edges and then those that
  261. not connected to the currently mathed vertices.
  262. """
  263. def addOutNeighbours(neighbours, N, index_M):
  264. """
  265. Given outgoing neighbours (a row from an adjacency matrix),
  266. label them as added by saving when they got added (index_M
  267. represents this, otherwise it is -1)
  268. """
  269. for neighbour_index in range(0, len(neighbours)):
  270. if neighbours[neighbour_index]:
  271. if N[neighbour_index] == -1:
  272. N[neighbour_index] = index_M
  273. def addIncNeighbours(G, j, N, index_M):
  274. """
  275. Given the adjacency matrix, and the colum j, representing that
  276. we want to add the incoming edges to vertex j,
  277. label them as added by saving when they got added (index_M
  278. represents this, otherwise it is -1)
  279. """
  280. for i in range(0, len(G)):
  281. if G[i][j]:
  282. if N[i] == -1:
  283. N[i] = index_M
  284. def delNeighbours(N, index_M):
  285. """
  286. Remove neighbours that where added at index_M.
  287. If we call this function, we are backtracking and we want to
  288. remove the added neighbours from the just tried matching (n, m)
  289. pair (whiched failed).
  290. """
  291. for n in range(0, len(N)):
  292. if N[n] == index_M:
  293. N[n] = -1
  294. def feasibilityTest(H, P, h, p, VF2_obj, n, m):
  295. """
  296. Examine all the nodes connected to n and m; if such nodes are
  297. in the current partial mapping, check if each branch from or to
  298. n has a corresponding branch from or to m and vice versa.
  299. If the nodes and the branches of the graphs being matched also
  300. carry semantic attributes, another condition must also hold for
  301. F(s, n, m) to be true; namely the attributes of the nodes and of
  302. the branches being paired must be compatible.
  303. Another pruning step is to check if the nr of ext_edges between
  304. the matched_vertices from the pattern and its adjecent vertices
  305. are less than or equal to the nr of ext_edges between
  306. matched_vertices from the graph and its adjecent vertices.
  307. And if the nr of ext_edges between those adjecent vertices from
  308. the pattern and the not connected vertices are less than or
  309. equal to the nr of ext_edges between those adjecent vertices from
  310. the graph and its adjecent vertices.
  311. """
  312. # Get all neighbours from graph node n and pattern node m
  313. # (including n and m)
  314. neighbours_graph = {}
  315. neighbours_graph[h[n].type] = set([h[n]])
  316. neighbours_pattern = {}
  317. neighbours_pattern[p[m].type] = set([p[m]])
  318. # add all neihgbours of pattern vertex m
  319. for i in range(0, len(P)): # P is a nxn-matrix
  320. if (P[m][i] or P[i][m]) and VF2_obj.core_pattern[i]:
  321. neighbours_pattern.setdefault(p[i].type, set()).add(p[i])
  322. # add all neihgbours of graph vertex n
  323. for i in range(0, len(H)): # P is a nxn-matrix
  324. if (H[n][i] or H[i][n]) and VF2_obj.core_graph[i]:
  325. neighbours_graph.setdefault(h[i].type, set()).add(h[i])
  326. # take a coding shortcut,
  327. # use self.matchNaive function to see if it is feasable.
  328. # this way, we immidiatly test the semantic attributes
  329. if not self.matchNaive(pattern, pattern_vertices=neighbours_pattern, vertices=neighbours_graph, edges=graph.edges):
  330. return False
  331. # count ext_edges from core_graph to a adjecent vertices and
  332. # cuotn ext_edges for adjecent vertices and not matched vertices
  333. # connected via the ext_edges
  334. ext_edges_graph_ca = 0
  335. ext_edges_graph_an = 0
  336. # for all core vertices
  337. for x in range(0, len(VF2_obj.core_graph)):
  338. # for all its neighbours
  339. for y in range(0, len(H)):
  340. if H[x][y]:
  341. # if it is a neighbor and not yet matched
  342. if (VF2_obj.N_out_graph[y] != -1 or VF2_obj.N_inc_graph[y] != -1) and VF2_obj.core_graph[y]:
  343. # if we matched it
  344. if VF2_obj.core_graph[x] != -1:
  345. ext_edges_graph_ca += 1
  346. else:
  347. ext_edges_graph_an += 1
  348. # count ext_edges from core_pattern to a adjecent vertices
  349. # connected via the ext_edges
  350. ext_edges_pattern_ca = 0
  351. ext_edges_pattern_an = 0
  352. # for all core vertices
  353. for x in range(0, len(VF2_obj.core_pattern)):
  354. # for all its neighbours
  355. for y in range(0, len(P)):
  356. if P[x][y]:
  357. # if it is a neighbor and not yet matched
  358. if (VF2_obj.N_out_pattern[y] != -1 or VF2_obj.N_inc_pattern[y] != -1) and VF2_obj.core_pattern[y]:
  359. # if we matched it
  360. if VF2_obj.core_pattern[x] != -1:
  361. ext_edges_pattern_ca += 1
  362. else:
  363. ext_edges_pattern_an += 1
  364. # The nr of ext_edges between matched_vertices from the pattern
  365. # and its adjecent vertices must be less than or equal to the nr
  366. # of ext_edges between matched_vertices from the graph and its
  367. # adjecent vertices, otherwise we wont find an occurrence
  368. if ext_edges_pattern_ca > ext_edges_graph_ca:
  369. return False
  370. # The nr of ext_edges between those adjancent vertices from the
  371. # pattern and its not connected vertices must be less than or
  372. # equal to the nr of ext_edges between those adjacent vertices
  373. # from the graph and its not connected vertices,
  374. # otherwise we wont find an occurrence
  375. if ext_edges_pattern_an > ext_edges_graph_an:
  376. return False
  377. return True
  378. def matchPhase(H, P, h, p, index_M, VF2_obj, n, m):
  379. """
  380. The matching fase of the VF2 algorithm. If the chosen n, m pair
  381. passes the feasibilityTest, the pair gets added and we start
  382. to search for the next matching pair.
  383. """
  384. # all candidate pair (n, m) represent graph x pattern
  385. candidate = frozenset(itertools.chain(
  386. ((i, j) for i,j in VF2_obj.mapping.items()),
  387. # ((self.reverseMapH[i], self.reverseMapP[j]) for i,j in VF2_obj.mapping.items()),
  388. [(h[n],p[m])],
  389. ))
  390. if candidate in self.alreadyVisited:
  391. # print(self.indent*" ", "candidate:", candidate)
  392. # for match in self.alreadyVisited.get(index_M, []):
  393. # if match == candidate:
  394. return False # already visited this (partial) match -> skip
  395. if feasibilityTest(H, P, h, p, VF2_obj, n, m):
  396. print(self.indent*" ","adding to match:", n, "->", m)
  397. # adapt VF2_obj
  398. VF2_obj.core_graph[n] = True
  399. VF2_obj.core_pattern[m] = True
  400. VF2_obj.mapping[h[n]] = p[m]
  401. addOutNeighbours(H[n], VF2_obj.N_out_graph, index_M)
  402. addIncNeighbours(H, n, VF2_obj.N_inc_graph, index_M)
  403. addOutNeighbours(P[m], VF2_obj.N_out_pattern, index_M)
  404. addIncNeighbours(P, m, VF2_obj.N_inc_pattern, index_M)
  405. if index_M > 0:
  406. # remember our partial match (shallow copy) so we don't visit it again
  407. self.alreadyVisited.add(frozenset([ (i, j) for i,j in VF2_obj.mapping.items()]))
  408. # self.alreadyVisited.setdefault(index_M, set()).add(frozenset([ (self.reverseMapH[i], self.reverseMapP[j]) for i,j in VF2_obj.mapping.items()]))
  409. # print(self.alreadyVisited)
  410. self.indent += 1
  411. matched = yield from findM(H, P, h, p, VF2_obj, index_M + 1)
  412. if matched:
  413. # return True
  414. # print(self.indent*" ","found match", len(self.results), ", continuing...")
  415. pass
  416. self.indent -= 1
  417. if True:
  418. # else:
  419. print(self.indent*" ","backtracking... remove", n, "->", m)
  420. # else, backtrack, adapt VF2_obj
  421. VF2_obj.core_graph[n] = False
  422. VF2_obj.core_pattern[m] = False
  423. del VF2_obj.mapping[h[n]]
  424. delNeighbours(VF2_obj.N_out_graph, index_M)
  425. delNeighbours(VF2_obj.N_inc_graph, index_M)
  426. delNeighbours(VF2_obj.N_out_pattern, index_M)
  427. delNeighbours(VF2_obj.N_inc_pattern, index_M)
  428. return False
  429. def preferred(H, P, h, p, index_M, VF2_obj, N_graph, N_pattern):
  430. """
  431. Try to match the adjacency vertices connected via outgoing
  432. or incoming edges. (Depending on what is given for N_graph and
  433. N_pattern.)
  434. """
  435. for n in range(0, len(N_graph)):
  436. # skip graph vertices that are not in VF2_obj.N_out_graph
  437. # (or already matched)
  438. if N_graph[n] == -1 or VF2_obj.core_graph[n]:
  439. # print(self.indent*" "," skipping")
  440. continue
  441. print(self.indent*" "," n:", n)
  442. for m in range(0, len(N_pattern)):
  443. # skip graph vertices that are not in VF2_obj.N_out_pattern
  444. # (or already matched)
  445. if N_pattern[m] == -1 or VF2_obj.core_pattern[m]:
  446. continue
  447. print(self.indent*" "," m:", m)
  448. matched = yield from matchPhase(H, P, h, p, index_M, VF2_obj, n, m)
  449. if matched:
  450. return True
  451. return False
  452. def leastPreferred(H, P, h, p, index_M, VF2_obj):
  453. """
  454. Try to match the vertices that are not connected to the curretly
  455. matched vertices.
  456. """
  457. for n in range(0, len(VF2_obj.N_out_graph)):
  458. # skip vertices that are connected to the graph
  459. # (or already matched)
  460. if not (VF2_obj.N_out_graph[n] == -1 and VF2_obj.N_inc_graph[n] == -1) or VF2_obj.core_graph[n]:
  461. # print(self.indent*" "," skipping")
  462. continue
  463. print(" n:", n)
  464. for m in range(0, len(VF2_obj.N_out_pattern)):
  465. # skip vertices that are connected to the graph
  466. # (or already matched)
  467. if not (VF2_obj.N_out_pattern[m] == -1 and VF2_obj.N_inc_pattern[m] == -1) or VF2_obj.core_pattern[m]:
  468. # print(self.indent*" "," skipping")
  469. continue
  470. print(self.indent*" "," m:", m)
  471. matched = yield from matchPhase(H, P, h, p, index_M, VF2_obj, n, m)
  472. if matched:
  473. return True
  474. return False
  475. print(self.indent*" ","index_M:", index_M)
  476. # We are at the end, we found an candidate.
  477. if index_M == len(p):
  478. print(self.indent*" ","end...")
  479. bound_graph_vertices = {}
  480. for vertex_bound, _ in VF2_obj.mapping.items():
  481. bound_graph_vertices.setdefault(vertex_bound.type, set()).add(vertex_bound)
  482. result = self.matchNaive(pattern, vertices=bound_graph_vertices, edges=graph.edges)
  483. if result != None:
  484. yield result
  485. return result != None
  486. if index_M > 0:
  487. # try the candidates is the preffered order
  488. # first try the adjacent vertices connected via the outgoing edges.
  489. print(self.indent*" ","preferred L1")
  490. matched = yield from preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_out_graph, VF2_obj.N_out_pattern)
  491. if matched:
  492. return True
  493. print(self.indent*" ","preferred L2")
  494. # then try the adjacent vertices connected via the incoming edges.
  495. matched = yield from preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_inc_graph, VF2_obj.N_inc_pattern)
  496. if matched:
  497. return True
  498. print(self.indent*" ","leastPreferred")
  499. # and lastly, try the vertices not connected to the currently matched vertices
  500. matched = yield from leastPreferred(H, P, h, p, index_M, VF2_obj)
  501. if matched:
  502. return True
  503. return False
  504. # create adjecency matrix of the graph
  505. H, h = self.createAdjacencyMatrixMap(graph, pattern)
  506. # create adjecency matrix of the pattern
  507. P, p = self.createAdjacencyMatrixMap(pattern, pattern)
  508. VF2_obj = VF2_Obj(len(h), len(p))
  509. # Only for debugging:
  510. self.indent = 0
  511. self.reverseMapH = { h[i] : i for i in range(len(h))}
  512. self.reverseMapP = { p[i] : i for i in range(len(p))}
  513. # Set of partial matches already explored - prevents us from producing the same match multiple times
  514. # Encoded as a mapping from match size to the partial match
  515. self.alreadyVisited = set()
  516. yield from findM(H, P, h, p, VF2_obj)