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.

gedlibpy.pyx 55 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563
  1. # distutils: language = c++
  2. """
  3. Python GedLib module
  4. ======================
  5. This module allow to use a C++ library for edit distance between graphs (GedLib) with Python.
  6. Authors
  7. -------------------
  8. David Blumenthal
  9. Natacha Lambert
  10. Linlin Jia
  11. Copyright (C) 2019-2020 by all the authors
  12. Classes & Functions
  13. -------------------
  14. """
  15. #################################
  16. ##DECLARATION OF C++ INTERFACES##
  17. #################################
  18. #Types imports for C++ compatibility
  19. from libcpp.vector cimport vector
  20. from libcpp.string cimport string
  21. from libcpp.map cimport map
  22. from libcpp cimport bool
  23. from libcpp.pair cimport pair
  24. from libcpp.list cimport list
  25. #Long unsigned int equivalent
  26. cimport numpy as cnp
  27. ctypedef cnp.npy_uint32 UINT32_t
  28. from cpython cimport array
  29. cdef extern from "src/GedLibBind.hpp" namespace "pyged":
  30. cdef vector[string] getEditCostStringOptions() except +
  31. cdef vector[string] getMethodStringOptions() except +
  32. cdef vector[string] getInitStringOptions() except +
  33. cdef size_t getDummyNode() except +
  34. cdef cppclass PyGEDEnv:
  35. PyGEDEnv() except +
  36. bool isInitialized() except +
  37. void restartEnv() except +
  38. void loadGXLGraph(string pathFolder, string pathXML, bool node_type, bool edge_type) except +
  39. pair[size_t,size_t] getGraphIds() except +
  40. vector[size_t] getAllGraphIds() except +
  41. string getGraphClass(size_t id) except +
  42. string getGraphName(size_t id) except +
  43. size_t addGraph(string name, string classe) except +
  44. void addNode(size_t graphId, string nodeId, map[string, string] nodeLabel) except +
  45. void addEdge(size_t graphId, string tail, string head, map[string, string] edgeLabel, bool ignoreDuplicates) except +
  46. void clearGraph(size_t graphId) except +
  47. size_t getGraphInternalId(size_t graphId) except +
  48. size_t getGraphNumNodes(size_t graphId) except +
  49. size_t getGraphNumEdges(size_t graphId) except +
  50. vector[string] getGraphOriginalNodeIds(size_t graphId) except +
  51. vector[map[string, string]] getGraphNodeLabels(size_t graphId) except +
  52. map[pair[size_t, size_t], map[string, string]] getGraphEdges(size_t graphId) except +
  53. vector[vector[size_t]] getGraphAdjacenceMatrix(size_t graphId) except +
  54. void setEditCost(string editCost, vector[double] editCostConstant) except +
  55. void setPersonalEditCost(vector[double] editCostConstant) except +
  56. void initEnv(string initOption, bool print_to_stdout) except +
  57. void setMethod(string method, string options) except +
  58. void initMethod() except +
  59. double getInitime() except +
  60. void runMethod(size_t g, size_t h) except +
  61. double getUpperBound(size_t g, size_t h) except +
  62. double getLowerBound(size_t g, size_t h) except +
  63. vector[cnp.npy_uint64] getForwardMap(size_t g, size_t h) except +
  64. vector[cnp.npy_uint64] getBackwardMap(size_t g, size_t h) except +
  65. size_t getNodeImage(size_t g, size_t h, size_t nodeId) except +
  66. size_t getNodePreImage(size_t g, size_t h, size_t nodeId) except +
  67. double getInducedCost(size_t g, size_t h) except +
  68. vector[pair[size_t,size_t]] getNodeMap(size_t g, size_t h) except +
  69. vector[vector[int]] getAssignmentMatrix(size_t g, size_t h) except +
  70. vector[vector[cnp.npy_uint64]] getAllMap(size_t g, size_t h) except +
  71. double getRuntime(size_t g, size_t h) except +
  72. bool quasimetricCosts() except +
  73. vector[vector[size_t]] hungarianLSAP(vector[vector[size_t]] matrixCost) except +
  74. vector[vector[double]] hungarianLSAPE(vector[vector[double]] matrixCost) except +
  75. # added by Linlin Jia.
  76. size_t getNumNodeLabels() except +
  77. map[string, string] getNodeLabel(size_t label_id) except +
  78. size_t getNumEdgeLabels() except +
  79. map[string, string] getEdgeLabel(size_t label_id) except +
  80. # size_t getNumNodes(size_t graph_id) except +
  81. double getAvgNumNodes() except +
  82. double getNodeRelCost(map[string, string] & node_label_1, map[string, string] & node_label_2) except +
  83. double getNodeDelCost(map[string, string] & node_label) except +
  84. double getNodeInsCost(map[string, string] & node_label) except +
  85. map[string, string] getMedianNodeLabel(vector[map[string, string]] & node_labels) except +
  86. double getEdgeRelCost(map[string, string] & edge_label_1, map[string, string] & edge_label_2) except +
  87. double getEdgeDelCost(map[string, string] & edge_label) except +
  88. double getEdgeInsCost(map[string, string] & edge_label) except +
  89. map[string, string] getMedianEdgeLabel(vector[map[string, string]] & edge_labels) except +
  90. string getInitType() except +
  91. # double getNodeCost(size_t label1, size_t label2) except +
  92. double computeInducedCost(size_t g_id, size_t h_id) except +
  93. #############################
  94. ##CYTHON WRAPPER INTERFACES##
  95. #############################
  96. import numpy as np
  97. import networkx as nx
  98. from gklearn.ged.env import NodeMap
  99. # import librariesImport
  100. from ctypes import *
  101. import os
  102. lib1 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/fann/libdoublefann.so')
  103. lib2 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/libsvm.3.22/libsvm.so')
  104. lib3 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/nomad/libnomad.so')
  105. lib4 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/nomad/libsgtelib.so')
  106. def get_edit_cost_options() :
  107. """
  108. Searchs the differents edit cost functions and returns the result.
  109. :return: The list of edit cost functions
  110. :rtype: list[string]
  111. .. warning:: This function is useless for an external use. Please use directly list_of_edit_cost_options.
  112. .. note:: Prefer the list_of_edit_cost_options attribute of this module.
  113. """
  114. return [option.decode('utf-8') for option in getEditCostStringOptions()]
  115. def get_method_options() :
  116. """
  117. Searchs the differents method for edit distance computation between graphs and returns the result.
  118. :return: The list of method to compute the edit distance between graphs
  119. :rtype: list[string]
  120. .. warning:: This function is useless for an external use. Please use directly list_of_method_options.
  121. .. note:: Prefer the list_of_method_options attribute of this module.
  122. """
  123. return [option.decode('utf-8') for option in getMethodStringOptions()]
  124. def get_init_options() :
  125. """
  126. Searchs the differents initialization parameters for the environment computation for graphs and returns the result.
  127. :return: The list of options to initialize the computation environment
  128. :rtype: list[string]
  129. .. warning:: This function is useless for an external use. Please use directly list_of_init_options.
  130. .. note:: Prefer the list_of_init_options attribute of this module.
  131. """
  132. return [option.decode('utf-8') for option in getInitStringOptions()]
  133. def get_dummy_node() :
  134. """
  135. Returns the ID of a dummy node.
  136. :return: The ID of the dummy node (18446744073709551614 for my computer, the hugest number possible)
  137. :rtype: size_t
  138. .. note:: A dummy node is used when a node isn't associated to an other node.
  139. """
  140. return getDummyNode()
  141. cdef class GEDEnv:
  142. """Cython wrapper class for C++ class PyGEDEnv
  143. """
  144. # cdef PyGEDEnv c_env # Hold a C++ instance which we're wrapping
  145. cdef PyGEDEnv* c_env # hold a pointer to the C++ instance which we're wrapping
  146. def __cinit__(self):
  147. self.c_env = new PyGEDEnv()
  148. def __dealloc__(self):
  149. del self.c_env
  150. def is_initialized(self) :
  151. """
  152. Checks and returns if the computation environment is initialized or not.
  153. :return: True if it's initialized, False otherwise
  154. :rtype: bool
  155. .. note:: This function exists for internals verifications but you can use it for your code.
  156. """
  157. return self.c_env.isInitialized()
  158. def restart_env(self) :
  159. """
  160. Restarts the environment variable. All data related to it will be delete.
  161. .. warning:: This function deletes all graphs, computations and more so make sure you don't need anymore your environment.
  162. .. note:: You can now delete and add somes graphs after initialization so you can avoid this function.
  163. """
  164. self.c_env.restartEnv()
  165. def load_GXL_graphs(self, path_folder, path_XML, node_type, edge_type) :
  166. """
  167. Loads some GXL graphes on the environment which is in a same folder, and present in the XMLfile.
  168. :param path_folder: The folder's path which contains GXL graphs
  169. :param path_XML: The XML's path which indicates which graphes you want to load
  170. :param node_type: Select if nodes are labeled or unlabeled
  171. :param edge_type: Select if edges are labeled or unlabeled
  172. :type path_folder: string
  173. :type path_XML: string
  174. :type node_type: bool
  175. :type edge_type: bool
  176. .. note:: You can call this function multiple times if you want, but not after an init call.
  177. """
  178. self.c_env.loadGXLGraph(path_folder.encode('utf-8'), path_XML.encode('utf-8'), node_type, edge_type)
  179. def graph_ids(self) :
  180. """
  181. Searchs the first and last IDs of the loaded graphs in the environment.
  182. :return: The pair of the first and the last graphs Ids
  183. :rtype: tuple(size_t, size_t)
  184. .. note:: Prefer this function if you have huges structures with lots of graphs.
  185. """
  186. return self.c_env.getGraphIds()
  187. def get_all_graph_ids(self) :
  188. """
  189. Searchs all the IDs of the loaded graphs in the environment.
  190. :return: The list of all graphs's Ids
  191. :rtype: list[size_t]
  192. .. note:: The last ID is equal to (number of graphs - 1). The order correspond to the loading order.
  193. """
  194. return self.c_env.getAllGraphIds()
  195. def get_graph_class(self, id) :
  196. """
  197. Returns the class of a graph with its ID.
  198. :param id: The ID of the wanted graph
  199. :type id: size_t
  200. :return: The class of the graph which correpond to the ID
  201. :rtype: string
  202. .. seealso:: get_graph_class()
  203. .. note:: An empty string can be a class.
  204. """
  205. return self.c_env.getGraphClass(id)
  206. def get_graph_name(self, id) :
  207. """
  208. Returns the name of a graph with its ID.
  209. :param id: The ID of the wanted graph
  210. :type id: size_t
  211. :return: The name of the graph which correpond to the ID
  212. :rtype: string
  213. .. seealso:: get_graph_class()
  214. .. note:: An empty string can be a name.
  215. """
  216. return self.c_env.getGraphName(id).decode('utf-8')
  217. def add_graph(self, name="", classe="") :
  218. """
  219. Adds a empty graph on the environment, with its name and its class. Nodes and edges will be add in a second time.
  220. :param name: The name of the new graph, an empty string by default
  221. :param classe: The class of the new graph, an empty string by default
  222. :type name: string
  223. :type classe: string
  224. :return: The ID of the newly graphe
  225. :rtype: size_t
  226. .. seealso::add_node(), add_edge() , add_symmetrical_edge()
  227. .. note:: You can call this function without parameters. You can also use this function after initialization, call init() after you're finished your modifications.
  228. """
  229. return self.c_env.addGraph(name.encode('utf-8'), classe.encode('utf-8'))
  230. def add_node(self, graph_id, node_id, node_label):
  231. """
  232. Adds a node on a graph selected by its ID. A ID and a label for the node is required.
  233. :param graph_id: The ID of the wanted graph
  234. :param node_id: The ID of the new node
  235. :param node_label: The label of the new node
  236. :type graph_id: size_t
  237. :type node_id: string
  238. :type node_label: dict{string : string}
  239. .. seealso:: add_graph(), add_edge(), add_symmetrical_edge()
  240. .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications.
  241. """
  242. self.c_env.addNode(graph_id, node_id.encode('utf-8'), encode_your_map(node_label))
  243. def add_edge(self, graph_id, tail, head, edge_label, ignore_duplicates=True) :
  244. """
  245. Adds an edge on a graph selected by its ID.
  246. :param graph_id: The ID of the wanted graph
  247. :param tail: The ID of the tail node for the new edge
  248. :param head: The ID of the head node for the new edge
  249. :param edge_label: The label of the new edge
  250. :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default
  251. :type graph_id: size_t
  252. :type tail: string
  253. :type head: string
  254. :type edge_label: dict{string : string}
  255. :type ignore_duplicates: bool
  256. .. seealso:: add_graph(), add_node(), add_symmetrical_edge()
  257. .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications.
  258. """
  259. self.c_env.addEdge(graph_id, tail.encode('utf-8'), head.encode('utf-8'), encode_your_map(edge_label), ignore_duplicates)
  260. def add_symmetrical_edge(self, graph_id, tail, head, edge_label) :
  261. """
  262. Adds a symmetrical edge on a graph selected by its ID.
  263. :param graph_id: The ID of the wanted graph
  264. :param tail: The ID of the tail node for the new edge
  265. :param head: The ID of the head node for the new edge
  266. :param edge_label: The label of the new edge
  267. :type graph_id: size_t
  268. :type tail: string
  269. :type head: string
  270. :type edge_label: dict{string : string}
  271. .. seealso:: add_graph(), add_node(), add_edge()
  272. .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications.
  273. """
  274. tailB = tail.encode('utf-8')
  275. headB = head.encode('utf-8')
  276. edgeLabelB = encode_your_map(edge_label)
  277. self.c_env.addEdge(graph_id, tailB, headB, edgeLabelB, True)
  278. self.c_env.addEdge(graph_id, headB, tailB, edgeLabelB, True)
  279. def clear_graph(self, graph_id) :
  280. """
  281. Deletes a graph, selected by its ID, to the environment.
  282. :param graph_id: The ID of the wanted graph
  283. :type graph_id: size_t
  284. .. note:: Call init() after you're finished your modifications.
  285. """
  286. self.c_env.clearGraph(graph_id)
  287. def get_graph_internal_id(self, graph_id) :
  288. """
  289. Searchs and returns the internal Id of a graph, selected by its ID.
  290. :param graph_id: The ID of the wanted graph
  291. :type graph_id: size_t
  292. :return: The internal ID of the selected graph
  293. :rtype: size_t
  294. .. seealso:: get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix()
  295. .. note:: These functions allow to collect all the graph's informations.
  296. """
  297. return self.c_env.getGraphInternalId(graph_id)
  298. def get_graph_num_nodes(self, graph_id) :
  299. """
  300. Searchs and returns the number of nodes on a graph, selected by its ID.
  301. :param graph_id: The ID of the wanted graph
  302. :type graph_id: size_t
  303. :return: The number of nodes on the selected graph
  304. :rtype: size_t
  305. .. seealso:: get_graph_internal_id(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix()
  306. .. note:: These functions allow to collect all the graph's informations.
  307. """
  308. return self.c_env.getGraphNumNodes(graph_id)
  309. def get_graph_num_edges(self, graph_id) :
  310. """
  311. Searchs and returns the number of edges on a graph, selected by its ID.
  312. :param graph_id: The ID of the wanted graph
  313. :type graph_id: size_t
  314. :return: The number of edges on the selected graph
  315. :rtype: size_t
  316. .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix()
  317. .. note:: These functions allow to collect all the graph's informations.
  318. """
  319. return self.c_env.getGraphNumEdges(graph_id)
  320. def get_original_node_ids(self, graph_id) :
  321. """
  322. Searchs and returns all th Ids of nodes on a graph, selected by its ID.
  323. :param graph_id: The ID of the wanted graph
  324. :type graph_id: size_t
  325. :return: The list of IDs's nodes on the selected graph
  326. :rtype: list[string]
  327. .. seealso::get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix()
  328. .. note:: These functions allow to collect all the graph's informations.
  329. """
  330. return [gid.decode('utf-8') for gid in self.c_env.getGraphOriginalNodeIds(graph_id)]
  331. def get_graph_node_labels(self, graph_id) :
  332. """
  333. Searchs and returns all the labels of nodes on a graph, selected by its ID.
  334. :param graph_id: The ID of the wanted graph
  335. :type graph_id: size_t
  336. :return: The list of nodes' labels on the selected graph
  337. :rtype: list[dict{string : string}]
  338. .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_edges(), get_graph_adjacence_matrix()
  339. .. note:: These functions allow to collect all the graph's informations.
  340. """
  341. return [decode_your_map(node_label) for node_label in self.c_env.getGraphNodeLabels(graph_id)]
  342. def get_graph_edges(self, graph_id) :
  343. """
  344. Searchs and returns all the edges on a graph, selected by its ID.
  345. :param graph_id: The ID of the wanted graph
  346. :type graph_id: size_t
  347. :return: The list of edges on the selected graph
  348. :rtype: dict{tuple(size_t, size_t) : dict{string : string}}
  349. .. 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()
  350. .. note:: These functions allow to collect all the graph's informations.
  351. """
  352. return decode_graph_edges(self.c_env.getGraphEdges(graph_id))
  353. def get_graph_adjacence_matrix(self, graph_id) :
  354. """
  355. Searchs and returns the adjacence list of a graph, selected by its ID.
  356. :param graph_id: The ID of the wanted graph
  357. :type graph_id: size_t
  358. :return: The adjacence list of the selected graph
  359. :rtype: list[list[size_t]]
  360. .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges()
  361. .. note:: These functions allow to collect all the graph's informations.
  362. """
  363. return self.c_env.getGraphAdjacenceMatrix(graph_id)
  364. def set_edit_cost(self, edit_cost, edit_cost_constant = []) :
  365. """
  366. Sets an edit cost function to the environment, if it exists.
  367. :param edit_cost: The name of the edit cost function
  368. :type edit_cost: string
  369. :param edi_cost_constant: The parameters you will add to the editCost, empty by default
  370. :type edit_cost_constant: list
  371. .. seealso:: list_of_edit_cost_options
  372. .. note:: Try to make sure the edit cost function exists with list_of_edit_cost_options, raise an error otherwise.
  373. """
  374. if edit_cost in list_of_edit_cost_options:
  375. edit_cost_b = edit_cost.encode('utf-8')
  376. self.c_env.setEditCost(edit_cost_b, edit_cost_constant)
  377. else:
  378. raise EditCostError("This edit cost function doesn't exist, please see list_of_edit_cost_options for selecting a edit cost function")
  379. def set_personal_edit_cost(self, edit_cost_constant = []) :
  380. """
  381. Sets an personal edit cost function to the environment.
  382. :param edit_cost_constant: The parameters you will add to the editCost, empty by default
  383. :type edit_cost_constant: list
  384. .. seealso:: list_of_edit_cost_options, set_edit_cost()
  385. .. note::You have to modify the C++ function to use it. Please see the documentation to add your Edit Cost function.
  386. """
  387. self.c_env.setPersonalEditCost(edit_cost_constant)
  388. def init(self, init_option='EAGER_WITHOUT_SHUFFLED_COPIES', print_to_stdout=False) :
  389. """
  390. Initializes the environment with the chosen edit cost function and graphs.
  391. :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default
  392. :type init_option: string
  393. .. seealso:: list_of_init_options
  394. .. warning:: No modification were allowed after initialization. Try to make sure your choices is correct. You can though clear or add a graph, but recall init() after that.
  395. .. note:: Try to make sure the option exists with list_of_init_options or choose no options, raise an error otherwise.
  396. """
  397. if init_option in list_of_init_options:
  398. init_option_b = init_option.encode('utf-8')
  399. self.c_env.initEnv(init_option_b, print_to_stdout)
  400. else:
  401. raise InitError("This init option doesn't exist, please see list_of_init_options for selecting an option. You can choose any options.")
  402. def set_method(self, method, options="") :
  403. """
  404. Sets a computation method to the environment, if its exists.
  405. :param method: The name of the computation method
  406. :param options: The options of the method (like bash options), an empty string by default
  407. :type method: string
  408. :type options: string
  409. .. seealso:: init_method(), list_of_method_options
  410. .. note:: Try to make sure the edit cost function exists with list_of_method_options, raise an error otherwise. Call init_method() after your set.
  411. """
  412. if method in list_of_method_options:
  413. method_b = method.encode('utf-8')
  414. self.c_env.setMethod(method_b, options.encode('utf-8'))
  415. else:
  416. raise MethodError("This method doesn't exist, please see list_of_method_options for selecting a method")
  417. def init_method(self) :
  418. """
  419. Inits the environment with the set method.
  420. .. seealso:: set_method(), list_of_method_options
  421. .. note:: Call this function after set the method. You can't launch computation or change the method after that.
  422. """
  423. self.c_env.initMethod()
  424. def get_init_time(self) :
  425. """
  426. Returns the initialization time.
  427. :return: The initialization time
  428. :rtype: double
  429. """
  430. return self.c_env.getInitime()
  431. def run_method(self, g, h) :
  432. """
  433. Computes the edit distance between two graphs g and h, with the edit cost function and method computation selected.
  434. :param g: The Id of the first graph to compare
  435. :param h: The Id of the second graph to compare
  436. :type g: size_t
  437. :type h: size_t
  438. .. seealso:: get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost()
  439. .. note:: This function only compute the distance between two graphs, without returning a result. Use the differents function to see the result between the two graphs.
  440. """
  441. self.c_env.runMethod(g, h)
  442. def get_upper_bound(self, g, h) :
  443. """
  444. Returns the upper bound of the edit distance cost between two graphs g and h.
  445. :param g: The Id of the first compared graph
  446. :param h: The Id of the second compared graph
  447. :type g: size_t
  448. :type h: size_t
  449. :return: The upper bound of the edit distance cost
  450. :rtype: double
  451. .. seealso:: run_method(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost()
  452. .. warning:: run_method() between the same two graph must be called before this function.
  453. .. note:: The upper bound is equivalent to the result of the pessimist edit distance cost. Methods are heuristics so the library can't compute the real perfect result because it's NP-Hard problem.
  454. """
  455. return self.c_env.getUpperBound(g, h)
  456. def get_lower_bound(self, g, h) :
  457. """
  458. Returns the lower bound of the edit distance cost between two graphs g and h.
  459. :param g: The Id of the first compared graph
  460. :param h: The Id of the second compared graph
  461. :type g: size_t
  462. :type h: size_t
  463. :return: The lower bound of the edit distance cost
  464. :rtype: double
  465. .. seealso:: run_method(), get_upper_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost()
  466. .. warning:: run_method() between the same two graph must be called before this function.
  467. .. note:: This function can be ignored, because lower bound doesn't have a crucial utility.
  468. """
  469. return self.c_env.getLowerBound(g, h)
  470. def get_forward_map(self, g, h) :
  471. """
  472. Returns the forward map (or the half of the adjacence matrix) between nodes of the two indicated graphs.
  473. :param g: The Id of the first compared graph
  474. :param h: The Id of the second compared graph
  475. :type g: size_t
  476. :type h: size_t
  477. :return: The forward map to the adjacence matrix between nodes of the two graphs
  478. :rtype: list[npy_uint32]
  479. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_backward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix()
  480. .. warning:: run_method() between the same two graph must be called before this function.
  481. .. 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 !
  482. """
  483. return self.c_env.getForwardMap(g, h)
  484. def get_backward_map(self, g, h) :
  485. """
  486. Returns the backward map (or the half of the adjacence matrix) between nodes of the two indicated graphs.
  487. :param g: The Id of the first compared graph
  488. :param h: The Id of the second compared graph
  489. :type g: size_t
  490. :type h: size_t
  491. :return: The backward map to the adjacence matrix between nodes of the two graphs
  492. :rtype: list[npy_uint32]
  493. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix()
  494. .. warning:: run_method() between the same two graph must be called before this function.
  495. .. 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 !
  496. """
  497. return self.c_env.getBackwardMap(g, h)
  498. def get_node_image(self, g, h, node_id) :
  499. """
  500. Returns the node's image in the adjacence matrix, if it exists.
  501. :param g: The Id of the first compared graph
  502. :param h: The Id of the second compared graph
  503. :param node_id: The ID of the node which you want to see the image
  504. :type g: size_t
  505. :type h: size_t
  506. :type node_id: size_t
  507. :return: The ID of the image node
  508. :rtype: size_t
  509. .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_pre_image(), get_node_map(), get_assignment_matrix()
  510. .. warning:: run_method() between the same two graph must be called before this function.
  511. .. note:: Use BackwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map().
  512. """
  513. return self.c_env.getNodeImage(g, h, node_id)
  514. def get_node_pre_image(self, g, h, node_id) :
  515. """
  516. Returns the node's preimage in the adjacence matrix, if it exists.
  517. :param g: The Id of the first compared graph
  518. :param h: The Id of the second compared graph
  519. :param node_id: The ID of the node which you want to see the preimage
  520. :type g: size_t
  521. :type h: size_t
  522. :type node_id: size_t
  523. :return: The ID of the preimage node
  524. :rtype: size_t
  525. .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_map(), get_assignment_matrix()
  526. .. warning:: run_method() between the same two graph must be called before this function.
  527. .. note:: Use ForwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map().
  528. """
  529. return self.c_env.getNodePreImage(g, h, node_id)
  530. def get_induced_cost(self, g, h) :
  531. """
  532. Returns the induced cost between the two indicated graphs.
  533. :param g: The Id of the first compared graph
  534. :param h: The Id of the second compared graph
  535. :type g: size_t
  536. :type h: size_t
  537. :return: The induced cost between the two indicated graphs
  538. :rtype: double
  539. .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_map(), get_assignment_matrix()
  540. .. warning:: run_method() between the same two graph must be called before this function.
  541. .. note:: Use ForwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map().
  542. """
  543. return self.c_env.getInducedCost(g, h)
  544. def get_node_map(self, g, h) :
  545. """
  546. Returns the Node Map, like C++ NodeMap.
  547. :param g: The Id of the first compared graph
  548. :param h: The Id of the second compared graph
  549. :type g: size_t
  550. :type h: size_t
  551. :return: The Node Map between the two selected graph.
  552. :rtype: gklearn.ged.env.NodeMap.
  553. .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_pre_image(), get_assignment_matrix()
  554. .. warning:: run_method() between the same two graph must be called before this function.
  555. .. note:: This function creates datas so use it if necessary, however you can understand how assignement works with this example.
  556. """
  557. map_as_relation = self.c_env.getNodeMap(g, h)
  558. induced_cost = self.c_env.getInducedCost(g, h) # @todo: the C++ implementation for this function in GedLibBind.ipp re-call get_node_map() once more, this is not neccessary.
  559. source_map = [item.first if item.first < len(map_as_relation) else np.inf for item in map_as_relation] # item.first < len(map_as_relation) is not exactly correct.
  560. # print(source_map)
  561. target_map = [item.second if item.second < len(map_as_relation) else np.inf for item in map_as_relation]
  562. # print(target_map)
  563. num_node_source = len([item for item in source_map if item != np.inf])
  564. # print(num_node_source)
  565. num_node_target = len([item for item in target_map if item != np.inf])
  566. # print(num_node_target)
  567. node_map = NodeMap(num_node_source, num_node_target)
  568. # print(node_map.get_forward_map(), node_map.get_backward_map())
  569. for i in range(len(source_map)):
  570. node_map.add_assignment(source_map[i], target_map[i])
  571. node_map.set_induced_cost(induced_cost)
  572. return node_map
  573. def get_assignment_matrix(self, g, h) :
  574. """
  575. Returns the Assignment Matrix between two selected graphs g and h.
  576. :param g: The Id of the first compared graph
  577. :param h: The Id of the second compared graph
  578. :type g: size_t
  579. :type h: size_t
  580. :return: The Assignment Matrix between the two selected graph.
  581. :rtype: list[list[int]]
  582. .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_pre_image(), get_node_map()
  583. .. warning:: run_method() between the same two graph must be called before this function.
  584. .. note:: This function creates datas so use it if necessary.
  585. """
  586. return self.c_env.getAssignmentMatrix(g, h)
  587. def get_all_map(self, g, h) :
  588. """
  589. Returns a vector which contains the forward and the backward maps between nodes of the two indicated graphs.
  590. :param g: The Id of the first compared graph
  591. :param h: The Id of the second compared graph
  592. :type g: size_t
  593. :type h: size_t
  594. :return: The forward and backward maps to the adjacence matrix between nodes of the two graphs
  595. :rtype: list[list[npy_uint32]]
  596. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost()
  597. .. warning:: run_method() between the same two graph must be called before this function.
  598. .. note:: This function duplicates data so please don't use it. I also don't know how to connect the two map to reconstruct the adjacence matrix. Please come back when I know how it's work !
  599. """
  600. return self.c_env.getAllMap(g, h)
  601. def get_runtime(self, g, h) :
  602. """
  603. Returns the runtime to compute the edit distance cost between two graphs g and h
  604. :param g: The Id of the first compared graph
  605. :param h: The Id of the second compared graph
  606. :type g: size_t
  607. :type h: size_t
  608. :return: The runtime of the computation of edit distance cost between the two selected graphs
  609. :rtype: double
  610. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), quasimetric_cost()
  611. .. warning:: run_method() between the same two graph must be called before this function.
  612. .. note:: Python is a bit longer than C++ due to the functions's encapsulate.
  613. """
  614. return self.c_env.getRuntime(g,h)
  615. def quasimetric_cost(self) :
  616. """
  617. Checks and returns if the edit costs are quasimetric.
  618. :param g: The Id of the first compared graph
  619. :param h: The Id of the second compared graph
  620. :type g: size_t
  621. :type h: size_t
  622. :return: True if it's verified, False otherwise
  623. :rtype: bool
  624. .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime()
  625. .. warning:: run_method() between the same two graph must be called before this function.
  626. """
  627. return self.c_env.quasimetricCosts()
  628. def hungarian_LSAP(self, matrix_cost) :
  629. """
  630. Applies the hungarian algorithm (LSAP) on a matrix Cost.
  631. :param matrix_cost: The matrix Cost
  632. :type matrix_cost: vector[vector[size_t]]
  633. :return: The values of rho, varrho, u and v, in this order
  634. :rtype: vector[vector[size_t]]
  635. .. seealso:: hungarian_LSAPE()
  636. """
  637. return self.c_env.hungarianLSAP(matrix_cost)
  638. def hungarian_LSAPE(self, matrix_cost) :
  639. """
  640. Applies the hungarian algorithm (LSAPE) on a matrix Cost.
  641. :param matrix_cost: The matrix Cost
  642. :type matrix_cost: vector[vector[double]]
  643. :return: The values of rho, varrho, u and v, in this order
  644. :rtype: vector[vector[double]]
  645. .. seealso:: hungarian_LSAP()
  646. """
  647. return self.c_env.hungarianLSAPE(matrix_cost)
  648. def add_random_graph(self, name, classe, list_of_nodes, list_of_edges, ignore_duplicates=True) :
  649. """
  650. Add a Graph (not GXL) on the environment. Be careful to respect the same format as GXL graphs for labelling nodes and edges.
  651. :param name: The name of the graph to add, can be an empty string
  652. :param classe: The classe of the graph to add, can be an empty string
  653. :param list_of_nodes: The list of nodes to add
  654. :param list_of_edges: The list of edges to add
  655. :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default
  656. :type name: string
  657. :type classe: string
  658. :type list_of_nodes: list[tuple(size_t, dict{string : string})]
  659. :type list_of_edges: list[tuple(tuple(size_t,size_t), dict{string : string})]
  660. :type ignore_duplicates: bool
  661. :return: The ID of the newly added graphe
  662. :rtype: size_t
  663. .. note:: The graph must respect the GXL structure. Please see how a GXL graph is construct.
  664. """
  665. id = self.add_graph(name, classe)
  666. for node in list_of_nodes:
  667. self.add_node(id, node[0], node[1])
  668. for edge in list_of_edges:
  669. self.add_edge(id, edge[0], edge[1], edge[2], ignore_duplicates)
  670. return id
  671. def add_nx_graph(self, g, classe, ignore_duplicates=True) :
  672. """
  673. Add a Graph (made by networkx) on the environment. Be careful to respect the same format as GXL graphs for labelling nodes and edges.
  674. :param g: The graph to add (networkx graph)
  675. :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default
  676. :type g: networkx.graph
  677. :type ignore_duplicates: bool
  678. :return: The ID of the newly added graphe
  679. :rtype: size_t
  680. .. note:: The NX graph must respect the GXL structure. Please see how a GXL graph is construct.
  681. """
  682. id = self.add_graph(g.name, classe)
  683. for node in g.nodes:
  684. self.add_node(id, str(node), g.nodes[node])
  685. for edge in g.edges:
  686. self.add_edge(id, str(edge[0]), str(edge[1]), g.get_edge_data(edge[0], edge[1]), ignore_duplicates)
  687. return id
  688. def compute_ged_on_two_graphs(self, g1, g2, edit_cost, method, options, init_option="EAGER_WITHOUT_SHUFFLED_COPIES") :
  689. """
  690. Computes the edit distance between two NX graphs.
  691. :param g1: The first graph to add and compute
  692. :param g2: The second graph to add and compute
  693. :param edit_cost: The name of the edit cost function
  694. :param method: The name of the computation method
  695. :param options: The options of the method (like bash options), an empty string by default
  696. :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default
  697. :type g1: networksx.graph
  698. :type g2: networksx.graph
  699. :type edit_cost: string
  700. :type method: string
  701. :type options: string
  702. :type init_option: string
  703. :return: The edit distance between the two graphs and the nodeMap between them.
  704. :rtype: double, list[tuple(size_t, size_t)]
  705. .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options
  706. .. note:: Make sure each parameter exists with your architecture and these lists : list_of_edit_cost_options, list_of_method_options, list_of_init_options. The structure of graphs must be similar as GXL.
  707. """
  708. if self.is_initialized() :
  709. self.restart_env()
  710. g = self.add_nx_graph(g1, "")
  711. h = self.add_nx_graph(g2, "")
  712. self.set_edit_cost(edit_cost)
  713. self.init(init_option)
  714. self.set_method(method, options)
  715. self.init_method()
  716. resDistance = 0
  717. resMapping = []
  718. self.run_method(g, h)
  719. resDistance = self.get_upper_bound(g, h)
  720. resMapping = self.get_node_map(g, h)
  721. return resDistance, resMapping
  722. def compute_edit_distance_on_nx_graphs(self, dataset, classes, edit_cost, method, options, init_option="EAGER_WITHOUT_SHUFFLED_COPIES") :
  723. """
  724. Computes all the edit distance between each NX graphs on the dataset.
  725. :param dataset: The list of graphs to add and compute
  726. :param classes: The classe of all the graph, can be an empty string
  727. :param edit_cost: The name of the edit cost function
  728. :param method: The name of the computation method
  729. :param options: The options of the method (like bash options), an empty string by default
  730. :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default
  731. :type dataset: list[networksx.graph]
  732. :type classes: string
  733. :type edit_cost: string
  734. :type method: string
  735. :type options: string
  736. :type init_option: string
  737. :return: Two matrix, the first with edit distances between graphs and the second the nodeMap between graphs. The result between g and h is one the [g][h] coordinates.
  738. :rtype: list[list[double]], list[list[list[tuple(size_t, size_t)]]]
  739. .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options
  740. .. note:: Make sure each parameter exists with your architecture and these lists : list_of_edit_cost_options, list_of_method_options, list_of_init_options. The structure of graphs must be similar as GXL.
  741. """
  742. if self.is_initialized() :
  743. self.restart_env()
  744. print("Loading graphs in progress...")
  745. for graph in dataset :
  746. self.add_nx_graph(graph, classes)
  747. listID = self.graph_ids()
  748. print("Graphs loaded ! ")
  749. print("Number of graphs = " + str(listID[1]))
  750. self.set_edit_cost(edit_cost)
  751. print("Initialization in progress...")
  752. self.init(init_option)
  753. print("Initialization terminated !")
  754. self.set_method(method, options)
  755. self.init_method()
  756. resDistance = [[]]
  757. resMapping = [[]]
  758. for g in range(listID[0], listID[1]) :
  759. print("Computation between graph " + str(g) + " with all the others including himself.")
  760. for h in range(listID[0], listID[1]) :
  761. #print("Computation between graph " + str(g) + " and graph " + str(h))
  762. self.run_method(g, h)
  763. resDistance[g][h] = self.get_upper_bound(g, h)
  764. resMapping[g][h] = self.get_node_map(g, h)
  765. print("Finish ! The return contains edit distances and NodeMap but you can check the result with graphs'ID until you restart the environment")
  766. return resDistance, resMapping
  767. def compute_edit_distance_on_GXl_graphs(self, path_folder, path_XML, edit_cost, method, options="", init_option="EAGER_WITHOUT_SHUFFLED_COPIES") :
  768. """
  769. Computes all the edit distance between each GXL graphs on the folder and the XMl file.
  770. :param path_folder: The folder's path which contains GXL graphs
  771. :param path_XML: The XML's path which indicates which graphes you want to load
  772. :param edit_cost: The name of the edit cost function
  773. :param method: The name of the computation method
  774. :param options: The options of the method (like bash options), an empty string by default
  775. :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default
  776. :type path_folder: string
  777. :type path_XML: string
  778. :type edit_cost: string
  779. :type method: string
  780. :type options: string
  781. :type init_option: string
  782. :return: The list of the first and last-1 ID of graphs
  783. :rtype: tuple(size_t, size_t)
  784. .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options
  785. .. note:: Make sure each parameter exists with your architecture and these lists : list_of_edit_cost_options, list_of_method_options, list_of_init_options.
  786. """
  787. if self.is_initialized() :
  788. self.restart_env()
  789. print("Loading graphs in progress...")
  790. self.load_GXL_graphs(path_folder, path_XML)
  791. listID = self.graph_ids()
  792. print("Graphs loaded ! ")
  793. print("Number of graphs = " + str(listID[1]))
  794. self.set_edit_cost(edit_cost)
  795. print("Initialization in progress...")
  796. self.init(init_option)
  797. print("Initialization terminated !")
  798. self.set_method(method, options)
  799. self.init_method()
  800. #res = []
  801. for g in range(listID[0], listID[1]) :
  802. print("Computation between graph " + str(g) + " with all the others including himself.")
  803. for h in range(listID[0], listID[1]) :
  804. #print("Computation between graph " + str(g) + " and graph " + str(h))
  805. self.run_method(g,h)
  806. #res.append((get_upper_bound(g,h), get_node_map(g,h), get_runtime(g,h)))
  807. #return res
  808. print ("Finish ! You can check the result with each ID of graphs ! There are in the return")
  809. print ("Please don't restart the environment or recall this function, you will lose your results !")
  810. return listID
  811. def get_num_node_labels(self):
  812. """
  813. Returns the number of node labels.
  814. :return: Number of pairwise different node labels contained in the environment.
  815. :rtype: size_t
  816. .. note:: If 1 is returned, the nodes are unlabeled.
  817. """
  818. return self.c_env.getNumNodeLabels()
  819. def get_node_label(self, label_id):
  820. """
  821. Returns node label.
  822. :param label_id: ID of node label that should be returned. Must be between 1 and get_num_node_labels().
  823. :type label_id: size_t
  824. :return: Node label for selected label ID.
  825. :rtype: dict{string : string}
  826. """
  827. return decode_your_map(self.c_env.getNodeLabel(label_id))
  828. def get_num_edge_labels(self):
  829. """
  830. Returns the number of edge labels.
  831. :return: Number of pairwise different edge labels contained in the environment.
  832. :rtype: size_t
  833. .. note:: If 1 is returned, the edges are unlabeled.
  834. """
  835. return self.c_env.getNumEdgeLabels()
  836. def get_edge_label(self, label_id):
  837. """
  838. Returns edge label.
  839. :param label_id: ID of edge label that should be returned. Must be between 1 and get_num_edge_labels().
  840. :type label_id: size_t
  841. :return: Edge label for selected label ID.
  842. :rtype: dict{string : string}
  843. """
  844. return decode_your_map(self.c_env.getEdgeLabel(label_id))
  845. # def get_num_nodes(self, graph_id):
  846. # """
  847. # Returns the number of nodes.
  848. #
  849. # :param graph_id: ID of an input graph that has been added to the environment.
  850. # :type graph_id: size_t
  851. # :return: Number of nodes in the graph.
  852. # :rtype: size_t
  853. # """
  854. # return self.c_env.getNumNodes(graph_id)
  855. def get_avg_num_nodes(self):
  856. """
  857. Returns average number of nodes.
  858. :return: Average number of nodes of the graphs contained in the environment.
  859. :rtype: double
  860. """
  861. return self.c_env.getAvgNumNodes()
  862. def get_node_rel_cost(self, node_label_1, node_label_2):
  863. """
  864. Returns node relabeling cost.
  865. :param node_label_1: First node label.
  866. :param node_label_2: Second node label.
  867. :type node_label_1: dict{string : string}
  868. :type node_label_2: dict{string : string}
  869. :return: Node relabeling cost for the given node labels.
  870. :rtype: double
  871. """
  872. return self.c_env.getNodeRelCost(encode_your_map(node_label_1), encode_your_map(node_label_2))
  873. def get_node_del_cost(self, node_label):
  874. """
  875. Returns node deletion cost.
  876. :param node_label: Node label.
  877. :type node_label: dict{string : string}
  878. :return: Cost of deleting node with given label.
  879. :rtype: double
  880. """
  881. return self.c_env.getNodeDelCost(encode_your_map(node_label))
  882. def get_node_ins_cost(self, node_label):
  883. """
  884. Returns node insertion cost.
  885. :param node_label: Node label.
  886. :type node_label: dict{string : string}
  887. :return: Cost of inserting node with given label.
  888. :rtype: double
  889. """
  890. return self.c_env.getNodeInsCost(encode_your_map(node_label))
  891. def get_median_node_label(self, node_labels):
  892. """
  893. Computes median node label.
  894. :param node_labels: The node labels whose median should be computed.
  895. :type node_labels: list[dict{string : string}]
  896. :return: Median of the given node labels.
  897. :rtype: dict{string : string}
  898. """
  899. node_labels_b = [encode_your_map(node_label) for node_label in node_labels]
  900. return decode_your_map(self.c_env.getMedianNodeLabel(node_labels_b))
  901. def get_edge_rel_cost(self, edge_label_1, edge_label_2):
  902. """
  903. Returns edge relabeling cost.
  904. :param edge_label_1: First edge label.
  905. :param edge_label_2: Second edge label.
  906. :type edge_label_1: dict{string : string}
  907. :type edge_label_2: dict{string : string}
  908. :return: Edge relabeling cost for the given edge labels.
  909. :rtype: double
  910. """
  911. return self.c_env.getEdgeRelCost(encode_your_map(edge_label_1), encode_your_map(edge_label_2))
  912. def get_edge_del_cost(self, edge_label):
  913. """
  914. Returns edge deletion cost.
  915. :param edge_label: Edge label.
  916. :type edge_label: dict{string : string}
  917. :return: Cost of deleting edge with given label.
  918. :rtype: double
  919. """
  920. return self.c_env.getEdgeDelCost(encode_your_map(edge_label))
  921. def get_edge_ins_cost(self, edge_label):
  922. """
  923. Returns edge insertion cost.
  924. :param edge_label: Edge label.
  925. :type edge_label: dict{string : string}
  926. :return: Cost of inserting edge with given label.
  927. :rtype: double
  928. """
  929. return self.c_env.getEdgeInsCost(encode_your_map(edge_label))
  930. def get_median_edge_label(self, edge_labels):
  931. """
  932. Computes median edge label.
  933. :param edge_labels: The edge labels whose median should be computed.
  934. :type edge_labels: list[dict{string : string}]
  935. :return: Median of the given edge labels.
  936. :rtype: dict{string : string}
  937. """
  938. edge_labels_b = [encode_your_map(edge_label) for edge_label in edge_labels]
  939. return decode_your_map(self.c_env.getMedianEdgeLabel(edge_label_b))
  940. def get_nx_graph(self, graph_id, adj_matrix=True, adj_lists=False, edge_list=False): # @todo
  941. """
  942. Get graph with id `graph_id` in the form of the NetworkX Graph.
  943. Parameters
  944. ----------
  945. graph_id : int
  946. ID of the selected graph.
  947. adj_matrix : bool
  948. Set to `True` to construct an adjacency matrix `adj_matrix` and a hash-map `edge_labels`, which has a key for each pair `(i,j)` such that `adj_matrix[i][j]` equals 1. No effect for now.
  949. adj_lists : bool
  950. No effect for now.
  951. edge_list : bool
  952. No effect for now.
  953. Returns
  954. -------
  955. NetworkX Graph object
  956. The obtained graph.
  957. """
  958. graph = nx.Graph()
  959. graph.graph['id'] = graph_id
  960. nb_nodes = self.get_graph_num_nodes(graph_id)
  961. original_node_ids = self.get_original_node_ids(graph_id)
  962. node_labels = self.get_graph_node_labels(graph_id)
  963. # print(original_node_ids)
  964. # print(node_labels)
  965. graph.graph['original_node_ids'] = original_node_ids
  966. for node_id in range(0, nb_nodes):
  967. graph.add_node(node_id, **node_labels[node_id])
  968. # graph.nodes[node_id]['original_node_id'] = original_node_ids[node_id]
  969. edges = self.get_graph_edges(graph_id)
  970. for (head, tail), labels in edges.items():
  971. graph.add_edge(head, tail, **labels)
  972. # print(edges)
  973. return graph
  974. def get_init_type(self):
  975. """
  976. Returns the initialization type of the last initialization in string.
  977. Returns
  978. -------
  979. string
  980. Initialization type in string.
  981. """
  982. return self.c_env.getInitType().decode('utf-8')
  983. # def get_node_cost(self, label1, label2):
  984. # """
  985. # Returns node relabeling, insertion, or deletion cost.
  986. # Parameters
  987. # ----------
  988. # label1 : int
  989. # First node label.
  990. #
  991. # label2 : int
  992. # Second node label.
  993. #
  994. # Returns
  995. # -------
  996. # Node relabeling cost if `label1` and `label2` are both different from `ged::dummy_label()`, node insertion cost if `label1` equals `ged::dummy_label` and `label2` does not, node deletion cost if `label1` does not equal `ged::dummy_label` and `label2` does, and 0 otherwise.
  997. # """
  998. # return self.c_env.getNodeCost(label1, label2)
  999. def load_nx_graph(self, nx_graph, graph_id, graph_name='', graph_class=''):
  1000. """
  1001. Loads NetworkX Graph into the GED environment.
  1002. Parameters
  1003. ----------
  1004. nx_graph : NetworkX Graph object
  1005. The graph that should be loaded.
  1006. graph_id : int or None
  1007. The ID of a graph contained the environment (overwrite existing graph) or add new graph if `None`.
  1008. graph_name : string, optional
  1009. The name of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`.
  1010. graph_class : string, optional
  1011. The class of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`.
  1012. Returns
  1013. -------
  1014. int
  1015. The ID of the newly loaded graph.
  1016. """
  1017. if graph_id is None:
  1018. graph_id = self.add_graph(graph_name, graph_class)
  1019. else:
  1020. self.clear_graph(graph_id)
  1021. for node in nx_graph.nodes:
  1022. self.add_node(graph_id, str(node), nx_graph.nodes[node])
  1023. for edge in nx_graph.edges:
  1024. self.add_edge(graph_id, str(edge[0]), str(edge[1]), nx_graph.get_edge_data(edge[0], edge[1]))
  1025. return graph_id
  1026. def compute_induced_cost(self, g_id, h_id, node_map):
  1027. """
  1028. Computes the edit cost between two graphs induced by a node map.
  1029. Parameters
  1030. ----------
  1031. g_id : int
  1032. ID of input graph.
  1033. h_id : int
  1034. ID of input graph.
  1035. node_map: gklearn.ged.env.NodeMap.
  1036. The NodeMap instance whose reduced cost will be computed and re-assigned.
  1037. Returns
  1038. -------
  1039. None.
  1040. """
  1041. induced_cost = self.c_env.computeInducedCost(g_id, h_id)
  1042. node_map.set_induced_cost(induced_cost)
  1043. #####################################################################
  1044. ##LISTS OF EDIT COST FUNCTIONS, METHOD COMPUTATION AND INIT OPTIONS##
  1045. #####################################################################
  1046. list_of_edit_cost_options = get_edit_cost_options()
  1047. list_of_method_options = get_method_options()
  1048. list_of_init_options = get_init_options()
  1049. #####################
  1050. ##ERRORS MANAGEMENT##
  1051. #####################
  1052. class Error(Exception):
  1053. """
  1054. Class for error's management. This one is general.
  1055. """
  1056. pass
  1057. class EditCostError(Error) :
  1058. """
  1059. Class for Edit Cost Error. Raise an error if an edit cost function doesn't exist in the library (not in list_of_edit_cost_options).
  1060. :attribute message: The message to print when an error is detected.
  1061. :type message: string
  1062. """
  1063. def __init__(self, message):
  1064. """
  1065. Inits the error with its message.
  1066. :param message: The message to print when the error is detected
  1067. :type message: string
  1068. """
  1069. self.message = message
  1070. class MethodError(Error) :
  1071. """
  1072. Class for Method Error. Raise an error if a computation method doesn't exist in the library (not in list_of_method_options).
  1073. :attribute message: The message to print when an error is detected.
  1074. :type message: string
  1075. """
  1076. def __init__(self, message):
  1077. """
  1078. Inits the error with its message.
  1079. :param message: The message to print when the error is detected
  1080. :type message: string
  1081. """
  1082. self.message = message
  1083. class InitError(Error) :
  1084. """
  1085. Class for Init Error. Raise an error if an init option doesn't exist in the library (not in list_of_init_options).
  1086. :attribute message: The message to print when an error is detected.
  1087. :type message: string
  1088. """
  1089. def __init__(self, message):
  1090. """
  1091. Inits the error with its message.
  1092. :param message: The message to print when the error is detected
  1093. :type message: string
  1094. """
  1095. self.message = message
  1096. #########################################
  1097. ##PYTHON FUNCTIONS FOR SOME COMPUTATION##
  1098. #########################################
  1099. def encode_your_map(map_u):
  1100. """
  1101. Encodes Python unicode strings in dictionnary `map` to utf-8 byte strings for C++ functions.
  1102. :param map_b: The map to encode
  1103. :type map_b: dict{string : string}
  1104. :return: The encoded map
  1105. :rtype: dict{'b'string : 'b'string}
  1106. .. note:: This function is used for type connection.
  1107. """
  1108. res = {}
  1109. for key, value in map_u.items():
  1110. res[key.encode('utf-8')] = value.encode('utf-8')
  1111. return res
  1112. def decode_your_map(map_b):
  1113. """
  1114. Decodes utf-8 byte strings in `map` from C++ functions to Python unicode strings.
  1115. :param map_b: The map to decode
  1116. :type map_b: dict{'b'string : 'b'string}
  1117. :return: The decoded map
  1118. :rtype: dict{string : string}
  1119. .. note:: This function is used for type connection.
  1120. """
  1121. res = {}
  1122. for key, value in map_b.items():
  1123. res[key.decode('utf-8')] = value.decode('utf-8')
  1124. return res
  1125. def decode_graph_edges(map_edge_b):
  1126. """
  1127. Decode utf-8 byte strings in graph edges `map` from C++ functions to Python unicode strings.
  1128. Parameters
  1129. ----------
  1130. map_edge_b : dict{tuple(size_t, size_t) : dict{'b'string : 'b'string}}
  1131. The map to decode.
  1132. Returns
  1133. -------
  1134. dict{tuple(size_t, size_t) : dict{string : string}}
  1135. The decoded map.
  1136. Notes
  1137. -----
  1138. This is a helper function for function `GEDEnv.get_graph_edges()`.
  1139. """
  1140. map_edges = {}
  1141. for key, value in map_edge_b.items():
  1142. map_edges[key] = decode_your_map(value)
  1143. return map_edges
  1144. # cdef extern from "src/GedLibBind.h" namespace "shapes":
  1145. # cdef cppclass Rectangle:
  1146. # Rectangle() except +
  1147. # Rectangle(int, int, int, int) except +
  1148. # int x0, y0, x1, y1
  1149. # int getArea()
  1150. # void getSize(int* width, int* height)
  1151. # void move(int, int)
  1152. # # Create a Cython extension type which holds a C++ instance
  1153. # # as an attribute and create a bunch of forwarding methods
  1154. # # Python extension type.
  1155. # cdef class PyRectangle:
  1156. # cdef Rectangle c_rect # Hold a C++ instance which we're wrapping
  1157. # def __cinit__(self, int x0, int y0, int x1, int y1):
  1158. # self.c_rect = Rectangle(x0, y0, x1, y1)
  1159. # def get_area(self):
  1160. # return self.c_rect.getArea()
  1161. # def get_size(self):
  1162. # cdef int width, height
  1163. # self.c_rect.getSize(&width, &height)
  1164. # return width, height
  1165. # def move(self, dx, dy):
  1166. # self.c_rect.move(dx, dy)
  1167. # # Attribute access
  1168. # @property
  1169. # def x0(self):
  1170. # return self.c_rect.x0
  1171. # @x0.setter
  1172. # def x0(self, x0):
  1173. # self.c_rect.x0 = x0
  1174. # # Attribute access
  1175. # @property
  1176. # def x1(self):
  1177. # return self.c_rect.x1
  1178. # @x1.setter
  1179. # def x1(self, x1):
  1180. # self.c_rect.x1 = x1
  1181. # # Attribute access
  1182. # @property
  1183. # def y0(self):
  1184. # return self.c_rect.y0
  1185. # @y0.setter
  1186. # def y0(self, y0):
  1187. # self.c_rect.y0 = y0
  1188. # # Attribute access
  1189. # @property
  1190. # def y1(self):
  1191. # return self.c_rect.y1
  1192. # @y1.setter
  1193. # def y1(self, y1):
  1194. # self.c_rect.y1 = y1

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