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 54 kB

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

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