diff --git a/lang/fr/gklearn/gedlib/gedlibpy.pyx b/lang/fr/gklearn/gedlib/gedlibpy.pyx new file mode 100644 index 0000000..7fb7e84 --- /dev/null +++ b/lang/fr/gklearn/gedlib/gedlibpy.pyx @@ -0,0 +1,1581 @@ +# distutils: language = c++ + +""" + Python GedLib module + ====================== + + This module allow to use a C++ library for edit distance between graphs (GedLib) with Python. + + + Authors + ------------------- + + David Blumenthal + Natacha Lambert + Linlin Jia + + Copyright (C) 2019-2020 by all the authors + + Classes & Functions + ------------------- + +""" + +################################# +##DECLARATION OF C++ INTERFACES## +################################# + + +#Types imports for C++ compatibility +from libcpp.vector cimport vector +from libcpp.string cimport string +from libcpp.map cimport map +from libcpp cimport bool +from libcpp.pair cimport pair +from libcpp.list cimport list + +#Long unsigned int equivalent +cimport numpy as cnp +ctypedef cnp.npy_uint32 UINT32_t +from cpython cimport array + + +cdef extern from "src/GedLibBind.hpp" namespace "pyged": + + cdef vector[string] getEditCostStringOptions() except + + cdef vector[string] getMethodStringOptions() except + + cdef vector[string] getInitStringOptions() except + + cdef size_t getDummyNode() except + + + cdef cppclass PyGEDEnv: + PyGEDEnv() except + + bool isInitialized() except + + void restartEnv() except + + void loadGXLGraph(string pathFolder, string pathXML, bool node_type, bool edge_type) except + + pair[size_t,size_t] getGraphIds() except + + vector[size_t] getAllGraphIds() except + + string getGraphClass(size_t id) except + + string getGraphName(size_t id) except + + size_t addGraph(string name, string classe) except + + void addNode(size_t graphId, string nodeId, map[string, string] nodeLabel) except + + void addEdge(size_t graphId, string tail, string head, map[string, string] edgeLabel, bool ignoreDuplicates) except + + void clearGraph(size_t graphId) except + + size_t getGraphInternalId(size_t graphId) except + + size_t getGraphNumNodes(size_t graphId) except + + size_t getGraphNumEdges(size_t graphId) except + + vector[string] getGraphOriginalNodeIds(size_t graphId) except + + vector[map[string, string]] getGraphNodeLabels(size_t graphId) except + + map[pair[size_t, size_t], map[string, string]] getGraphEdges(size_t graphId) except + + vector[vector[size_t]] getGraphAdjacenceMatrix(size_t graphId) except + + void setEditCost(string editCost, vector[double] editCostConstant) except + + void setPersonalEditCost(vector[double] editCostConstant) except + + void initEnv(string initOption, bool print_to_stdout) except + + void setMethod(string method, string options) except + + void initMethod() except + + double getInitime() except + + void runMethod(size_t g, size_t h) except + + double getUpperBound(size_t g, size_t h) except + + double getLowerBound(size_t g, size_t h) except + + vector[cnp.npy_uint64] getForwardMap(size_t g, size_t h) except + + vector[cnp.npy_uint64] getBackwardMap(size_t g, size_t h) except + + size_t getNodeImage(size_t g, size_t h, size_t nodeId) except + + size_t getNodePreImage(size_t g, size_t h, size_t nodeId) except + + double getInducedCost(size_t g, size_t h) except + + vector[pair[size_t,size_t]] getNodeMap(size_t g, size_t h) except + + vector[vector[int]] getAssignmentMatrix(size_t g, size_t h) except + + vector[vector[cnp.npy_uint64]] getAllMap(size_t g, size_t h) except + + double getRuntime(size_t g, size_t h) except + + bool quasimetricCosts() except + + vector[vector[size_t]] hungarianLSAP(vector[vector[size_t]] matrixCost) except + + vector[vector[double]] hungarianLSAPE(vector[vector[double]] matrixCost) except + + # added by Linlin Jia. + size_t getNumNodeLabels() except + + map[string, string] getNodeLabel(size_t label_id) except + + size_t getNumEdgeLabels() except + + map[string, string] getEdgeLabel(size_t label_id) except + +# size_t getNumNodes(size_t graph_id) except + + double getAvgNumNodes() except + + double getNodeRelCost(map[string, string] & node_label_1, map[string, string] & node_label_2) except + + double getNodeDelCost(map[string, string] & node_label) except + + double getNodeInsCost(map[string, string] & node_label) except + + map[string, string] getMedianNodeLabel(vector[map[string, string]] & node_labels) except + + double getEdgeRelCost(map[string, string] & edge_label_1, map[string, string] & edge_label_2) except + + double getEdgeDelCost(map[string, string] & edge_label) except + + double getEdgeInsCost(map[string, string] & edge_label) except + + map[string, string] getMedianEdgeLabel(vector[map[string, string]] & edge_labels) except + + string getInitType() except + +# double getNodeCost(size_t label1, size_t label2) except + + double computeInducedCost(size_t g_id, size_t h_id, vector[pair[size_t,size_t]]) except + + + +############################# +##CYTHON WRAPPER INTERFACES## +############################# + +# import cython +import numpy as np +import networkx as nx +from gklearn.ged.env import NodeMap + +# import librariesImport +from ctypes import * +import os +lib1 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/fann/libdoublefann.so') +lib2 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/libsvm.3.22/libsvm.so') +lib3 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/nomad/libnomad.so') +lib4 = cdll.LoadLibrary(os.path.dirname(os.path.realpath(__file__)) + '/lib/nomad/libsgtelib.so') + + +def get_edit_cost_options() : + """ + Searchs the differents edit cost functions and returns the result. + + :return: The list of edit cost functions + :rtype: list[string] + + .. warning:: This function is useless for an external use. Please use directly list_of_edit_cost_options. + .. note:: Prefer the list_of_edit_cost_options attribute of this module. + """ + + return [option.decode('utf-8') for option in getEditCostStringOptions()] + + +def get_method_options() : + """ + Searchs the differents method for edit distance computation between graphs and returns the result. + + :return: The list of method to compute the edit distance between graphs + :rtype: list[string] + + .. warning:: This function is useless for an external use. Please use directly list_of_method_options. + .. note:: Prefer the list_of_method_options attribute of this module. + """ + return [option.decode('utf-8') for option in getMethodStringOptions()] + + +def get_init_options() : + """ + Searchs the differents initialization parameters for the environment computation for graphs and returns the result. + + :return: The list of options to initialize the computation environment + :rtype: list[string] + + .. warning:: This function is useless for an external use. Please use directly list_of_init_options. + .. note:: Prefer the list_of_init_options attribute of this module. + """ + return [option.decode('utf-8') for option in getInitStringOptions()] + + +def get_dummy_node() : + """ + Returns the ID of a dummy node. + + :return: The ID of the dummy node (18446744073709551614 for my computer, the hugest number possible) + :rtype: size_t + + .. note:: A dummy node is used when a node isn't associated to an other node. + """ + return getDummyNode() + + +# @cython.auto_pickle(True) +cdef class GEDEnv: + """Cython wrapper class for C++ class PyGEDEnv + """ +# cdef PyGEDEnv c_env # Hold a C++ instance which we're wrapping + cdef PyGEDEnv* c_env # hold a pointer to the C++ instance which we're wrapping + + + def __cinit__(self): +# self.c_env = PyGEDEnv() + self.c_env = new PyGEDEnv() + + + def __dealloc__(self): + del self.c_env + + +# def __reduce__(self): +# # return GEDEnv, (self.c_env,) +# return GEDEnv, tuple() + + + def is_initialized(self) : + """ + Checks and returns if the computation environment is initialized or not. + + :return: True if it's initialized, False otherwise + :rtype: bool + + .. note:: This function exists for internals verifications but you can use it for your code. + """ + return self.c_env.isInitialized() + + + def restart_env(self) : + """ + Restarts the environment variable. All data related to it will be delete. + + .. warning:: This function deletes all graphs, computations and more so make sure you don't need anymore your environment. + .. note:: You can now delete and add somes graphs after initialization so you can avoid this function. + """ + self.c_env.restartEnv() + + + def load_GXL_graphs(self, path_folder, path_XML, node_type, edge_type) : + """ + Loads some GXL graphes on the environment which is in a same folder, and present in the XMLfile. + + :param path_folder: The folder's path which contains GXL graphs + :param path_XML: The XML's path which indicates which graphes you want to load + :param node_type: Select if nodes are labeled or unlabeled + :param edge_type: Select if edges are labeled or unlabeled + :type path_folder: string + :type path_XML: string + :type node_type: bool + :type edge_type: bool + + + .. note:: You can call this function multiple times if you want, but not after an init call. + """ + self.c_env.loadGXLGraph(path_folder.encode('utf-8'), path_XML.encode('utf-8'), node_type, edge_type) + + + def graph_ids(self) : + """ + Searchs the first and last IDs of the loaded graphs in the environment. + + :return: The pair of the first and the last graphs Ids + :rtype: tuple(size_t, size_t) + + .. note:: Prefer this function if you have huges structures with lots of graphs. + """ + return self.c_env.getGraphIds() + + + def get_all_graph_ids(self) : + """ + Searchs all the IDs of the loaded graphs in the environment. + + :return: The list of all graphs's Ids + :rtype: list[size_t] + + .. note:: The last ID is equal to (number of graphs - 1). The order correspond to the loading order. + """ + return self.c_env.getAllGraphIds() + + + def get_graph_class(self, id) : + """ + Returns the class of a graph with its ID. + + :param id: The ID of the wanted graph + :type id: size_t + :return: The class of the graph which correpond to the ID + :rtype: string + + .. seealso:: get_graph_class() + .. note:: An empty string can be a class. + """ + return self.c_env.getGraphClass(id) + + + def get_graph_name(self, id) : + """ + Returns the name of a graph with its ID. + + :param id: The ID of the wanted graph + :type id: size_t + :return: The name of the graph which correpond to the ID + :rtype: string + + .. seealso:: get_graph_class() + .. note:: An empty string can be a name. + """ + return self.c_env.getGraphName(id).decode('utf-8') + + + def add_graph(self, name="", classe="") : + """ + Adds a empty graph on the environment, with its name and its class. Nodes and edges will be add in a second time. + + :param name: The name of the new graph, an empty string by default + :param classe: The class of the new graph, an empty string by default + :type name: string + :type classe: string + :return: The ID of the newly graphe + :rtype: size_t + + .. seealso::add_node(), add_edge() , add_symmetrical_edge() + .. note:: You can call this function without parameters. You can also use this function after initialization, call init() after you're finished your modifications. + """ + return self.c_env.addGraph(name.encode('utf-8'), classe.encode('utf-8')) + + + def add_node(self, graph_id, node_id, node_label): + """ + Adds a node on a graph selected by its ID. A ID and a label for the node is required. + + :param graph_id: The ID of the wanted graph + :param node_id: The ID of the new node + :param node_label: The label of the new node + :type graph_id: size_t + :type node_id: string + :type node_label: dict{string : string} + + .. seealso:: add_graph(), add_edge(), add_symmetrical_edge() + .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications. + """ + self.c_env.addNode(graph_id, node_id.encode('utf-8'), encode_your_map(node_label)) + + + def add_edge(self, graph_id, tail, head, edge_label, ignore_duplicates=True) : + """ + Adds an edge on a graph selected by its ID. + + :param graph_id: The ID of the wanted graph + :param tail: The ID of the tail node for the new edge + :param head: The ID of the head node for the new edge + :param edge_label: The label of the new edge + :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default + :type graph_id: size_t + :type tail: string + :type head: string + :type edge_label: dict{string : string} + :type ignore_duplicates: bool + + .. seealso:: add_graph(), add_node(), add_symmetrical_edge() + .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications. + """ + self.c_env.addEdge(graph_id, tail.encode('utf-8'), head.encode('utf-8'), encode_your_map(edge_label), ignore_duplicates) + + + def add_symmetrical_edge(self, graph_id, tail, head, edge_label) : + """ + Adds a symmetrical edge on a graph selected by its ID. + + :param graph_id: The ID of the wanted graph + :param tail: The ID of the tail node for the new edge + :param head: The ID of the head node for the new edge + :param edge_label: The label of the new edge + :type graph_id: size_t + :type tail: string + :type head: string + :type edge_label: dict{string : string} + + .. seealso:: add_graph(), add_node(), add_edge() + .. note:: You can also use this function after initialization, but only on a newly added graph. Call init() after you're finished your modifications. + """ + tailB = tail.encode('utf-8') + headB = head.encode('utf-8') + edgeLabelB = encode_your_map(edge_label) + self.c_env.addEdge(graph_id, tailB, headB, edgeLabelB, True) + self.c_env.addEdge(graph_id, headB, tailB, edgeLabelB, True) + + + def clear_graph(self, graph_id) : + """ + Deletes a graph, selected by its ID, to the environment. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + + .. note:: Call init() after you're finished your modifications. + """ + self.c_env.clearGraph(graph_id) + + + def get_graph_internal_id(self, graph_id) : + """ + Searchs and returns the internal Id of a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The internal ID of the selected graph + :rtype: size_t + + .. seealso:: get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix() + .. note:: These functions allow to collect all the graph's informations. + """ + return self.c_env.getGraphInternalId(graph_id) + + + def get_graph_num_nodes(self, graph_id) : + """ + Searchs and returns the number of nodes on a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The number of nodes on the selected graph + :rtype: size_t + + .. seealso:: get_graph_internal_id(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix() + .. note:: These functions allow to collect all the graph's informations. + """ + return self.c_env.getGraphNumNodes(graph_id) + + + def get_graph_num_edges(self, graph_id) : + """ + Searchs and returns the number of edges on a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The number of edges on the selected graph + :rtype: size_t + + .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix() + .. note:: These functions allow to collect all the graph's informations. + """ + return self.c_env.getGraphNumEdges(graph_id) + + + def get_original_node_ids(self, graph_id) : + """ + Searchs and returns all th Ids of nodes on a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The list of IDs's nodes on the selected graph + :rtype: list[string] + + .. seealso::get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_graph_node_labels(), get_graph_edges(), get_graph_adjacence_matrix() + .. note:: These functions allow to collect all the graph's informations. + """ + return [gid.decode('utf-8') for gid in self.c_env.getGraphOriginalNodeIds(graph_id)] + + + def get_graph_node_labels(self, graph_id) : + """ + Searchs and returns all the labels of nodes on a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The list of nodes' labels on the selected graph + :rtype: list[dict{string : string}] + + .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_edges(), get_graph_adjacence_matrix() + .. note:: These functions allow to collect all the graph's informations. + """ + return [decode_your_map(node_label) for node_label in self.c_env.getGraphNodeLabels(graph_id)] + + + def get_graph_edges(self, graph_id) : + """ + Searchs and returns all the edges on a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The list of edges on the selected graph + :rtype: dict{tuple(size_t, size_t) : dict{string : string}} + + .. 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() + .. note:: These functions allow to collect all the graph's informations. + """ + return decode_graph_edges(self.c_env.getGraphEdges(graph_id)) + + + def get_graph_adjacence_matrix(self, graph_id) : + """ + Searchs and returns the adjacence list of a graph, selected by its ID. + + :param graph_id: The ID of the wanted graph + :type graph_id: size_t + :return: The adjacence list of the selected graph + :rtype: list[list[size_t]] + + .. seealso:: get_graph_internal_id(), get_graph_num_nodes(), get_graph_num_edges(), get_original_node_ids(), get_graph_node_labels(), get_graph_edges() + .. note:: These functions allow to collect all the graph's informations. + """ + return self.c_env.getGraphAdjacenceMatrix(graph_id) + + + def set_edit_cost(self, edit_cost, edit_cost_constant = []) : + """ + Sets an edit cost function to the environment, if it exists. + + :param edit_cost: The name of the edit cost function + :type edit_cost: string + :param edi_cost_constant: The parameters you will add to the editCost, empty by default + :type edit_cost_constant: list + + .. seealso:: list_of_edit_cost_options + .. note:: Try to make sure the edit cost function exists with list_of_edit_cost_options, raise an error otherwise. + """ + if edit_cost in list_of_edit_cost_options: + edit_cost_b = edit_cost.encode('utf-8') + self.c_env.setEditCost(edit_cost_b, edit_cost_constant) + else: + raise EditCostError("This edit cost function doesn't exist, please see list_of_edit_cost_options for selecting a edit cost function") + + + def set_personal_edit_cost(self, edit_cost_constant = []) : + """ + Sets an personal edit cost function to the environment. + + :param edit_cost_constant: The parameters you will add to the editCost, empty by default + :type edit_cost_constant: list + + .. seealso:: list_of_edit_cost_options, set_edit_cost() + .. note::You have to modify the C++ function to use it. Please see the documentation to add your Edit Cost function. + """ + self.c_env.setPersonalEditCost(edit_cost_constant) + + + def init(self, init_option='EAGER_WITHOUT_SHUFFLED_COPIES', print_to_stdout=False) : + """ + Initializes the environment with the chosen edit cost function and graphs. + + :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default + :type init_option: string + + .. seealso:: list_of_init_options + .. 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. + .. note:: Try to make sure the option exists with list_of_init_options or choose no options, raise an error otherwise. + """ + if init_option in list_of_init_options: + init_option_b = init_option.encode('utf-8') + self.c_env.initEnv(init_option_b, print_to_stdout) + else: + raise InitError("This init option doesn't exist, please see list_of_init_options for selecting an option. You can choose any options.") + + + def set_method(self, method, options="") : + """ + Sets a computation method to the environment, if its exists. + + :param method: The name of the computation method + :param options: The options of the method (like bash options), an empty string by default + :type method: string + :type options: string + + .. seealso:: init_method(), list_of_method_options + .. 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. + """ + if method in list_of_method_options: + method_b = method.encode('utf-8') + self.c_env.setMethod(method_b, options.encode('utf-8')) + else: + raise MethodError("This method doesn't exist, please see list_of_method_options for selecting a method") + + + def init_method(self) : + """ + Inits the environment with the set method. + + .. seealso:: set_method(), list_of_method_options + .. note:: Call this function after set the method. You can't launch computation or change the method after that. + """ + self.c_env.initMethod() + + + def get_init_time(self) : + """ + Returns the initialization time. + + :return: The initialization time + :rtype: double + """ + return self.c_env.getInitime() + + + def run_method(self, g, h) : + """ + Computes the edit distance between two graphs g and h, with the edit cost function and method computation selected. + + :param g: The Id of the first graph to compare + :param h: The Id of the second graph to compare + :type g: size_t + :type h: size_t + + .. seealso:: get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost() + .. 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. + """ + self.c_env.runMethod(g, h) + + + def get_upper_bound(self, g, h) : + """ + Returns the upper bound of the edit distance cost between two graphs g and h. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The upper bound of the edit distance cost + :rtype: double + + .. seealso:: run_method(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost() + .. warning:: run_method() between the same two graph must be called before this function. + .. 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. + """ + return self.c_env.getUpperBound(g, h) + + + def get_lower_bound(self, g, h) : + """ + Returns the lower bound of the edit distance cost between two graphs g and h. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The lower bound of the edit distance cost + :rtype: double + + .. seealso:: run_method(), get_upper_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: This function can be ignored, because lower bound doesn't have a crucial utility. + """ + return self.c_env.getLowerBound(g, h) + + + def get_forward_map(self, g, h) : + """ + Returns the forward map (or the half of the adjacence matrix) between nodes of the two indicated graphs. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The forward map to the adjacence matrix between nodes of the two graphs + :rtype: list[npy_uint32] + + .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_backward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. 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 ! + """ + return self.c_env.getForwardMap(g, h) + + + def get_backward_map(self, g, h) : + """ + Returns the backward map (or the half of the adjacence matrix) between nodes of the two indicated graphs. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The backward map to the adjacence matrix between nodes of the two graphs + :rtype: list[npy_uint32] + + .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_runtime(), quasimetric_cost(), get_node_map(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. 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 ! + """ + return self.c_env.getBackwardMap(g, h) + + + def get_node_image(self, g, h, node_id) : + """ + Returns the node's image in the adjacence matrix, if it exists. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :param node_id: The ID of the node which you want to see the image + :type g: size_t + :type h: size_t + :type node_id: size_t + :return: The ID of the image node + :rtype: size_t + + .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_pre_image(), get_node_map(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: Use BackwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map(). + + """ + return self.c_env.getNodeImage(g, h, node_id) + + + def get_node_pre_image(self, g, h, node_id) : + """ + Returns the node's preimage in the adjacence matrix, if it exists. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :param node_id: The ID of the node which you want to see the preimage + :type g: size_t + :type h: size_t + :type node_id: size_t + :return: The ID of the preimage node + :rtype: size_t + + .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_map(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: Use ForwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map(). + + """ + return self.c_env.getNodePreImage(g, h, node_id) + + + def get_induced_cost(self, g, h) : + """ + Returns the induced cost between the two indicated graphs. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The induced cost between the two indicated graphs + :rtype: double + + .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_map(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: Use ForwardMap's Node to find its images ! You can also use get_forward_map() and get_backward_map(). + + """ + return self.c_env.getInducedCost(g, h) + + + def get_node_map(self, g, h) : + """ + Returns the Node Map, like C++ NodeMap. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The Node Map between the two selected graph. + :rtype: gklearn.ged.env.NodeMap. + + .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_pre_image(), get_assignment_matrix() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: This function creates datas so use it if necessary, however you can understand how assignement works with this example. + """ + map_as_relation = self.c_env.getNodeMap(g, h) + 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. + 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. +# print(source_map) + target_map = [item.second if item.second < len(map_as_relation) else np.inf for item in map_as_relation] +# print(target_map) + num_node_source = len([item for item in source_map if item != np.inf]) +# print(num_node_source) + num_node_target = len([item for item in target_map if item != np.inf]) +# print(num_node_target) + + node_map = NodeMap(num_node_source, num_node_target) +# print(node_map.get_forward_map(), node_map.get_backward_map()) + for i in range(len(source_map)): + node_map.add_assignment(source_map[i], target_map[i]) + node_map.set_induced_cost(induced_cost) + + return node_map + + + def get_assignment_matrix(self, g, h) : + """ + Returns the Assignment Matrix between two selected graphs g and h. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The Assignment Matrix between the two selected graph. + :rtype: list[list[int]] + + .. seealso:: run_method(), get_forward_map(), get_backward_map(), get_node_image(), get_node_pre_image(), get_node_map() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: This function creates datas so use it if necessary. + """ + return self.c_env.getAssignmentMatrix(g, h) + + + def get_all_map(self, g, h) : + """ + Returns a vector which contains the forward and the backward maps between nodes of the two indicated graphs. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The forward and backward maps to the adjacence matrix between nodes of the two graphs + :rtype: list[list[npy_uint32]] + + .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime(), quasimetric_cost() + .. warning:: run_method() between the same two graph must be called before this function. + .. 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 ! + """ + return self.c_env.getAllMap(g, h) + + + def get_runtime(self, g, h) : + """ + Returns the runtime to compute the edit distance cost between two graphs g and h + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: The runtime of the computation of edit distance cost between the two selected graphs + :rtype: double + + .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), quasimetric_cost() + .. warning:: run_method() between the same two graph must be called before this function. + .. note:: Python is a bit longer than C++ due to the functions's encapsulate. + """ + return self.c_env.getRuntime(g,h) + + + def quasimetric_cost(self) : + """ + Checks and returns if the edit costs are quasimetric. + + :param g: The Id of the first compared graph + :param h: The Id of the second compared graph + :type g: size_t + :type h: size_t + :return: True if it's verified, False otherwise + :rtype: bool + + .. seealso:: run_method(), get_upper_bound(), get_lower_bound(), get_forward_map(), get_backward_map(), get_runtime() + .. warning:: run_method() between the same two graph must be called before this function. + """ + return self.c_env.quasimetricCosts() + + + def hungarian_LSAP(self, matrix_cost) : + """ + Applies the hungarian algorithm (LSAP) on a matrix Cost. + + :param matrix_cost: The matrix Cost + :type matrix_cost: vector[vector[size_t]] + :return: The values of rho, varrho, u and v, in this order + :rtype: vector[vector[size_t]] + + .. seealso:: hungarian_LSAPE() + """ + return self.c_env.hungarianLSAP(matrix_cost) + + + def hungarian_LSAPE(self, matrix_cost) : + """ + Applies the hungarian algorithm (LSAPE) on a matrix Cost. + + :param matrix_cost: The matrix Cost + :type matrix_cost: vector[vector[double]] + :return: The values of rho, varrho, u and v, in this order + :rtype: vector[vector[double]] + + .. seealso:: hungarian_LSAP() + """ + return self.c_env.hungarianLSAPE(matrix_cost) + + + def add_random_graph(self, name, classe, list_of_nodes, list_of_edges, ignore_duplicates=True) : + """ + Add a Graph (not GXL) on the environment. Be careful to respect the same format as GXL graphs for labelling nodes and edges. + + :param name: The name of the graph to add, can be an empty string + :param classe: The classe of the graph to add, can be an empty string + :param list_of_nodes: The list of nodes to add + :param list_of_edges: The list of edges to add + :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default + :type name: string + :type classe: string + :type list_of_nodes: list[tuple(size_t, dict{string : string})] + :type list_of_edges: list[tuple(tuple(size_t,size_t), dict{string : string})] + :type ignore_duplicates: bool + :return: The ID of the newly added graphe + :rtype: size_t + + .. note:: The graph must respect the GXL structure. Please see how a GXL graph is construct. + + """ + id = self.add_graph(name, classe) + for node in list_of_nodes: + self.add_node(id, node[0], node[1]) + for edge in list_of_edges: + self.add_edge(id, edge[0], edge[1], edge[2], ignore_duplicates) + return id + + + def add_nx_graph(self, g, classe, ignore_duplicates=True) : + """ + Add a Graph (made by networkx) on the environment. Be careful to respect the same format as GXL graphs for labelling nodes and edges. + + :param g: The graph to add (networkx graph) + :param ignore_duplicates: If True, duplicate edges are ignored, otherwise it's raise an error if an existing edge is added. True by default + :type g: networkx.graph + :type ignore_duplicates: bool + :return: The ID of the newly added graphe + :rtype: size_t + + .. note:: The NX graph must respect the GXL structure. Please see how a GXL graph is construct. + + """ + id = self.add_graph(g.name, classe) + for node in g.nodes: + self.add_node(id, str(node), g.nodes[node]) + for edge in g.edges: + self.add_edge(id, str(edge[0]), str(edge[1]), g.get_edge_data(edge[0], edge[1]), ignore_duplicates) + return id + + + def compute_ged_on_two_graphs(self, g1, g2, edit_cost, method, options, init_option="EAGER_WITHOUT_SHUFFLED_COPIES") : + """ + Computes the edit distance between two NX graphs. + + :param g1: The first graph to add and compute + :param g2: The second graph to add and compute + :param edit_cost: The name of the edit cost function + :param method: The name of the computation method + :param options: The options of the method (like bash options), an empty string by default + :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default + :type g1: networksx.graph + :type g2: networksx.graph + :type edit_cost: string + :type method: string + :type options: string + :type init_option: string + :return: The edit distance between the two graphs and the nodeMap between them. + :rtype: double, list[tuple(size_t, size_t)] + + .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options + .. 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. + + """ + if self.is_initialized() : + self.restart_env() + + g = self.add_nx_graph(g1, "") + h = self.add_nx_graph(g2, "") + + self.set_edit_cost(edit_cost) + self.init(init_option) + + self.set_method(method, options) + self.init_method() + + resDistance = 0 + resMapping = [] + self.run_method(g, h) + resDistance = self.get_upper_bound(g, h) + resMapping = self.get_node_map(g, h) + + return resDistance, resMapping + + + def compute_edit_distance_on_nx_graphs(self, dataset, classes, edit_cost, method, options, init_option="EAGER_WITHOUT_SHUFFLED_COPIES") : + """ + + Computes all the edit distance between each NX graphs on the dataset. + + :param dataset: The list of graphs to add and compute + :param classes: The classe of all the graph, can be an empty string + :param edit_cost: The name of the edit cost function + :param method: The name of the computation method + :param options: The options of the method (like bash options), an empty string by default + :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default + :type dataset: list[networksx.graph] + :type classes: string + :type edit_cost: string + :type method: string + :type options: string + :type init_option: string + :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. + :rtype: list[list[double]], list[list[list[tuple(size_t, size_t)]]] + + .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options + .. 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. + + """ + if self.is_initialized() : + self.restart_env() + + print("Loading graphs in progress...") + for graph in dataset : + self.add_nx_graph(graph, classes) + listID = self.graph_ids() + print("Graphs loaded ! ") + print("Number of graphs = " + str(listID[1])) + + self.set_edit_cost(edit_cost) + print("Initialization in progress...") + self.init(init_option) + print("Initialization terminated !") + + self.set_method(method, options) + self.init_method() + + resDistance = [[]] + resMapping = [[]] + for g in range(listID[0], listID[1]) : + print("Computation between graph " + str(g) + " with all the others including himself.") + for h in range(listID[0], listID[1]) : + #print("Computation between graph " + str(g) + " and graph " + str(h)) + self.run_method(g, h) + resDistance[g][h] = self.get_upper_bound(g, h) + resMapping[g][h] = self.get_node_map(g, h) + + print("Finish ! The return contains edit distances and NodeMap but you can check the result with graphs'ID until you restart the environment") + return resDistance, resMapping + + + def compute_edit_distance_on_GXl_graphs(self, path_folder, path_XML, edit_cost, method, options="", init_option="EAGER_WITHOUT_SHUFFLED_COPIES") : + """ + Computes all the edit distance between each GXL graphs on the folder and the XMl file. + + :param path_folder: The folder's path which contains GXL graphs + :param path_XML: The XML's path which indicates which graphes you want to load + :param edit_cost: The name of the edit cost function + :param method: The name of the computation method + :param options: The options of the method (like bash options), an empty string by default + :param init_option: The name of the init option, "EAGER_WITHOUT_SHUFFLED_COPIES" by default + :type path_folder: string + :type path_XML: string + :type edit_cost: string + :type method: string + :type options: string + :type init_option: string + :return: The list of the first and last-1 ID of graphs + :rtype: tuple(size_t, size_t) + + .. seealso:: list_of_edit_cost_options, list_of_method_options, list_of_init_options + .. 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. + + """ + + if self.is_initialized() : + self.restart_env() + + print("Loading graphs in progress...") + self.load_GXL_graphs(path_folder, path_XML) + listID = self.graph_ids() + print("Graphs loaded ! ") + print("Number of graphs = " + str(listID[1])) + + self.set_edit_cost(edit_cost) + print("Initialization in progress...") + self.init(init_option) + print("Initialization terminated !") + + self.set_method(method, options) + self.init_method() + + #res = [] + for g in range(listID[0], listID[1]) : + print("Computation between graph " + str(g) + " with all the others including himself.") + for h in range(listID[0], listID[1]) : + #print("Computation between graph " + str(g) + " and graph " + str(h)) + self.run_method(g,h) + #res.append((get_upper_bound(g,h), get_node_map(g,h), get_runtime(g,h))) + + #return res + + print ("Finish ! You can check the result with each ID of graphs ! There are in the return") + print ("Please don't restart the environment or recall this function, you will lose your results !") + return listID + + + def get_num_node_labels(self): + """ + Returns the number of node labels. + + :return: Number of pairwise different node labels contained in the environment. + :rtype: size_t + + .. note:: If 1 is returned, the nodes are unlabeled. + """ + return self.c_env.getNumNodeLabels() + + + def get_node_label(self, label_id): + """ + Returns node label. + + :param label_id: ID of node label that should be returned. Must be between 1 and get_num_node_labels(). + :type label_id: size_t + :return: Node label for selected label ID. + :rtype: dict{string : string} + """ + return decode_your_map(self.c_env.getNodeLabel(label_id)) + + + def get_num_edge_labels(self): + """ + Returns the number of edge labels. + + :return: Number of pairwise different edge labels contained in the environment. + :rtype: size_t + + .. note:: If 1 is returned, the edges are unlabeled. + """ + return self.c_env.getNumEdgeLabels() + + + def get_edge_label(self, label_id): + """ + Returns edge label. + + :param label_id: ID of edge label that should be returned. Must be between 1 and get_num_edge_labels(). + :type label_id: size_t + :return: Edge label for selected label ID. + :rtype: dict{string : string} + """ + return decode_your_map(self.c_env.getEdgeLabel(label_id)) + + +# def get_num_nodes(self, graph_id): +# """ +# Returns the number of nodes. +# +# :param graph_id: ID of an input graph that has been added to the environment. +# :type graph_id: size_t +# :return: Number of nodes in the graph. +# :rtype: size_t +# """ +# return self.c_env.getNumNodes(graph_id) + + def get_avg_num_nodes(self): + """ + Returns average number of nodes. + + :return: Average number of nodes of the graphs contained in the environment. + :rtype: double + """ + return self.c_env.getAvgNumNodes() + + def get_node_rel_cost(self, node_label_1, node_label_2): + """ + Returns node relabeling cost. + + :param node_label_1: First node label. + :param node_label_2: Second node label. + :type node_label_1: dict{string : string} + :type node_label_2: dict{string : string} + :return: Node relabeling cost for the given node labels. + :rtype: double + """ + return self.c_env.getNodeRelCost(encode_your_map(node_label_1), encode_your_map(node_label_2)) + + + def get_node_del_cost(self, node_label): + """ + Returns node deletion cost. + + :param node_label: Node label. + :type node_label: dict{string : string} + :return: Cost of deleting node with given label. + :rtype: double + """ + return self.c_env.getNodeDelCost(encode_your_map(node_label)) + + + def get_node_ins_cost(self, node_label): + """ + Returns node insertion cost. + + :param node_label: Node label. + :type node_label: dict{string : string} + :return: Cost of inserting node with given label. + :rtype: double + """ + return self.c_env.getNodeInsCost(encode_your_map(node_label)) + + + def get_median_node_label(self, node_labels): + """ + Computes median node label. + + :param node_labels: The node labels whose median should be computed. + :type node_labels: list[dict{string : string}] + :return: Median of the given node labels. + :rtype: dict{string : string} + """ + node_labels_b = [encode_your_map(node_label) for node_label in node_labels] + return decode_your_map(self.c_env.getMedianNodeLabel(node_labels_b)) + + + def get_edge_rel_cost(self, edge_label_1, edge_label_2): + """ + Returns edge relabeling cost. + + :param edge_label_1: First edge label. + :param edge_label_2: Second edge label. + :type edge_label_1: dict{string : string} + :type edge_label_2: dict{string : string} + :return: Edge relabeling cost for the given edge labels. + :rtype: double + """ + return self.c_env.getEdgeRelCost(encode_your_map(edge_label_1), encode_your_map(edge_label_2)) + + + def get_edge_del_cost(self, edge_label): + """ + Returns edge deletion cost. + + :param edge_label: Edge label. + :type edge_label: dict{string : string} + :return: Cost of deleting edge with given label. + :rtype: double + """ + return self.c_env.getEdgeDelCost(encode_your_map(edge_label)) + + + def get_edge_ins_cost(self, edge_label): + """ + Returns edge insertion cost. + + :param edge_label: Edge label. + :type edge_label: dict{string : string} + :return: Cost of inserting edge with given label. + :rtype: double + """ + return self.c_env.getEdgeInsCost(encode_your_map(edge_label)) + + + def get_median_edge_label(self, edge_labels): + """ + Computes median edge label. + + :param edge_labels: The edge labels whose median should be computed. + :type edge_labels: list[dict{string : string}] + :return: Median of the given edge labels. + :rtype: dict{string : string} + """ + edge_labels_b = [encode_your_map(edge_label) for edge_label in edge_labels] + return decode_your_map(self.c_env.getMedianEdgeLabel(edge_label_b)) + + + def get_nx_graph(self, graph_id, adj_matrix=True, adj_lists=False, edge_list=False): # @todo + """ + Get graph with id `graph_id` in the form of the NetworkX Graph. + + Parameters + ---------- + graph_id : int + ID of the selected graph. + + adj_matrix : bool + 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. + + adj_lists : bool + No effect for now. + + edge_list : bool + No effect for now. + + Returns + ------- + NetworkX Graph object + The obtained graph. + """ + graph = nx.Graph() + graph.graph['id'] = graph_id + + nb_nodes = self.get_graph_num_nodes(graph_id) + original_node_ids = self.get_original_node_ids(graph_id) + node_labels = self.get_graph_node_labels(graph_id) +# print(original_node_ids) +# print(node_labels) + graph.graph['original_node_ids'] = original_node_ids + + for node_id in range(0, nb_nodes): + graph.add_node(node_id, **node_labels[node_id]) +# graph.nodes[node_id]['original_node_id'] = original_node_ids[node_id] + + edges = self.get_graph_edges(graph_id) + for (head, tail), labels in edges.items(): + graph.add_edge(head, tail, **labels) +# print(edges) + + return graph + + + def get_init_type(self): + """ + Returns the initialization type of the last initialization in string. + + Returns + ------- + string + Initialization type in string. + """ + return self.c_env.getInitType().decode('utf-8') + + +# def get_node_cost(self, label1, label2): +# """ +# Returns node relabeling, insertion, or deletion cost. + +# Parameters +# ---------- +# label1 : int +# First node label. +# +# label2 : int +# Second node label. +# +# Returns +# ------- +# 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. +# """ +# return self.c_env.getNodeCost(label1, label2) + + + def load_nx_graph(self, nx_graph, graph_id, graph_name='', graph_class=''): + """ + Loads NetworkX Graph into the GED environment. + + Parameters + ---------- + nx_graph : NetworkX Graph object + The graph that should be loaded. + + graph_id : int or None + The ID of a graph contained the environment (overwrite existing graph) or add new graph if `None`. + + graph_name : string, optional + The name of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`. + + graph_class : string, optional + The class of newly added graph. The default is ''. Has no effect unless `graph_id` equals `None`. + + Returns + ------- + int + The ID of the newly loaded graph. + """ + if graph_id is None: + graph_id = self.add_graph(graph_name, graph_class) + else: + self.clear_graph(graph_id) + for node in nx_graph.nodes: + self.add_node(graph_id, str(node), nx_graph.nodes[node]) + for edge in nx_graph.edges: + self.add_edge(graph_id, str(edge[0]), str(edge[1]), nx_graph.get_edge_data(edge[0], edge[1])) + return graph_id + + + def compute_induced_cost(self, g_id, h_id, node_map): + """ + Computes the edit cost between two graphs induced by a node map. + + Parameters + ---------- + g_id : int + ID of input graph. + h_id : int + ID of input graph. + node_map: gklearn.ged.env.NodeMap. + The NodeMap instance whose reduced cost will be computed and re-assigned. + + Returns + ------- + None. + """ + relation = [] + node_map.as_relation(relation) +# print(relation) + dummy_node = get_dummy_node() +# print(dummy_node) + for i, val in enumerate(relation): + val1 = dummy_node if val[0] == np.inf else val[0] + val2 = dummy_node if val[1] == np.inf else val[1] + relation[i] = tuple((val1, val2)) +# print(relation) + induced_cost = self.c_env.computeInducedCost(g_id, h_id, relation) + node_map.set_induced_cost(induced_cost) + + +##################################################################### +##LISTS OF EDIT COST FUNCTIONS, METHOD COMPUTATION AND INIT OPTIONS## +##################################################################### + +list_of_edit_cost_options = get_edit_cost_options() +list_of_method_options = get_method_options() +list_of_init_options = get_init_options() + + +##################### +##ERRORS MANAGEMENT## +##################### + +class Error(Exception): + """ + Class for error's management. This one is general. + """ + pass + + +class EditCostError(Error) : + """ + 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). + + :attribute message: The message to print when an error is detected. + :type message: string + """ + def __init__(self, message): + """ + Inits the error with its message. + + :param message: The message to print when the error is detected + :type message: string + """ + self.message = message + + +class MethodError(Error) : + """ + Class for Method Error. Raise an error if a computation method doesn't exist in the library (not in list_of_method_options). + + :attribute message: The message to print when an error is detected. + :type message: string + """ + def __init__(self, message): + """ + Inits the error with its message. + + :param message: The message to print when the error is detected + :type message: string + """ + self.message = message + + +class InitError(Error) : + """ + Class for Init Error. Raise an error if an init option doesn't exist in the library (not in list_of_init_options). + + :attribute message: The message to print when an error is detected. + :type message: string + """ + def __init__(self, message): + """ + Inits the error with its message. + + :param message: The message to print when the error is detected + :type message: string + """ + self.message = message + + +######################################### +##PYTHON FUNCTIONS FOR SOME COMPUTATION## +######################################### + +def encode_your_map(map_u): + """ + Encodes Python unicode strings in dictionnary `map` to utf-8 byte strings for C++ functions. + + :param map_b: The map to encode + :type map_b: dict{string : string} + :return: The encoded map + :rtype: dict{'b'string : 'b'string} + + .. note:: This function is used for type connection. + + """ + res = {} + for key, value in map_u.items(): + res[key.encode('utf-8')] = value.encode('utf-8') + return res + + +def decode_your_map(map_b): + """ + Decodes utf-8 byte strings in `map` from C++ functions to Python unicode strings. + + :param map_b: The map to decode + :type map_b: dict{'b'string : 'b'string} + :return: The decoded map + :rtype: dict{string : string} + + .. note:: This function is used for type connection. + + """ + res = {} + for key, value in map_b.items(): + res[key.decode('utf-8')] = value.decode('utf-8') + return res + + +def decode_graph_edges(map_edge_b): + """ + Decode utf-8 byte strings in graph edges `map` from C++ functions to Python unicode strings. + + Parameters + ---------- + map_edge_b : dict{tuple(size_t, size_t) : dict{'b'string : 'b'string}} + The map to decode. + + Returns + ------- + dict{tuple(size_t, size_t) : dict{string : string}} + The decoded map. + + Notes + ----- + This is a helper function for function `GEDEnv.get_graph_edges()`. + """ + map_edges = {} + for key, value in map_edge_b.items(): + map_edges[key] = decode_your_map(value) + return map_edges + + + + + + + +# cdef extern from "src/GedLibBind.h" namespace "shapes": +# cdef cppclass Rectangle: +# Rectangle() except + +# Rectangle(int, int, int, int) except + +# int x0, y0, x1, y1 +# int getArea() +# void getSize(int* width, int* height) +# void move(int, int) + + +# # Create a Cython extension type which holds a C++ instance +# # as an attribute and create a bunch of forwarding methods +# # Python extension type. +# cdef class PyRectangle: +# cdef Rectangle c_rect # Hold a C++ instance which we're wrapping + +# def __cinit__(self, int x0, int y0, int x1, int y1): +# self.c_rect = Rectangle(x0, y0, x1, y1) + +# def get_area(self): +# return self.c_rect.getArea() + +# def get_size(self): +# cdef int width, height +# self.c_rect.getSize(&width, &height) +# return width, height + +# def move(self, dx, dy): +# self.c_rect.move(dx, dy) + +# # Attribute access +# @property +# def x0(self): +# return self.c_rect.x0 +# @x0.setter +# def x0(self, x0): +# self.c_rect.x0 = x0 + +# # Attribute access +# @property +# def x1(self): +# return self.c_rect.x1 +# @x1.setter +# def x1(self, x1): +# self.c_rect.x1 = x1 + +# # Attribute access +# @property +# def y0(self): +# return self.c_rect.y0 +# @y0.setter +# def y0(self, y0): +# self.c_rect.y0 = y0 + +# # Attribute access +# @property +# def y1(self): +# return self.c_rect.y1 +# @y1.setter +# def y1(self, y1): +# self.c_rect.y1 = y1 \ No newline at end of file