Commit 7b844b13 authored by Fize Jacques's avatar Fize Jacques

Pip installation available + Debug the Graph extension/class + debug GED

parent c486d941
......@@ -19,7 +19,7 @@ To install `GMatch4py`, run the following commands:
```bash
git clone https://github.com/Jacobe2169/GMatch4py.git
cd GMatch4py
(sudo) python3 setup.py install
(sudo) pip(3) install .
```
## Get Started
......@@ -108,7 +108,13 @@ Jacques Fize, *jacques[dot]fize[at]cirad[dot]fr*
Some algorithms from other projects were integrated to Gmatch4py. **Be assured that
each code is associated with a reference to the original.**
## CHANGELOG
### 25.02.2019
* Add New Graph Class. Features : Cython Extensions, precomputed values (degrees, neighbor info), hash representation of edges and nodes for a faster comparison
* Some algorithms are parallelized such as graph edit distances or Jaccard
## TODO List
* Debug algorithms --> :runner: (almost done !)
* Debug algorithms --> Random Walk Kernel
* Optimize algorithms --> Vertex Ranking
* Write the documentation :runner:
\ No newline at end of file
name = "gmatch4py"
\ No newline at end of file
......@@ -8,7 +8,7 @@ from .ged.hausdorff_edit_distance import *
# Kernels algorithms import
from .kernels.weisfeiler_lehman import *
from .kernels.shortest_path_kernel import *
# Helpers import
from .helpers.reader import *
......
......@@ -117,7 +117,7 @@ cdef class AbstractGraphEditDistance(Base):
cdef long[:] n_nodes = np.array([g.size() for g in listgs])
cdef double[:] selected_test = np.array(self.get_selected_array(selected,n))
cdef int i,j
val=np.inf
cdef float inf=np.inf
with nogil, parallel(num_threads=self.cpu_count):
for i in prange(n,schedule='static'):
......@@ -126,6 +126,6 @@ cdef class AbstractGraphEditDistance(Base):
with gil:
comparison_matrix[i][j] = self.distance_ged(listgs[i],listgs[j])
else:
comparison_matrix[i][j] = 0
comparison_matrix[i][j] = inf
#comparison_matrix[j, i] = comparison_matrix[i, j]
return np.array(comparison_matrix)
......@@ -4,6 +4,8 @@ cimport numpy as np
from ..base cimport Base
from cython.parallel cimport prange,parallel
from ..helpers.general import parsenx2graph
cimport cython
cdef class BP_2(Base):
......@@ -49,6 +51,7 @@ cdef class BP_2(Base):
return comparison_matrix
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list listgs, list selected):
cdef int n = len(listgs)
cdef list new_gs=parsenx2graph(listgs)
......
......@@ -5,6 +5,7 @@ cimport numpy as np
from ..base cimport Base
from cython.parallel cimport prange,parallel
from ..helpers.general import parsenx2graph
cimport cython
cdef class HED(Base):
"""
......@@ -44,7 +45,8 @@ cdef class HED(Base):
comparison_matrix[j, i] = comparison_matrix[i, j]
return comparison_matrix
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list listgs, list selected):
cdef int n = len(listgs)
cdef list new_gs=parsenx2graph(listgs,self.node_attr_key,self.edge_attr_key)
......
# coding = utf-8
# coding = utf-8
import glob
from gmatch4py import *
from gmatch4py.helpers.reader import import_dir
from gmatch4py import GraphEditDistance as GED2
from gmatch4py.base import Base
import argparse, os, sys, re, json, logging
import threading
from queue import Queue
import datetime
from functools import wraps
def objectify(func):
"""Mimic an object given a dictionary.
Given a dictionary, create an object and make sure that each of its
keys are accessible via attributes.
If func is a function act as decorator, otherwise just change the dictionary
and return it.
:param func: A function or another kind of object.
:returns: Either the wrapper for the decorator, or the changed value.
Example::
>>> obj = {'old_key': 'old_value'}
>>> oobj = objectify(obj)
>>> oobj['new_key'] = 'new_value'
>>> print oobj['old_key'], oobj['new_key'], oobj.old_key, oobj.new_key
>>> @objectify
... def func():
... return {'old_key': 'old_value'}
>>> obj = func()
>>> obj['new_key'] = 'new_value'
>>> print obj['old_key'], obj['new_key'], obj.old_key, obj.new_key
"""
def create_object(value):
"""Create the object.
Given a dictionary, create an object and make sure that each of its
keys are accessible via attributes.
Ignore everything if the given value is not a dictionary.
:param value: A dictionary or another kind of object.
:returns: Either the created object or the given value.
"""
if isinstance(value, dict):
# Build a simple generic object.
class Object(dict):
def __setitem__(self, key, val):
setattr(self, key, val)
return super(Object, self).__setitem__(key, val)
# Create that simple generic object.
ret_obj = Object()
# Assign the attributes given the dictionary keys.
for key, val in value.items():
if isinstance(val,dict):
ret_obj[key] = objectify(val)
else:
ret_obj[key] = val
setattr(ret_obj, key, val)
return ret_obj
else:
return value
# If func is a function, wrap around and act like a decorator.
if hasattr(func, '__call__'):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function for the decorator.
:returns: The return value of the decorated function.
"""
value = func(*args, **kwargs)
return create_object(value)
return wrapper
# Else just try to objectify the value given.
else:
return create_object(func)
logging.basicConfig(
filename="{0}.csv".format(datetime.datetime.now().strftime("%Y_%m_%d__%H_%M_%S")),
format="%(message)s,%(asctime)s",
level=logging.DEBUG
)
def compute_matrix(config,graphs,selected,dir):
for class_ in config.algorithms_selected:
class_=eval(class_)
logging.info(msg="C_S,BEG,\"{0}\"".format(class_.__name__))
print("Computing the Similarity Matrix for {0}".format(class_.__name__))
if class_ in (GraphEditDistance, BP_2, GreedyEditDistance, HED):
comparator = class_(1, 1, 1, 1)
elif class_ == GED2:
comparator = class_(1, 1, 1, 1,weighted=True)
elif class_ == WeisfeleirLehmanKernel:
comparator = class_(h=2)
else:
comparator=class_()
matrix = comparator.compare(graphs, selected)
matrix = comparator.similarity(matrix)
logging.info(msg="C_S,DONE,\"{0}\"".format(class_.__name__))
output_fn="{0}/{1}_{2}_{3}.npy".format(
config.output_dir.rstrip("/"),
class_.__name__,os.path.basename(dir),
config.experiment_name.replace(" ","_").lower()
)
print(output_fn)
logging.info(msg="M_S,BEG,\"{0}\"".format(class_.__name__))
np.save(output_fn,matrix)
logging.info(msg="M_S,DONE,\"{0}\"".format(class_.__name__))
print("Matrix Saved")
def run(config_filename):
config=objectify(json.load(open(config_filename)))
if not os.path.exists(config.input_graph_dir):
print("Input graph directory doesn't exist!")
sys.exit(1)
if not os.path.exists(config.output_dir):
print("Output matrix directory doesn't exist!")
print("Creating directory")
os.makedirs(config.output_dir)
print("Directory created")
selected=None
if config.selected_graphs:
selected=json.load(open(config.selected_graph_input_filename))
if config.input_graph_sub_dirs:
dirs=[os.path.join(config.input_graph_dir,sub) for sub in config.input_graph_sub_dirs]
else:
dirs=[config.input_graph_dir]
for dir in dirs:
logging.info(msg="L_G,BEGIN,\"\"")
graphs = import_dir(dir)
logging.info(msg="L_G,DONE,\"\"")
threading.Thread(target=compute_matrix,args=(config,graphs,selected,dir)).start()
#json.dump(mapping_files_to_graphs,open("{0}/{1}".format(args.matrix_output_dir.rstrip("/"),"metadata.json")))
print("Done")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("configuration_file")
args = parser.parse_args()
run(args.configuration_file)
\ No newline at end of file
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.10
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
import os, json,glob
from gmatch4py import *
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
self.graph_input_dir=""
self.selected_input_fn=""
self.output_dir=""
self.available_algs=['BP_2','BagOfCliques','BagOfNodes','GraphEditDistance','GreedyEditDistance','HED','Jaccard','MCS','VertexEdgeOverlap','VertexRanking', 'WeisfeleirLehmanKernel']
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1000, 661)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.textBrowser = QtWidgets.QTextBrowser(self.centralWidget)
self.textBrowser.setGeometry(QtCore.QRect(405, 31, 591, 551))
self.textBrowser.setObjectName("textBrowser")
self.label = QtWidgets.QLabel(self.centralWidget)
self.label.setGeometry(QtCore.QRect(410, 10, 100, 16))
self.label.setObjectName("label")
self.graph_dir_but = QtWidgets.QPushButton(self.centralWidget)
self.graph_dir_but.setGeometry(QtCore.QRect(280, 90, 113, 32))
self.graph_dir_but.setObjectName("graph_dir_but")
self.label_2 = QtWidgets.QLabel(self.centralWidget)
self.label_2.setGeometry(QtCore.QRect(10, 70, 200, 16))
self.label_2.setObjectName("label_2")
self.selected_fn_but = QtWidgets.QPushButton(self.centralWidget)
self.selected_fn_but.setGeometry(QtCore.QRect(280, 160, 113, 32))
self.selected_fn_but.setObjectName("selected_fn_but")
self.label_3 = QtWidgets.QLabel(self.centralWidget)
self.label_3.setGeometry(QtCore.QRect(10, 140, 300, 16))
self.label_3.setObjectName("label_3")
self.generate_button = QtWidgets.QPushButton(self.centralWidget)
self.generate_button.setGeometry(QtCore.QRect(20, 540, 113, 32))
self.generate_button.setObjectName("generate_button")
self.label_4 = QtWidgets.QLabel(self.centralWidget)
self.label_4.setGeometry(QtCore.QRect(10, 210, 131, 16))
self.label_4.setObjectName("label_4")
self.ouptut_dir_but = QtWidgets.QPushButton(self.centralWidget)
self.ouptut_dir_but.setGeometry(QtCore.QRect(280, 230, 113, 32))
self.ouptut_dir_but.setObjectName("ouptut_dir_but")
self.all_alg = QtWidgets.QCheckBox(self.centralWidget)
self.all_alg.setGeometry(QtCore.QRect(10, 500, 200, 20))
self.all_alg.setObjectName("all_alg")
self.save_button = QtWidgets.QPushButton(self.centralWidget)
self.save_button.setGeometry(QtCore.QRect(150, 540, 200, 32))
self.save_button.setObjectName("save_button")
self.label_5 = QtWidgets.QLabel(self.centralWidget)
self.label_5.setGeometry(QtCore.QRect(10, 30, 121, 31))
self.label_5.setObjectName("label_5")
self.experiment_name = QtWidgets.QLineEdit(self.centralWidget)
self.experiment_name.setGeometry(QtCore.QRect(130, 30, 231, 31))
self.experiment_name.setObjectName("experiment_name")
self.graph_dir_label = QtWidgets.QLineEdit(self.centralWidget)
self.graph_dir_label.setGeometry(QtCore.QRect(10, 90, 261, 31))
self.graph_dir_label.setObjectName("graph_dir_label")
self.selected_file_label = QtWidgets.QLineEdit(self.centralWidget)
self.selected_file_label.setGeometry(QtCore.QRect(10, 160, 261, 31))
self.selected_file_label.setObjectName("selected_file_label")
self.output_dir_label = QtWidgets.QLineEdit(self.centralWidget)
self.output_dir_label.setGeometry(QtCore.QRect(10, 230, 261, 31))
self.output_dir_label.setText("")
self.output_dir_label.setObjectName("output_dir_label")
self.alg_selector = QtWidgets.QListWidget(self.centralWidget)
self.alg_selector.setGeometry(QtCore.QRect(10, 300, 256, 192))
self.alg_selector.setObjectName("alg_selector")
self.alg_selector.setSelectionMode(QtWidgets.QListWidget.MultiSelection)
self.label_6 = QtWidgets.QLabel(self.centralWidget)
self.label_6.setGeometry(QtCore.QRect(10, 280, 221, 16))
self.label_6.setObjectName("label_6")
MainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtWidgets.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 1000, 22))
self.menuBar.setObjectName("menuBar")
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QtWidgets.QToolBar(MainWindow)
self.mainToolBar.setObjectName("mainToolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
self.statusBar = QtWidgets.QStatusBar(MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
for item in self.available_algs:
self.alg_selector.addItem(item)
self.graph_dir_but.clicked.connect(self.get_graph_input_dir)
self.ouptut_dir_but.clicked.connect(self.get_res_output_dir)
self.selected_fn_but.clicked.connect(self.get_selected_file)
self.generate_button.clicked.connect(self.generate_conf)
self.save_button.clicked.connect(self.file_save)
def openDirNameDialog(self,title):
fileName = QtWidgets.QFileDialog.getExistingDirectory(None,title)
if not fileName:
return ""
return str(fileName)
def openFileNameDialog(self,title):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
filename,_ = QtWidgets.QFileDialog.getOpenFileNames(None)
if filename:
return filename
else:
return ""
def get_graph_input_dir(self):
fn = self.openDirNameDialog("Graph Input Dir")
self.graph_dir_label.setText(fn)
self.graph_input_dir=fn
def get_res_output_dir(self):
fn=self.openDirNameDialog("Results Output Dir")
self.output_dir_label.setText(fn)
self.output_dir=fn
def get_selected_file(self):
fn=self.openFileNameDialog("SelectGraph File")
self.selected_file_label.setText(fn[0])
self.selected_input_fn=fn[0]
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Générateur de Configuration pour Gmatch4py"))
self.label.setText(_translate("MainWindow", "Configuration:"))
self.graph_dir_but.setText(_translate("MainWindow", "Parcourir"))
self.label_2.setText(_translate("MainWindow", "Dossier contenant les graphes"))
self.selected_fn_but.setText(_translate("MainWindow", "Parcourir"))
self.label_3.setText(_translate("MainWindow", "Fichier contenant les graphes sélectionnés"))
self.generate_button.setText(_translate("MainWindow", "Générer"))
self.save_button.setText(_translate("MainWindow", "Sauvegarder la configuration"))
self.label_4.setText(_translate("MainWindow", "Dossier de Sortie"))
self.ouptut_dir_but.setText(_translate("MainWindow", "Parcourir"))
self.all_alg.setText(_translate("MainWindow", "Selectionnez tout ?"))
self.label_5.setText(_translate("MainWindow", "Nom de l'expérimentation"))
self.label_6.setText(_translate("MainWindow", "Sélectionnez les algorithmes :"))
def file_save(self):
name,_ = QtWidgets.QFileDialog.getSaveFileName(None, 'Sauvegarder la configuration')
print(name)
if name:
file = open(name,'w')
text = self.generate_conf()[1]
file.write(text)
file.close()
msg=QtWidgets.QMessageBox()
msg.setText("Sauvegarde")
msg.setInformativeText("Sauvegarde Réussie")
msg.setWindowTitle("Sauvegarde")
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg.exec_()
def generate_conf(self):
conf= {
"experiment_name":self.experiment_name.text(),
"input_graph_dir":self.graph_input_dir,
"input_graph_sub_dirs":[dir_ for dir_ in next(os.walk(self.graph_input_dir))[1] if next(os.walk(self.graph_input_dir))[1]],
"selected_graphs":(True if self.selected_input_fn else False),
"selected_graph_input_filename":self.selected_input_fn,
"algorithms_selected": [item.text() for item in self.alg_selector.selectedItems()] if not self.all_alg.isChecked() else self.available_algs,
"execute_all_algorithms": self.all_alg.isChecked()
}
str_conf=json.dumps(conf,indent=2)
self.textBrowser.setPlainText(str_conf)
return conf,str_conf
def run_conf_generator():
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
if __name__ == "__main__":
run_conf_generator()
......@@ -7,6 +7,7 @@ from .base cimport Base
from .base cimport intersection,union_
from .helpers.general import parsenx2graph
from cython.parallel cimport prange,parallel
cimport cython
cdef class Jaccard(Base):
......@@ -39,6 +40,7 @@ cdef class Jaccard(Base):
return comparison_matrix
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list listgs, list selected):
cdef int n = len(listgs)
cdef list new_gs=parsenx2graph(listgs,self.node_attr_key,self.edge_attr_key)
......@@ -58,7 +60,7 @@ cdef class Jaccard(Base):
intersect_len_nodes[i][j]=new_gs[i].size_node_intersect(new_gs[j])
intersect_len_edges[i][j]=new_gs[i].size_edge_intersect(new_gs[j])#len(set(hash_edges[i]).intersection(hash_edges[j]))
union_len_nodes[i][j]=new_gs[i].size_node_union(new_gs[j])
union_len_edges[i][j]=new_gs[i].size_node_union(new_gs[j])
union_len_edges[i][j]=new_gs[i].size_edge_union(new_gs[j])
with nogil, parallel(num_threads=self.cpu_count):
for i in prange(n,schedule='static'):
for j in range(i,n):
......
......@@ -18,6 +18,7 @@ from .adjacency import get_adjacency
from cython.parallel cimport prange,parallel
from ..helpers.general import parsenx2graph
from ..base cimport Base
cimport cython
cdef class ShortestPathGraphKernel(Base):
"""
......@@ -65,7 +66,7 @@ cdef class ShortestPathGraphKernel(Base):
return np.sum(v1 * v2)
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list graph_list, list selected):
"""Compute the all-pairs kernel values for a list of graphs.
This function can be used to directly compute the kernel
......@@ -84,8 +85,9 @@ cdef class ShortestPathGraphKernel(Base):
cdef int n = len(graph_list)
cdef double[:,:] k = np.zeros((n, n))
cdef int cpu_count = self.cpu_count
cdef list adjacency_matrices = [[None for i in range(n)]for j in range(n)]
cdef int i,j
cdef list adjacency_matrices = [[None for i in range(n)]for j in range(n)]
for i in range(n):
for j in range(i, n):
adjacency_matrices[i][j] = get_adjacency(graph_list[i],graph_list[j])
......
......@@ -5,6 +5,7 @@ from .graph cimport Graph
from .base cimport Base
from cython.parallel cimport prange,parallel
from .helpers.general import parsenx2graph
cimport cython
cdef class MCS(Base):
"""
......@@ -27,7 +28,8 @@ cdef class MCS(Base):
comparison_matrix[i, j] = 0.
comparison_matrix[j, i] = comparison_matrix[i, j]
return comparison_matrix
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list listgs, list selected):
cdef int n = len(listgs)
cdef double [:,:] comparison_matrix = np.zeros((n, n))
......@@ -54,20 +56,4 @@ cdef class MCS(Base):
return np.array(comparison_matrix)
def s_mcs(self,G, H):
"""
Return the MCS measure value between
Parameters
----------
G : networkx.Graph
First Graph
H : networkx.Graph
Second Graph
Returns
-------
"""
return len(self.mcs(G, H)) / float(max(len(G), len(H)))
......@@ -6,7 +6,7 @@ from .base cimport Base,intersection
from .graph cimport Graph
from cython.parallel cimport prange,parallel
from .helpers.general import parsenx2graph
cimport cython
cdef class VertexEdgeOverlap(Base):
"""
......@@ -39,6 +39,7 @@ cdef class VertexEdgeOverlap(Base):
comparison_matrix[j, i] = comparison_matrix[i, j]
return comparison_matrix
@cython.boundscheck(False)
cpdef np.ndarray compare(self,list listgs, list selected):
cdef int n = len(listgs)
cdef list new_gs=parsenx2graph(listgs,self.node_attr_key,self.edge_attr_key)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment