You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ged_env.py 28 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Created on Wed Jun 17 12:02:36 2020
  5. @author: ljia
  6. """
  7. import numpy as np
  8. import networkx as nx
  9. from gklearn.ged.env import Options, OptionsStringMap
  10. from gklearn.ged.env import GEDData
  11. class GEDEnv(object):
  12. def __init__(self):
  13. self._initialized = False
  14. self._new_graph_ids = []
  15. self._ged_data = GEDData()
  16. # Variables needed for approximating ged_instance_.
  17. self._lower_bounds = {}
  18. self._upper_bounds = {}
  19. self._runtimes = {}
  20. self._node_maps = {}
  21. self._original_to_internal_node_ids = []
  22. self._internal_to_original_node_ids = []
  23. self._ged_method = None
  24. def set_edit_cost(self, edit_cost, edit_cost_constants=[]):
  25. """
  26. /*!
  27. * @brief Sets the edit costs to one of the predefined edit costs.
  28. * @param[in] edit_costs Select one of the predefined edit costs.
  29. * @param[in] edit_cost_constants Constants passed to the constructor of the edit cost class selected by @p edit_costs.
  30. */
  31. """
  32. self._ged_data._set_edit_cost(edit_cost, edit_cost_constants)
  33. def add_graph(self, graph_name='', graph_class=''):
  34. """
  35. /*!
  36. * @brief Adds a new uninitialized graph to the environment. Call init() after calling this method.
  37. * @param[in] graph_name The name of the added graph. Empty if not specified.
  38. * @param[in] graph_class The class of the added graph. Empty if not specified.
  39. * @return The ID of the newly added graph.
  40. */
  41. """
  42. # @todo: graphs are not uninitialized.
  43. self._initialized = False
  44. graph_id = self._ged_data._num_graphs_without_shuffled_copies
  45. self._ged_data._num_graphs_without_shuffled_copies += 1
  46. self._new_graph_ids.append(graph_id)
  47. self._ged_data._graphs.append(nx.Graph())
  48. self._ged_data._graph_names.append(graph_name)
  49. self._ged_data._graph_classes.append(graph_class)
  50. self._original_to_internal_node_ids.append({})
  51. self._internal_to_original_node_ids.append({})
  52. self._ged_data._strings_to_internal_node_ids.append({})
  53. self._ged_data._internal_node_ids_to_strings.append({})
  54. return graph_id
  55. def clear_graph(self, graph_id):
  56. """
  57. /*!
  58. * @brief Clears and de-initializes a graph that has previously been added to the environment. Call init() after calling this method.
  59. * @param[in] graph_id ID of graph that has to be cleared.
  60. */
  61. """
  62. if graph_id > self._ged_data.num_graphs_without_shuffled_copies():
  63. raise Exception('The graph', self.get_graph_name(graph_id), 'has not been added to the environment.')
  64. self._ged_data._graphs[graph_id].clear()
  65. self._original_to_internal_node_ids[graph_id].clear()
  66. self._internal_to_original_node_ids[graph_id].clear()
  67. self._ged_data._strings_to_internal_node_ids[graph_id].clear()
  68. self._ged_data._internal_node_ids_to_strings[graph_id].clear()
  69. self._initialized = False
  70. def add_node(self, graph_id, node_id, node_label):
  71. """
  72. /*!
  73. * @brief Adds a labeled node.
  74. * @param[in] graph_id ID of graph that has been added to the environment.
  75. * @param[in] node_id The user-specific ID of the vertex that has to be added.
  76. * @param[in] node_label The label of the vertex that has to be added. Set to ged::NoLabel() if template parameter @p UserNodeLabel equals ged::NoLabel.
  77. */
  78. """
  79. # @todo: check ids.
  80. self._initialized = False
  81. internal_node_id = nx.number_of_nodes(self._ged_data._graphs[graph_id])
  82. self._ged_data._graphs[graph_id].add_node(internal_node_id, label=node_label)
  83. self._original_to_internal_node_ids[graph_id][node_id] = internal_node_id
  84. self._internal_to_original_node_ids[graph_id][internal_node_id] = node_id
  85. self._ged_data._strings_to_internal_node_ids[graph_id][str(node_id)] = internal_node_id
  86. self._ged_data._internal_node_ids_to_strings[graph_id][internal_node_id] = str(node_id)
  87. self._ged_data._node_label_to_id(node_label)
  88. label_id = self._ged_data._node_label_to_id(node_label)
  89. # @todo: ged_data_.graphs_[graph_id].set_label
  90. def add_edge(self, graph_id, nd_from, nd_to, edge_label, ignore_duplicates=True):
  91. """
  92. /*!
  93. * @brief Adds a labeled edge.
  94. * @param[in] graph_id ID of graph that has been added to the environment.
  95. * @param[in] tail The user-specific ID of the tail of the edge that has to be added.
  96. * @param[in] head The user-specific ID of the head of the edge that has to be added.
  97. * @param[in] edge_label The label of the vertex that has to be added. Set to ged::NoLabel() if template parameter @p UserEdgeLabel equals ged::NoLabel.
  98. * @param[in] ignore_duplicates If @p true, duplicate edges are ignores. Otherwise, an exception is thrown if an existing edge is added to the graph.
  99. */
  100. """
  101. # @todo: check everything.
  102. self._initialized = False
  103. # @todo: check ignore_duplicates.
  104. self._ged_data._graphs[graph_id].add_edge(self._original_to_internal_node_ids[graph_id][nd_from], self._original_to_internal_node_ids[graph_id][nd_to], label=edge_label)
  105. label_id = self._ged_data._edge_label_to_id(edge_label)
  106. # @todo: ged_data_.graphs_[graph_id].set_label
  107. def add_nx_graph(self, g, classe, ignore_duplicates=True) :
  108. """
  109. Add a Graph (made by networkx) on the environment. Be careful to respect the same format as GXL graphs for labelling nodes and edges.
  110. :param g: The graph to add (networkx graph)
  111. :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default
  112. :type g: networkx.graph
  113. :type ignore_duplicates: bool
  114. :return: The ID of the newly added graphe
  115. :rtype: size_t
  116. .. note:: The NX graph must respect the GXL structure. Please see how a GXL graph is construct.
  117. """
  118. graph_id = self.add_graph(g.name, classe) # check if the graph name already exists.
  119. for node in g.nodes: # @todo: if the keys of labels include int and str at the same time.
  120. self.add_node(graph_id, node, tuple(sorted(g.nodes[node].items(), key=lambda kv: kv[0])))
  121. for edge in g.edges:
  122. self.add_edge(graph_id, edge[0], edge[1], tuple(sorted(g.edges[(edge[0], edge[1])].items(), key=lambda kv: kv[0])), ignore_duplicates)
  123. return graph_id
  124. def load_nx_graph(self, nx_graph, graph_id, graph_name='', graph_class=''):
  125. """
  126. Loads NetworkX Graph into the GED environment.
  127. Parameters
  128. ----------
  129. nx_graph : NetworkX Graph object
  130. The graph that should be loaded.
  131. graph_id : int or None
  132. The ID of a graph contained the environment (overwrite existing graph) or add new graph if `None`.
  133. graph_name : string, optional
  134. The name of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`.
  135. graph_class : string, optional
  136. The class of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`.
  137. Returns
  138. -------
  139. int
  140. The ID of the newly loaded graph.
  141. """
  142. if graph_id is None: # @todo: undefined.
  143. graph_id = self.add_graph(graph_name, graph_class)
  144. else:
  145. self.clear_graph(graph_id)
  146. for node in nx_graph.nodes:
  147. self.add_node(graph_id, node, tuple(sorted(nx_graph.nodes[node].items(), key=lambda kv: kv[0])))
  148. for edge in nx_graph.edges:
  149. self.add_edge(graph_id, edge[0], edge[1], tuple(sorted(nx_graph.edges[(edge[0], edge[1])].items(), key=lambda kv: kv[0])))
  150. return graph_id
  151. def init(self, init_type=Options.InitType.EAGER_WITHOUT_SHUFFLED_COPIES, print_to_stdout=False):
  152. if isinstance(init_type, str):
  153. init_type = OptionsStringMap.InitType[init_type]
  154. # Throw an exception if no edit costs have been selected.
  155. if self._ged_data._edit_cost is None:
  156. raise Exception('No edit costs have been selected. Call set_edit_cost() before calling init().')
  157. # Return if the environment is initialized.
  158. if self._initialized:
  159. return
  160. # Set initialization type.
  161. self._ged_data._init_type = init_type
  162. # @todo: Construct shuffled graph copies if necessary.
  163. # Re-initialize adjacency matrices (also previously initialized graphs must be re-initialized because of possible re-allocation).
  164. # @todo: setup_adjacency_matrix, don't know if neccessary.
  165. self._ged_data._max_num_nodes = np.max([nx.number_of_nodes(g) for g in self._ged_data._graphs])
  166. self._ged_data._max_num_edges = np.max([nx.number_of_edges(g) for g in self._ged_data._graphs])
  167. # Initialize cost matrices if necessary.
  168. if self._ged_data._eager_init():
  169. pass # @todo: init_cost_matrices_: 1. Update node cost matrix if new node labels have been added to the environment; 2. Update edge cost matrix if new edge labels have been added to the environment.
  170. # Mark environment as initialized.
  171. self._initialized = True
  172. self._new_graph_ids.clear()
  173. def is_initialized(self):
  174. """
  175. /*!
  176. * @brief Check if the environment is initialized.
  177. * @return True if the environment is initialized.
  178. */
  179. """
  180. return self._initialized
  181. def get_init_type(self):
  182. """
  183. /*!
  184. * @brief Returns the initialization type of the last initialization.
  185. * @return Initialization type.
  186. */
  187. """
  188. return self._ged_data._init_type
  189. def set_label_costs(self, node_label_costs=None, edge_label_costs=None):
  190. """Set the costs between labels.
  191. """
  192. if node_label_costs is not None:
  193. self._ged_data._node_label_costs = node_label_costs
  194. if edge_label_costs is not None:
  195. self._ged_data._edge_label_costs = edge_label_costs
  196. def set_method(self, method, options=''):
  197. """
  198. /*!
  199. * @brief Sets the GEDMethod to be used by run_method().
  200. * @param[in] method Select the method that is to be used.
  201. * @param[in] options An options string of the form @"[--@<option@> @<arg@>] [...]@" passed to the selected method.
  202. */
  203. """
  204. del self._ged_method
  205. if isinstance(method, str):
  206. method = OptionsStringMap.GEDMethod[method]
  207. if method == Options.GEDMethod.BRANCH:
  208. self._ged_method = Branch(self._ged_data)
  209. elif method == Options.GEDMethod.BRANCH_FAST:
  210. self._ged_method = BranchFast(self._ged_data)
  211. elif method == Options.GEDMethod.BRANCH_FAST:
  212. self._ged_method = BranchFast(self._ged_data)
  213. elif method == Options.GEDMethod.BRANCH_TIGHT:
  214. self._ged_method = BranchTight(self._ged_data)
  215. elif method == Options.GEDMethod.BRANCH_UNIFORM:
  216. self._ged_method = BranchUniform(self._ged_data)
  217. elif method == Options.GEDMethod.BRANCH_COMPACT:
  218. self._ged_method = BranchCompact(self._ged_data)
  219. elif method == Options.GEDMethod.PARTITION:
  220. self._ged_method = Partition(self._ged_data)
  221. elif method == Options.GEDMethod.HYBRID:
  222. self._ged_method = Hybrid(self._ged_data)
  223. elif method == Options.GEDMethod.RING:
  224. self._ged_method = Ring(self._ged_data)
  225. elif method == Options.GEDMethod.ANCHOR_AWARE_GED:
  226. self._ged_method = AnchorAwareGED(self._ged_data)
  227. elif method == Options.GEDMethod.WALKS:
  228. self._ged_method = Walks(self._ged_data)
  229. elif method == Options.GEDMethod.IPFP:
  230. self._ged_method = IPFP(self._ged_data)
  231. elif method == Options.GEDMethod.BIPARTITE:
  232. from gklearn.ged.methods import Bipartite
  233. self._ged_method = Bipartite(self._ged_data)
  234. elif method == Options.GEDMethod.SUBGRAPH:
  235. self._ged_method = Subgraph(self._ged_data)
  236. elif method == Options.GEDMethod.NODE:
  237. self._ged_method = Node(self._ged_data)
  238. elif method == Options.GEDMethod.RING_ML:
  239. self._ged_method = RingML(self._ged_data)
  240. elif method == Options.GEDMethod.BIPARTITE_ML:
  241. self._ged_method = BipartiteML(self._ged_data)
  242. elif method == Options.GEDMethod.REFINE:
  243. self._ged_method = Refine(self._ged_data)
  244. elif method == Options.GEDMethod.BP_BEAM:
  245. self._ged_method = BPBeam(self._ged_data)
  246. elif method == Options.GEDMethod.SIMULATED_ANNEALING:
  247. self._ged_method = SimulatedAnnealing(self._ged_data)
  248. elif method == Options.GEDMethod.HED:
  249. self._ged_method = HED(self._ged_data)
  250. elif method == Options.GEDMethod.STAR:
  251. self._ged_method = STAR(self._ged_data)
  252. # #ifdef GUROBI
  253. elif method == Options.GEDMethod.F1:
  254. self._ged_method = F1(self._ged_data)
  255. elif method == Options.GEDMethod.F2:
  256. self._ged_method = F2(self._ged_data)
  257. elif method == Options.GEDMethod.COMPACT_MIP:
  258. self._ged_method = CompactMIP(self._ged_data)
  259. elif method == Options.GEDMethod.BLP_NO_EDGE_LABELS:
  260. self._ged_method = BLPNoEdgeLabels(self._ged_data)
  261. self._ged_method.set_options(options)
  262. def run_method(self, g_id, h_id):
  263. """
  264. /*!
  265. * @brief Runs the GED method specified by call to set_method() between the graphs with IDs @p g_id and @p h_id.
  266. * @param[in] g_id ID of an input graph that has been added to the environment.
  267. * @param[in] h_id ID of an input graph that has been added to the environment.
  268. */
  269. """
  270. if g_id >= self._ged_data.num_graphs():
  271. raise Exception('The graph with ID', str(g_id), 'has not been added to the environment.')
  272. if h_id >= self._ged_data.num_graphs():
  273. raise Exception('The graph with ID', str(h_id), 'has not been added to the environment.')
  274. if not self._initialized:
  275. raise Exception('The environment is uninitialized. Call init() after adding all graphs to the environment.')
  276. if self._ged_method is None:
  277. raise Exception('No method has been set. Call set_method() before calling run().')
  278. # Call selected GEDMethod and store results.
  279. if self._ged_data.shuffled_graph_copies_available() and (g_id == h_id):
  280. self._ged_method.run(g_id, self._ged_data.id_shuffled_graph_copy(h_id)) # @todo: why shuffle?
  281. else:
  282. self._ged_method.run(g_id, h_id)
  283. self._lower_bounds[(g_id, h_id)] = self._ged_method.get_lower_bound()
  284. self._upper_bounds[(g_id, h_id)] = self._ged_method.get_upper_bound()
  285. self._runtimes[(g_id, h_id)] = self._ged_method.get_runtime()
  286. self._node_maps[(g_id, h_id)] = self._ged_method.get_node_map()
  287. def init_method(self):
  288. """Initializes the method specified by call to set_method().
  289. """
  290. if not self._initialized:
  291. raise Exception('The environment is uninitialized. Call init() before calling init_method().')
  292. if self._ged_method is None:
  293. raise Exception('No method has been set. Call set_method() before calling init_method().')
  294. self._ged_method.init()
  295. def get_num_node_labels(self):
  296. """
  297. /*!
  298. * @brief Returns the number of node labels.
  299. * @return Number of pairwise different node labels contained in the environment.
  300. * @note If @p 1 is returned, the nodes are unlabeled.
  301. */
  302. """
  303. return len(self._ged_data._node_labels)
  304. def get_all_node_labels(self):
  305. """
  306. /*!
  307. * @brief Returns the list of all node labels.
  308. * @return List of pairwise different node labels contained in the environment.
  309. * @note If @p 1 is returned, the nodes are unlabeled.
  310. */
  311. """
  312. return self._ged_data._node_labels
  313. def get_node_label(self, label_id, to_dict=True):
  314. """
  315. /*!
  316. * @brief Returns node label.
  317. * @param[in] label_id ID of node label that should be returned. Must be between 1 and num_node_labels().
  318. * @return Node label for selected label ID.
  319. */
  320. """
  321. if label_id < 1 or label_id > self.get_num_node_labels():
  322. raise Exception('The environment does not contain a node label with ID', str(label_id), '.')
  323. if to_dict:
  324. return dict(self._ged_data._node_labels[label_id - 1])
  325. return self._ged_data._node_labels[label_id - 1]
  326. def get_num_edge_labels(self):
  327. """
  328. /*!
  329. * @brief Returns the number of edge labels.
  330. * @return Number of pairwise different edge labels contained in the environment.
  331. * @note If @p 1 is returned, the edges are unlabeled.
  332. */
  333. """
  334. return len(self._ged_data._edge_labels)
  335. def get_all_edge_labels(self):
  336. """
  337. /*!
  338. * @brief Returns the list of all edge labels.
  339. * @return List of pairwise different edge labels contained in the environment.
  340. * @note If @p 1 is returned, the edges are unlabeled.
  341. */
  342. """
  343. return self._ged_data._edge_labels
  344. def get_edge_label(self, label_id, to_dict=True):
  345. """
  346. /*!
  347. * @brief Returns edge label.
  348. * @param[in] label_id ID of edge label that should be returned. Must be between 1 and num_node_labels().
  349. * @return Edge label for selected label ID.
  350. */
  351. """
  352. if label_id < 1 or label_id > self.get_num_edge_labels():
  353. raise Exception('The environment does not contain an edge label with ID', str(label_id), '.')
  354. if to_dict:
  355. return dict(self._ged_data._edge_labels[label_id - 1])
  356. return self._ged_data._edge_labels[label_id - 1]
  357. def get_upper_bound(self, g_id, h_id):
  358. """
  359. /*!
  360. * @brief Returns upper bound for edit distance between the input graphs.
  361. * @param[in] g_id ID of an input graph that has been added to the environment.
  362. * @param[in] h_id ID of an input graph that has been added to the environment.
  363. * @return Upper bound computed by the last call to run_method() with arguments @p g_id and @p h_id.
  364. */
  365. """
  366. if (g_id, h_id) not in self._upper_bounds:
  367. raise Exception('Call run(' + str(g_id) + ',' + str(h_id) + ') before calling get_upper_bound(' + str(g_id) + ',' + str(h_id) + ').')
  368. return self._upper_bounds[(g_id, h_id)]
  369. def get_lower_bound(self, g_id, h_id):
  370. """
  371. /*!
  372. * @brief Returns lower bound for edit distance between the input graphs.
  373. * @param[in] g_id ID of an input graph that has been added to the environment.
  374. * @param[in] h_id ID of an input graph that has been added to the environment.
  375. * @return Lower bound computed by the last call to run_method() with arguments @p g_id and @p h_id.
  376. */
  377. """
  378. if (g_id, h_id) not in self._lower_bounds:
  379. raise Exception('Call run(' + str(g_id) + ',' + str(h_id) + ') before calling get_lower_bound(' + str(g_id) + ',' + str(h_id) + ').')
  380. return self._lower_bounds[(g_id, h_id)]
  381. def get_runtime(self, g_id, h_id):
  382. """
  383. /*!
  384. * @brief Returns runtime.
  385. * @param[in] g_id ID of an input graph that has been added to the environment.
  386. * @param[in] h_id ID of an input graph that has been added to the environment.
  387. * @return Runtime of last call to run_method() with arguments @p g_id and @p h_id.
  388. */
  389. """
  390. if (g_id, h_id) not in self._runtimes:
  391. raise Exception('Call run(' + str(g_id) + ',' + str(h_id) + ') before calling get_runtime(' + str(g_id) + ',' + str(h_id) + ').')
  392. return self._runtimes[(g_id, h_id)]
  393. def get_init_time(self):
  394. """
  395. /*!
  396. * @brief Returns initialization time.
  397. * @return Runtime of the last call to init_method().
  398. */
  399. """
  400. return self._ged_method.get_init_time()
  401. def get_node_map(self, g_id, h_id):
  402. """
  403. /*!
  404. * @brief Returns node map between the input graphs.
  405. * @param[in] g_id ID of an input graph that has been added to the environment.
  406. * @param[in] h_id ID of an input graph that has been added to the environment.
  407. * @return Node map computed by the last call to run_method() with arguments @p g_id and @p h_id.
  408. */
  409. """
  410. if (g_id, h_id) not in self._node_maps:
  411. raise Exception('Call run(' + str(g_id) + ',' + str(h_id) + ') before calling get_node_map(' + str(g_id) + ',' + str(h_id) + ').')
  412. return self._node_maps[(g_id, h_id)]
  413. def get_forward_map(self, g_id, h_id) :
  414. """
  415. Returns the forward map (or the half of the adjacence matrix) between nodes of the two indicated graphs.
  416. :param g: The Id of the first compared graph
  417. :param h: The Id of the second compared graph
  418. :type g: size_t
  419. :type h: size_t
  420. :return: The forward map to the adjacence matrix between nodes of the two graphs
  421. :rtype: list[npy_uint32]
  422. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_backward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix()
  423. .. warning:: run_method() between the same two graph must be called before this function.
  424. .. note:: I don't know how to connect the two map to reconstruct the adjacence matrix. Please come back when I know how it's work !
  425. """
  426. return self.get_node_map(g_id, h_id).forward_map
  427. def get_backward_map(self, g_id, h_id) :
  428. """
  429. Returns the backward map (or the half of the adjacence matrix) between nodes of the two indicated graphs.
  430. :param g: The Id of the first compared graph
  431. :param h: The Id of the second compared graph
  432. :type g: size_t
  433. :type h: size_t
  434. :return: The backward map to the adjacence matrix between nodes of the two graphs
  435. :rtype: list[npy_uint32]
  436. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix()
  437. .. warning:: run_method() between the same two graph must be called before this function.
  438. .. note:: I don't know how to connect the two map to reconstruct the adjacence matrix. Please come back when I know how it's work !
  439. """
  440. return self.get_node_map(g_id, h_id).backward_map
  441. def compute_induced_cost(self, g_id, h_id, node_map):
  442. """
  443. /*!
  444. * @brief Computes the edit cost between two graphs induced by a node map.
  445. * @param[in] g_id ID of input graph.
  446. * @param[in] h_id ID of input graph.
  447. * @param[in,out] node_map Node map whose induced edit cost is to be computed.
  448. */
  449. """
  450. self._ged_data.compute_induced_cost(self._ged_data._graphs[g_id], self._ged_data._graphs[h_id], node_map)
  451. def get_nx_graph(self, graph_id):
  452. """
  453. * @brief Returns NetworkX.Graph() representation.
  454. * @param[in] graph_id ID of the selected graph.
  455. """
  456. graph = nx.Graph() # @todo: add graph attributes.
  457. graph.graph['id'] = graph_id
  458. nb_nodes = self.get_graph_num_nodes(graph_id)
  459. original_node_ids = self.get_original_node_ids(graph_id)
  460. node_labels = self.get_graph_node_labels(graph_id, to_dict=True)
  461. graph.graph['original_node_ids'] = original_node_ids
  462. for node_id in range(0, nb_nodes):
  463. graph.add_node(node_id, **node_labels[node_id])
  464. edges = self.get_graph_edges(graph_id, to_dict=True)
  465. for (head, tail), labels in edges.items():
  466. graph.add_edge(head, tail, **labels)
  467. return graph
  468. def get_graph_node_labels(self, graph_id, to_dict=True):
  469. """
  470. Searchs and returns all the labels of nodes on a graph, selected by its ID.
  471. :param graph_id: The ID of the wanted graph
  472. :type graph_id: size_t
  473. :return: The list of nodes' labels on the selected graph
  474. :rtype: list[dict{string : string}]
  475. .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_edges(), get_graph_adjacence_matrix()
  476. .. note:: These functions allow to collect all the graph's informations.
  477. """
  478. graph = self._ged_data.graph(graph_id)
  479. node_labels = []
  480. for n in graph.nodes():
  481. node_labels.append(graph.nodes[n]['label'])
  482. if to_dict:
  483. return [dict(i) for i in node_labels]
  484. return node_labels
  485. def get_graph_edges(self, graph_id, to_dict=True):
  486. """
  487. Searchs and returns all the edges on a graph, selected by its ID.
  488. :param graph_id: The ID of the wanted graph
  489. :type graph_id: size_t
  490. :return: The list of edges on the selected graph
  491. :rtype: dict{tuple(size_t, size_t) : dict{string : string}}
  492. .. seealso::get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_adjacence_matrix()
  493. .. note:: These functions allow to collect all the graph's informations.
  494. """
  495. graph = self._ged_data.graph(graph_id)
  496. if to_dict:
  497. edges = {}
  498. for n1, n2, attr in graph.edges(data=True):
  499. edges[(n1, n2)] = dict(attr['label'])
  500. return edges
  501. return {(n1, n2): attr['label'] for n1, n2, attr in graph.edges(data=True)}
  502. def get_graph_name(self, graph_id):
  503. """
  504. /*!
  505. * @brief Returns the graph name.
  506. * @param[in] graph_id ID of an input graph that has been added to the environment.
  507. * @return Name of the input graph.
  508. */
  509. """
  510. return self._ged_data._graph_names[graph_id]
  511. def get_graph_num_nodes(self, graph_id):
  512. """
  513. /*!
  514. * @brief Returns the number of nodes.
  515. * @param[in] graph_id ID of an input graph that has been added to the environment.
  516. * @return Number of nodes in the graph.
  517. */
  518. """
  519. return nx.number_of_nodes(self._ged_data.graph(graph_id))
  520. def get_original_node_ids(self, graph_id):
  521. """
  522. Searchs and returns all th Ids of nodes on a graph, selected by its ID.
  523. :param graph_id: The ID of the wanted graph
  524. :type graph_id: size_t
  525. :return: The list of IDs's nodes on the selected graph
  526. :rtype: list[string]
  527. .. seealso::get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix()
  528. .. note:: These functions allow to collect all the graph's informations.
  529. """
  530. return [i for i in self._internal_to_original_node_ids[graph_id].values()]
  531. def get_node_cost(self, node_label_1, node_label_2):
  532. return self._ged_data.node_cost(node_label_1, node_label_2)
  533. def get_node_rel_cost(self, node_label_1, node_label_2):
  534. """
  535. /*!
  536. * @brief Returns node relabeling cost.
  537. * @param[in] node_label_1 First node label.
  538. * @param[in] node_label_2 Second node label.
  539. * @return Node relabeling cost for the given node labels.
  540. */
  541. """
  542. if isinstance(node_label_1, dict):
  543. node_label_1 = tuple(sorted(node_label_1.items(), key=lambda kv: kv[0]))
  544. if isinstance(node_label_2, dict):
  545. node_label_2 = tuple(sorted(node_label_2.items(), key=lambda kv: kv[0]))
  546. return self._ged_data._edit_cost.node_rel_cost_fun(node_label_1, node_label_2) # @todo: may need to use node_cost() instead (or change node_cost() and modify ged_method for pre-defined cost matrices.)
  547. def get_node_del_cost(self, node_label):
  548. """
  549. /*!
  550. * @brief Returns node deletion cost.
  551. * @param[in] node_label Node label.
  552. * @return Cost of deleting node with given label.
  553. */
  554. """
  555. if isinstance(node_label, dict):
  556. node_label = tuple(sorted(node_label.items(), key=lambda kv: kv[0]))
  557. return self._ged_data._edit_cost.node_del_cost_fun(node_label)
  558. def get_node_ins_cost(self, node_label):
  559. """
  560. /*!
  561. * @brief Returns node insertion cost.
  562. * @param[in] node_label Node label.
  563. * @return Cost of inserting node with given label.
  564. */
  565. """
  566. if isinstance(node_label, dict):
  567. node_label = tuple(sorted(node_label.items(), key=lambda kv: kv[0]))
  568. return self._ged_data._edit_cost.node_ins_cost_fun(node_label)
  569. def get_edge_cost(self, edge_label_1, edge_label_2):
  570. return self._ged_data.edge_cost(edge_label_1, edge_label_2)
  571. def get_edge_rel_cost(self, edge_label_1, edge_label_2):
  572. """
  573. /*!
  574. * @brief Returns edge relabeling cost.
  575. * @param[in] edge_label_1 First edge label.
  576. * @param[in] edge_label_2 Second edge label.
  577. * @return Edge relabeling cost for the given edge labels.
  578. */
  579. """
  580. if isinstance(edge_label_1, dict):
  581. edge_label_1 = tuple(sorted(edge_label_1.items(), key=lambda kv: kv[0]))
  582. if isinstance(edge_label_2, dict):
  583. edge_label_2 = tuple(sorted(edge_label_2.items(), key=lambda kv: kv[0]))
  584. return self._ged_data._edit_cost.edge_rel_cost_fun(edge_label_1, edge_label_2)
  585. def get_edge_del_cost(self, edge_label):
  586. """
  587. /*!
  588. * @brief Returns edge deletion cost.
  589. * @param[in] edge_label Edge label.
  590. * @return Cost of deleting edge with given label.
  591. */
  592. """
  593. if isinstance(edge_label, dict):
  594. edge_label = tuple(sorted(edge_label.items(), key=lambda kv: kv[0]))
  595. return self._ged_data._edit_cost.edge_del_cost_fun(edge_label)
  596. def get_edge_ins_cost(self, edge_label):
  597. """
  598. /*!
  599. * @brief Returns edge insertion cost.
  600. * @param[in] edge_label Edge label.
  601. * @return Cost of inserting edge with given label.
  602. */
  603. """
  604. if isinstance(edge_label, dict):
  605. edge_label = tuple(sorted(edge_label.items(), key=lambda kv: kv[0]))
  606. return self._ged_data._edit_cost.edge_ins_cost_fun(edge_label)
  607. def get_all_graph_ids(self):
  608. return [i for i in range(0, self._ged_data._num_graphs_without_shuffled_copies)]

A Python package for graph kernels, graph edit distances and graph pre-image problem.