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

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

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