From 41f3f2e489a5be45021ce432c25a33dafdcd84ba Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 19 Sep 2023 15:51:54 +0200 Subject: [PATCH 01/12] [Enhancement] Add `get_params()` method to `GraphKernel` class. --- gklearn/kernels/graph_kernel.py | 101 +++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/gklearn/kernels/graph_kernel.py b/gklearn/kernels/graph_kernel.py index 52c22d38cc..708c11302f 100644 --- a/gklearn/kernels/graph_kernel.py +++ b/gklearn/kernels/graph_kernel.py @@ -189,8 +189,60 @@ def fit_transform(self, X, save_gm_train=False): return gram_matrix - def get_params(self): - pass + def get_params( + self, + with_graphs: bool = False, + with_ndarray: bool = False + ): + """Get parameters for this estimator. + + Parameters + ---------- + with_graphs : bool, optional + Whether to include the graphs. Default: False. + + with_ndarray : bool, optional + Whether to include the ndarray. Default: False. + + Returns + ------- + params : dict + Parameter names mapped to their values. + """ + # loop over attributes in the object: + params = dict() + for key, value in self.__dict__.items(): + # if the attribute is a list of graphs or a graph: + if (isinstance(value, list) and len(value) > 0 and \ + isinstance(value[0], nx.Graph)) or \ + isinstance(value, nx.Graph): + if with_graphs: + # add the name(s) and params to dict: + params[key] = dict() + params[key]['name'] = value[0].__class__.__name__ + params[key]['params'] = value[0].get_params() + else: + continue + + # if the attribute is a numpy array: + elif isinstance(value, np.ndarray): + if with_ndarray: + params[key] = value + else: + continue + + # if the attribute is a simple type, add it to dict: + if not hasattr(value, '__dict__'): + params[key] = value + # if the attribute is a function, add its name to dict: + elif hasattr(value, '__call__'): + params[key] = value.__name__ + # if the attribute is a class, add its name and params to dict: + else: + params[key] = dict() + params[key]['name'] = value.__class__.__name__ + params[key]['params'] = value.get_params() + return params def set_params(self): @@ -206,6 +258,8 @@ def clear_attributes(self): delattr(self, '_Y') if hasattr(self, '_run_time'): delattr(self, '_run_time') + if hasattr(self, '_test_run_time'): + delattr(self, '_test_run_time') def validate_parameters(self): @@ -282,9 +336,8 @@ def compute_kernel_matrix(self, Y=None): else: # Compute kernel matrix between Y and self._graphs (X). - start_time = time.time() - if self.parallel == 'imap_unordered': + start_time = time.time() kernel_matrix = self._compute_kernel_matrix_imap_unordered(Y) elif self.parallel is None: @@ -293,15 +346,16 @@ def compute_kernel_matrix(self, Y=None): [g.copy() for g in self._graphs] if self.copy_graphs else self._graphs ) + start_time = time.time() kernel_matrix = self._compute_kernel_matrix_series( Y_copy, graphs_copy ) - self._run_time = time.time() - start_time + self._test_run_time = time.time() - start_time if self.verbose: print( 'Kernel matrix of size (%d, %d) built in %s seconds.' - % (len(Y), len(self._graphs), self._run_time) + % (len(Y), len(self._graphs), self._test_run_time) ) return kernel_matrix @@ -443,13 +497,13 @@ def compute(self, *graphs, **kwargs): graphs[0]] # @todo: might be very slow. else: self._graphs = graphs - self._gram_matrix = self._compute_gram_matrix() + self._gm_train = self._compute_gram_matrix() if self.save_unnormed: - self._gram_matrix_unnorm = np.copy(self._gram_matrix) + self._gram_matrix_unnorm = np.copy(self._gm_train) if self.normalize: - self._gram_matrix = normalize_gram_matrix(self._gram_matrix) - return self._gram_matrix, self._run_time + self._gm_train = normalize_gram_matrix(self._gm_train) + return self._gm_train, self._run_time elif len(graphs) == 2: # If the inputs are two graphs. @@ -512,15 +566,15 @@ def normalize_gm(gram_matrix): def compute_distance_matrix(self): - if self._gram_matrix is None: + if self._gm_train is None: raise Exception( 'Please compute the Gram matrix before computing distance matrix.' ) - dis_mat = np.empty((len(self._gram_matrix), len(self._gram_matrix))) - for i in range(len(self._gram_matrix)): - for j in range(i, len(self._gram_matrix)): - dis = self._gram_matrix[i, i] + self._gram_matrix[j, j] - 2 * \ - self._gram_matrix[i, j] + dis_mat = np.empty((len(self._gm_train), len(self._gm_train))) + for i in range(len(self._gm_train)): + for j in range(i, len(self._gm_train)): + dis = self._gm_train[i, i] + self._gm_train[j, j] - 2 * \ + self._gm_train[i, j] if dis < 0: if dis > -1e-10: dis = 0 @@ -535,15 +589,17 @@ def compute_distance_matrix(self): def _compute_gram_matrix(self): - start_time = time.time() - if self.parallel == 'imap_unordered': + start_time = time.time() gram_matrix = self._compute_gm_imap_unordered() + elif self.parallel is None: graphs = ( [g.copy() for g in self._graphs] if self.copy_graphs else self._graphs) + start_time = time.time() gram_matrix = self._compute_gm_series(graphs) + else: raise Exception('Parallel mode is not set correctly.') @@ -665,14 +721,19 @@ def run_time(self): return self._run_time + @property + def test_run_time(self): + return self._test_run_time + + @property def gram_matrix(self): - return self._gram_matrix + return self._gm_train @gram_matrix.setter def gram_matrix(self, value): - self._gram_matrix = value + self._gm_train = value @property From d25ab264a2cda68baa734108f63eafabbaf90625 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 19 Sep 2023 15:52:33 +0200 Subject: [PATCH 02/12] [Enhancement] Add `__init__.py` file to `ged.model` module. --- gklearn/ged/model/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gklearn/ged/model/__init__.py diff --git a/gklearn/ged/model/__init__.py b/gklearn/ged/model/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 47417a6257c1c4b7d42ca0ef8398e3995c291b67 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 19 Sep 2023 18:53:34 +0200 Subject: [PATCH 03/12] [Enhancement] Update `get_params()` method of `GraphKernel` class. --- gklearn/kernels/graph_kernel.py | 19 +++++--- gklearn/utils/__init__.py | 1 + gklearn/utils/utils.py | 82 ++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/gklearn/kernels/graph_kernel.py b/gklearn/kernels/graph_kernel.py index 708c11302f..65f49a6e06 100644 --- a/gklearn/kernels/graph_kernel.py +++ b/gklearn/kernels/graph_kernel.py @@ -13,7 +13,7 @@ from sklearn.base import BaseEstimator # , TransformerMixin from sklearn.utils.validation import check_is_fitted # check_X_y, check_array, from sklearn.exceptions import NotFittedError -from gklearn.utils import normalize_gram_matrix +from gklearn.utils import normalize_gram_matrix, is_basic_python_type class GraphKernel(BaseEstimator): # , ABC): @@ -231,17 +231,22 @@ def get_params( else: continue - # if the attribute is a simple type, add it to dict: - if not hasattr(value, '__dict__'): - params[key] = value - # if the attribute is a function, add its name to dict: + # If the attribute is a function, add its name to dict: elif hasattr(value, '__call__'): params[key] = value.__name__ - # if the attribute is a class, add its name and params to dict: - else: + + # If the attribute is a class, add its name and params to dict: + elif hasattr(value, '__dict__'): params[key] = dict() params[key]['name'] = value.__class__.__name__ params[key]['params'] = value.get_params() + + # If the attribute is a basic type, add it to dict: + elif is_basic_python_type(value, deep=True): + params[key] = value + + # Otherwise, do nothing. + return params diff --git a/gklearn/utils/__init__.py b/gklearn/utils/__init__.py index 0461a78c61..2bc07fabef 100644 --- a/gklearn/utils/__init__.py +++ b/gklearn/utils/__init__.py @@ -22,6 +22,7 @@ from gklearn.utils.utils import compute_gram_matrices_by_class from gklearn.utils.utils import SpecialLabel, dummy_node, undefined_node, dummy_edge from gklearn.utils.utils import normalize_gram_matrix, compute_distance_matrix +from gklearn.utils.utils import check_json_serializable, is_basic_python_type from gklearn.utils.trie import Trie from gklearn.utils.knn import knn_cv, knn_classification from gklearn.utils.model_selection_precomputed import model_selection_for_precomputed_kernel diff --git a/gklearn/utils/utils.py b/gklearn/utils/utils.py index 3b3cafec1f..15ab296665 100644 --- a/gklearn/utils/utils.py +++ b/gklearn/utils/utils.py @@ -841,4 +841,84 @@ class SpecialLabel(Enum): """can be used to define special labels. """ DUMMY = 1 # The dummy label. - # DUMMY = auto # enum.auto does not exist in Python 3.5. \ No newline at end of file + # DUMMY = auto # enum.auto does not exist in Python 3.5. + + +#%% + + +def check_json_serializable( + obj, + deep: bool = False, +) -> bool: + """Check if an object is JSON serializable. + + Parameters + ---------- + obj : object + The object to be checked. + + deep : bool, optional + Whether to check the object recursively when `obj` is iterable. + The default is False. + + Returns + ------- + bool + True if the object is JSON serializable, False otherwise. + """ + import json + try: + json.dumps(obj) + except TypeError: + return False + else: + if deep and hasattr(obj, '__iter__'): + for item in obj: + if not check_json_serializable(item, deep=True): + return False + return True + + +def is_basic_python_type( + obj, + type_list: list = None, + deep: bool = False, +) -> bool: + """Check if an object is a basic type in Python. + + Parameters + ---------- + obj : object + The object to be checked. + + type_list : list, optional + The list of basic types in Python. The default is None, which means + the default basic types are used. The default basic types include + `int`, `float`, `complex`, `str`, `bool`, `NoneType`, `list`, + `tuple`, `dict`, `set`, `frozenset`, `range`, `slice`. + + deep : bool, optional + Whether to check the object recursively when `obj` is iterable. + The default is False. + + Returns + ------- + bool + True if the object is a basic type in Python, False otherwise. + """ + if type_list is None: + type_list = [ + int, float, complex, str, bool, type(None), list, tuple, dict, + set, frozenset, range, slice + ] + if not hasattr(obj, '__iter__') or isinstance(obj, (str, bytes)): + return type(obj) in type_list + else: + if deep: + for item in obj: + if not is_basic_python_type(item, type_list=type_list, deep=True): + return False + return True + else: + return False From 6589376ea9d294e14b3ef2b3195b8e8136fe7730 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 20 Sep 2023 11:24:50 +0200 Subject: [PATCH 04/12] [Enhancement] Update `get_params()` method of `GraphKernel` class. --- gklearn/kernels/graph_kernel.py | 64 +++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/gklearn/kernels/graph_kernel.py b/gklearn/kernels/graph_kernel.py index 65f49a6e06..a08a0b473e 100644 --- a/gklearn/kernels/graph_kernel.py +++ b/gklearn/kernels/graph_kernel.py @@ -5,14 +5,18 @@ @author: ljia """ +import time +import functools +import multiprocessing + import numpy as np import networkx as nx -import multiprocessing -import time # from abc import ABC, abstractmethod + from sklearn.base import BaseEstimator # , TransformerMixin from sklearn.utils.validation import check_is_fitted # check_X_y, check_array, from sklearn.exceptions import NotFittedError + from gklearn.utils import normalize_gram_matrix, is_basic_python_type @@ -192,7 +196,8 @@ def fit_transform(self, X, save_gm_train=False): def get_params( self, with_graphs: bool = False, - with_ndarray: bool = False + with_ndarray: bool = False, + check_json_serializable: bool = True ): """Get parameters for this estimator. @@ -204,48 +209,77 @@ def get_params( with_ndarray : bool, optional Whether to include the ndarray. Default: False. + check_json_serializable : bool, optional + Whether to check if the parameters are JSON serializable. Default: True. + todo: maybe this needs to be checked in case some important attributes are + removed. + Returns ------- params : dict Parameter names mapped to their values. + + Todos + ----- + It may be better to seperate this method with the __str__ method. """ # loop over attributes in the object: params = dict() for key, value in self.__dict__.items(): + cur_params = dict() # if the attribute is a list of graphs or a graph: if (isinstance(value, list) and len(value) > 0 and \ isinstance(value[0], nx.Graph)) or \ isinstance(value, nx.Graph): if with_graphs: - # add the name(s) and params to dict: - params[key] = dict() - params[key]['name'] = value[0].__class__.__name__ - params[key]['params'] = value[0].get_params() + # add the name(s) and params to dict: + cur_params[key] = dict() + cur_params[key]['name'] = value[0].__class__.__name__ + cur_params[key]['params'] = value[0].get_params() else: continue # if the attribute is a numpy array: elif isinstance(value, np.ndarray): if with_ndarray: - params[key] = value + cur_params[key] = value else: continue - # If the attribute is a function, add its name to dict: + # If the attribute is a function: elif hasattr(value, '__call__'): - params[key] = value.__name__ + # If it is a partial function, add its `__str__()`: + if isinstance(value, functools.partial): + cur_params[key] = str(value) + # Otherwise, add its name to dict: + else: + cur_params[key] = value.__name__ # If the attribute is a class, add its name and params to dict: elif hasattr(value, '__dict__'): - params[key] = dict() - params[key]['name'] = value.__class__.__name__ - params[key]['params'] = value.get_params() + cur_params[key] = dict() + cur_params[key]['name'] = value.__class__.__name__ + cur_params[key]['params'] = value.get_params() # If the attribute is a basic type, add it to dict: elif is_basic_python_type(value, deep=True): - params[key] = value + cur_params[key] = value # Otherwise, do nothing. + else: + continue + + # todo: SpecialLabel.DUMMY (e.g., COX2 + Path) + + if check_json_serializable: + # If the current params is serializable, add it to params: + try: + import json + json.dumps(cur_params) + except TypeError: + continue + + params[key] = cur_params[key] return params @@ -604,7 +638,7 @@ def _compute_gram_matrix(self): self._graphs] if self.copy_graphs else self._graphs) start_time = time.time() gram_matrix = self._compute_gm_series(graphs) - + else: raise Exception('Parallel mode is not set correctly.') From dfd01540f51bbc96c97174f0d921f3fa1f2c62da Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 20 Sep 2023 15:35:20 +0200 Subject: [PATCH 05/12] [Enhancement] Update `get_params()` method of `GraphKernel` class to include module pass when value is a function. --- gklearn/kernels/graph_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gklearn/kernels/graph_kernel.py b/gklearn/kernels/graph_kernel.py index a08a0b473e..10bc344a13 100644 --- a/gklearn/kernels/graph_kernel.py +++ b/gklearn/kernels/graph_kernel.py @@ -253,7 +253,7 @@ def get_params( cur_params[key] = str(value) # Otherwise, add its name to dict: else: - cur_params[key] = value.__name__ + cur_params[key] = value.__module__ + '.' + value.__name__ # If the attribute is a class, add its name and params to dict: elif hasattr(value, '__dict__'): From ffb7742105e91619db66ed0edd93926ec2c9a2f7 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sat, 30 Sep 2023 19:33:02 +0200 Subject: [PATCH 06/12] [Fix] Update Treelet Model, replace `G.degree(node)` by `len(G[node])`, so that it will be able to deal with self loop, Since `G.degree(node)` counts a self loop as 2 degrees in undirected graphs. --- gklearn/kernels/treelet.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gklearn/kernels/treelet.py b/gklearn/kernels/treelet.py index c9e890c18e..199123eb57 100644 --- a/gklearn/kernels/treelet.py +++ b/gklearn/kernels/treelet.py @@ -524,11 +524,11 @@ def _get_canonkeys(self, G): # n-star patterns patterns['3star'] = [[node] + [neighbor for neighbor in G[node]] for - node in G.nodes() if G.degree(node) == 3] + node in G.nodes() if len(G[node]) == 3] patterns['4star'] = [[node] + [neighbor for neighbor in G[node]] for - node in G.nodes() if G.degree(node) == 4] + node in G.nodes() if len(G[node]) == 4] patterns['5star'] = [[node] + [neighbor for neighbor in G[node]] for - node in G.nodes() if G.degree(node) == 5] + node in G.nodes() if len(G[node]) == 5] # n-star patterns canonkey['6'] = len(patterns['3star']) canonkey['8'] = len(patterns['4star']) @@ -538,7 +538,7 @@ def _get_canonkeys(self, G): patterns['7'] = [] # the 1st line of Table 1 in Ref [1] for pattern in patterns['3star']: for i in range(1, len(pattern)): # for each neighbor of node 0 - if G.degree(pattern[i]) >= 2: + if len(G[pattern[i]]) >= 2: pattern_t = pattern[:] # set the node with degree >= 2 as the 4th node pattern_t[i], pattern_t[3] = pattern_t[3], pattern_t[i] @@ -552,7 +552,7 @@ def _get_canonkeys(self, G): patterns['11'] = [] # the 4th line of Table 1 in Ref [1] for pattern in patterns['4star']: for i in range(1, len(pattern)): - if G.degree(pattern[i]) >= 2: + if len(G[pattern[i]]) >= 2: pattern_t = pattern[:] pattern_t[i], pattern_t[4] = pattern_t[4], pattern_t[i] for neighborx in G[pattern[i]]: @@ -569,7 +569,7 @@ def _get_canonkeys(self, G): 0] not in rootlist: # prevent to count the same pattern twice from each of the two root nodes rootlist.append(pattern[0]) for i in range(1, len(pattern)): - if G.degree(pattern[i]) >= 3: + if len(G[pattern[i]]) >= 3: rootlist.append(pattern[i]) pattern_t = pattern[:] pattern_t[i], pattern_t[3] = pattern_t[3], pattern_t[i] @@ -588,9 +588,9 @@ def _get_canonkeys(self, G): patterns['9'] = [] # the 2nd line of Table 1 in Ref [1] for pattern in patterns['3star']: for pairs in [[neighbor1, neighbor2] for neighbor1 in G[pattern[0]] - if G.degree(neighbor1) >= 2 \ + if len(G[neighbor1]) >= 2 \ for neighbor2 in G[pattern[0]] if - G.degree(neighbor2) >= 2 if neighbor1 > neighbor2]: + len(G[neighbor2]) >= 2 if neighbor1 > neighbor2]: pattern_t = pattern[:] # move nodes with extended labels 4 to specific position to correspond to their children pattern_t[pattern_t.index(pairs[0])], pattern_t[2] = pattern_t[ @@ -610,9 +610,9 @@ def _get_canonkeys(self, G): patterns['10'] = [] # the 3rd line of Table 1 in Ref [1] for pattern in patterns['3star']: for i in range(1, len(pattern)): - if G.degree(pattern[i]) >= 2: + if len(G[pattern[i]]) >= 2: for neighborx in G[pattern[i]]: - if neighborx != pattern[0] and G.degree(neighborx) >= 2: + if neighborx != pattern[0] and len(G[neighborx]) >= 2: pattern_t = pattern[:] pattern_t[i], pattern_t[3] = pattern_t[3], \ pattern_t[i] From 93e33501afee0f2322e99b5e5820ceaeb00312e2 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sat, 30 Sep 2023 20:11:08 +0200 Subject: [PATCH 07/12] [Fix][Todo] Temporarily fix the graph kernels for self loops by removing these loops a prior. To be fixed properly. --- gklearn/kernels/graph_kernel.py | 8 ++++++++ gklearn/kernels/treelet.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/gklearn/kernels/graph_kernel.py b/gklearn/kernels/graph_kernel.py index 10bc344a13..7992396942 100644 --- a/gklearn/kernels/graph_kernel.py +++ b/gklearn/kernels/graph_kernel.py @@ -636,6 +636,14 @@ def _compute_gram_matrix(self): graphs = ( [g.copy() for g in self._graphs] if self.copy_graphs else self._graphs) + + # todo: this is just a temporary fix for the self loop problem. + # Remove self loops from the graphs: + for g in graphs: + for node in g: + if g.has_edge(node, node): + g.remove_edge(node, node) + start_time = time.time() gram_matrix = self._compute_gm_series(graphs) diff --git a/gklearn/kernels/treelet.py b/gklearn/kernels/treelet.py index 199123eb57..cd7831df22 100644 --- a/gklearn/kernels/treelet.py +++ b/gklearn/kernels/treelet.py @@ -585,6 +585,9 @@ def _get_canonkeys(self, G): canonkey['c'] = int(len(patterns['12']) / 2) # pattern 9 + # todo: this is not correct for self loops, but for now, we simply remove + # self loops from the graph at the beginning of the model, + # see GraphKernel._compute_gram_matrix(). patterns['9'] = [] # the 2nd line of Table 1 in Ref [1] for pattern in patterns['3star']: for pairs in [[neighbor1, neighbor2] for neighbor1 in G[pattern[0]] From b71574244fdd66563c3df67f837a39c0fda7c3d1 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sun, 1 Oct 2023 13:51:41 +0200 Subject: [PATCH 08/12] [Enhancement] Update GEDModel. --- gklearn/ged/model/ged_model.py | 49 +++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/gklearn/ged/model/ged_model.py b/gklearn/ged/model/ged_model.py index 30e89cb06c..eb3f6afc8b 100644 --- a/gklearn/ged/model/ged_model.py +++ b/gklearn/ged/model/ged_model.py @@ -258,6 +258,8 @@ def clear_attributes(self): # @todo: update delattr(self, '_Y') if hasattr(self, '_run_time'): delattr(self, '_run_time') + if hasattr(self, '_test_run_time'): + delattr(self, '_test_run_time') def validate_parameters(self): @@ -330,24 +332,26 @@ def compute_distance_matrix(self, Y=None, **kwargs): else: # Compute kernel matrix between Y and self._graphs (X). + Y_copy = ([g.copy() for g in Y] if self.copy_graphs else Y) + graphs_copy = ([g.copy() for g in + self._graphs] if self.copy_graphs else self._graphs) + start_time = time.time() if self.parallel == 'imap_unordered': dis_matrix = self._compute_distance_matrix_imap_unordered(Y) elif self.parallel is None: - Y_copy = ([g.copy() for g in Y] if self.copy_graphs else Y) - graphs_copy = ([g.copy() for g in - self._graphs] if self.copy_graphs else self._graphs) dis_matrix = self._compute_distance_matrix_series( Y_copy, graphs_copy, **kwargs ) - self._run_time = time.time() - start_time + self._test_run_time = time.time() - start_time + if self.verbose: print( 'Distance matrix of size (%d, %d) built in %s seconds.' - % (len(Y), len(self._graphs), self._run_time) + % (len(Y), len(self._graphs), self._test_run_time) ) return dis_matrix @@ -620,10 +624,11 @@ def compute_edit_costs(self, Y=None, Y_targets=None, **kwargs): # return dis_mat, dis_max, dis_min, dis_mean def _compute_X_distance_matrix(self, **kwargs): - start_time = time.time() - graphs = ([g.copy() for g in self._graphs] if self.copy_graphs else self._graphs) + + start_time = time.time() + if self.parallel == 'imap_unordered': dis_matrix = self._compute_X_dm_imap_unordered(graphs, **kwargs) elif self.parallel is None: @@ -632,6 +637,7 @@ def _compute_X_distance_matrix(self, **kwargs): raise Exception('Parallel mode is not set correctly.') self._run_time = time.time() - start_time + if self.verbose: print( 'Distance matrix of size %d built in %s seconds.' @@ -646,7 +652,7 @@ def _compute_X_dm_series(self, graphs, **kwargs): dis_matrix = np.zeros((n, n)) iterator = combinations(range(n), 2) - len_itr = int(n * (n + 1) / 2) + len_itr = int(n * (n - 1) / 2) if self.verbose: print('Graphs in total: %d.' % len(graphs)) print('The total # of pairs is %d.' % len_itr) @@ -780,6 +786,26 @@ def is_graph(self, graph): if isinstance(graph, nx.MultiDiGraph): return True return False + + + def __repr__(self): + return ( + f"{self.__class__.__name__}(" + f"optim_method={self.optim_method}, " + f"ed_method={self.ed_method}, " + f"edit_cost_fun={self.edit_cost_fun}, " + f"node_labels={self.node_labels}, " + f"edge_labels={self.edge_labels}, " + f"optim_options={self.optim_options}, " + f"init_edit_cost_constants={self.init_edit_cost_constants}, " + f"copy_graphs={self.copy_graphs}, " + f"parallel={self.parallel}, " + f"n_jobs={self.n_jobs}, " + f"verbose={self.verbose}, " + f"normalize={self.normalize}, " + f"run_time={self.run_time}" + f")" + ) @property @@ -807,15 +833,18 @@ def graphs(self): def run_time(self): return self._run_time + @property + def test_run_time(self): + return self._test_run_time @property def dis_matrix(self): - return self._dis_matrix + return self._dm_train @dis_matrix.setter def dis_matrix(self, value): - self._dis_matrix = value + self._dm_train = value @property From 0e56d1ef9221a4c9df124f63fcde7a9449c6ad33 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 6 Dec 2023 17:18:01 +0100 Subject: [PATCH 09/12] [CI] Temporarily remove the test for gedlib. --- .github/workflows/github-actions-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-ubuntu.yml b/.github/workflows/github-actions-ubuntu.yml index 61b5c0b9ef..077d86835c 100644 --- a/.github/workflows/github-actions-ubuntu.yml +++ b/.github/workflows/github-actions-ubuntu.yml @@ -91,7 +91,7 @@ jobs: # python setup.py bdist_wheel python setup.py install # pytest -v --cov-config=.coveragerc --cov-report term --cov=gklearn gklearn/tests/ged/ - pytest -v --cov-config=.coveragerc --cov-report term --cov=gklearn gklearn/tests/ --ignore=gklearn/tests/test_median_preimage_generator.py --ignore=gklearn/tests/test_graphkernels.py + pytest -v --cov-config=.coveragerc --cov-report term --cov=gklearn gklearn/tests/ --ignore=gklearn/tests/test_median_preimage_generator.py --ignore=gklearn/tests/test_graphkernels.py --ignore=gklearn/tests/ged/test_gedlib.py - name: Run code coverage run: | From c68304ec63f16d18bfe1868c731025ce463dd0a5 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 6 Dec 2023 17:28:46 +0100 Subject: [PATCH 10/12] [CI] Fix GitHub Actions Badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd81ee9e0f..4dcd0fc562 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # graphkit-learn -![GitHub Actions](https://github.com/jajupmochi/graphkit-learn/actions/workflows/github-actions.yml/badge.svg) +![GitHub Actions](https://github.com/jajupmochi/graphkit-learn/actions/workflows/github-actions-ubuntu.yml/badge.svg) [![Build status](https://ci.appveyor.com/api/projects/status/bdxsolk0t1uji9rd?svg=true)](https://ci.appveyor.com/project/jajupmochi/graphkit-learn) [![codecov](https://codecov.io/gh/jajupmochi/graphkit-learn/branch/master/graph/badge.svg)](https://codecov.io/gh/jajupmochi/graphkit-learn) [![Documentation Status](https://readthedocs.org/projects/graphkit-learn/badge/?version=master)](https://graphkit-learn.readthedocs.io/en/master/?badge=master) From 4a419aae88ef99193e83ffd25aba341be4d3c14f Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 6 Dec 2023 17:35:36 +0100 Subject: [PATCH 11/12] =?UTF-8?q?[CI]=20Temporarily=20remove=20the=20Publi?= =?UTF-8?q?sh=20distribution=20=F0=9F=93=A6=20to=20Test=20PyPI.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/github-actions-ubuntu.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/github-actions-ubuntu.yml b/.github/workflows/github-actions-ubuntu.yml index 077d86835c..b52ff0246b 100644 --- a/.github/workflows/github-actions-ubuntu.yml +++ b/.github/workflows/github-actions-ubuntu.yml @@ -97,12 +97,12 @@ jobs: run: | codecov - - name: Publish distribution 📦 to Test PyPI - if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository-url: https://test.pypi.org/legacy/ +# - name: Publish distribution 📦 to Test PyPI +# if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# password: ${{ secrets.TEST_PYPI_API_TOKEN }} +# repository-url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' From 13fbce29dabdc3faad51ef84e9ddb3000c5215f5 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Wed, 6 Dec 2023 17:51:48 +0100 Subject: [PATCH 12/12] =?UTF-8?q?[CI]=20Temporarily=20remove=20the=20publi?= =?UTF-8?q?sh=20distribution=20=F0=9F=93=A6=20to=20PyPI.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/github-actions-ubuntu.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/github-actions-ubuntu.yml b/.github/workflows/github-actions-ubuntu.yml index b52ff0246b..2327eb8dac 100644 --- a/.github/workflows/github-actions-ubuntu.yml +++ b/.github/workflows/github-actions-ubuntu.yml @@ -104,9 +104,9 @@ jobs: # password: ${{ secrets.TEST_PYPI_API_TOKEN }} # repository-url: https://test.pypi.org/legacy/ - - name: Publish distribution 📦 to PyPI - if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file +# - name: Publish distribution 📦 to PyPI +# if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# user: __token__ +# password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file