diff --git a/graphblas_algorithms/algorithms/core.py b/graphblas_algorithms/algorithms/core.py index 9212242..85ab592 100644 --- a/graphblas_algorithms/algorithms/core.py +++ b/graphblas_algorithms/algorithms/core.py @@ -34,4 +34,4 @@ def k_truss(G: Graph, k) -> Graph: # Convert back to networkx graph with correct node ids keys = G.list_to_keys(indices) key_to_id = dict(zip(keys, range(len(indices)))) - return Graph.from_graphblas(Ktruss, key_to_id=key_to_id) + return Graph(Ktruss, key_to_id=key_to_id) diff --git a/graphblas_algorithms/algorithms/tests/test_cluster.py b/graphblas_algorithms/algorithms/tests/test_cluster.py index 82e9efc..50a3196 100644 --- a/graphblas_algorithms/algorithms/tests/test_cluster.py +++ b/graphblas_algorithms/algorithms/tests/test_cluster.py @@ -9,8 +9,8 @@ def test_triangles_full(): G = gb.Matrix(bool, 5, 5) G[:, :] = True G2 = gb.select.offdiag(G).new() - G = Graph.from_graphblas(G) - G2 = Graph.from_graphblas(G2) + G = Graph(G) + G2 = Graph(G2) result = cluster.triangles(G) expected = gb.Vector(int, 5) expected[:] = 6 diff --git a/graphblas_algorithms/classes/_utils.py b/graphblas_algorithms/classes/_utils.py index c8f4eba..237dcf3 100644 --- a/graphblas_algorithms/classes/_utils.py +++ b/graphblas_algorithms/classes/_utils.py @@ -2,7 +2,6 @@ import numpy as np from graphblas import Matrix, Vector, binary from graphblas.core.matrix import TransposedMatrix -from graphblas.core.utils import ensure_type ################ # Classmethods # @@ -19,21 +18,6 @@ def from_networkx(cls, G, weight=None, dtype=None): return rv -def from_graphblas(cls, A, *, key_to_id=None): - # Does not copy if A is a Matrix! - A = ensure_type(A, Matrix) - if A.nrows != A.ncols: - raise ValueError(f"Adjacency matrix must be square; got {A.nrows} x {A.ncols}") - rv = cls() - # If there is no mapping, it may be nice to keep this as None - if key_to_id is None: - rv._key_to_id = {i: i for i in range(A.nrows)} - else: - rv._key_to_id = key_to_id - rv._A = A - return rv - - ############## # Properties # ############## @@ -144,23 +128,17 @@ def vector_to_nodemap(self, v, *, mask=None, fillvalue=None): elif fillvalue is not None and v.nvals < v.size: v(mask=~v.S) << fillvalue - rv = object.__new__(NodeMap) - rv.vector = v - rv._key_to_id = self._key_to_id + rv = NodeMap(v, key_to_id=self._key_to_id) rv._id_to_key = self._id_to_key return rv - # return NodeMap.from_graphblas(v, key_to_id=self._key_to_id) def vector_to_nodeset(self, v): from .nodeset import NodeSet - rv = object.__new__(NodeSet) - rv.vector = v - rv._key_to_id = self._key_to_id + rv = NodeSet(v, key_to_id=self._key_to_id) rv._id_to_key = self._id_to_key return rv - # return NodeSet.from_graphblas(v, key_to_id=self._key_to_id) def vector_to_set(self, v): diff --git a/graphblas_algorithms/classes/digraph.py b/graphblas_algorithms/classes/digraph.py index 7b6890d..167bd29 100644 --- a/graphblas_algorithms/classes/digraph.py +++ b/graphblas_algorithms/classes/digraph.py @@ -1,5 +1,6 @@ from collections import defaultdict +import graphblas as gb from graphblas import Matrix, Vector, binary, replace, select, unary import graphblas_algorithms as ga @@ -415,7 +416,7 @@ def to_directed_graph(G, weight=None, dtype=None): if isinstance(G, DiGraph): return G try: - return DiGraph.from_graphblas(G) + return DiGraph(G) except TypeError: pass @@ -435,7 +436,7 @@ def to_graph(G, weight=None, dtype=None): return G try: # Should we check if it can be undirected? - return DiGraph.from_graphblas(G) + return DiGraph(G) except TypeError: pass @@ -538,22 +539,28 @@ class DiGraph(Graph): } graph_attr_dict_factory = dict - def __init__(self, incoming_graph_data=None, **attr): + def __init__(self, incoming_graph_data=None, *, key_to_id=None, **attr): if incoming_graph_data is not None: - raise NotImplementedError("incoming_graph_data is not None") + # Does not copy if A is a Matrix! + A = gb.core.utils.ensure_type(incoming_graph_data, Matrix) + if A.nrows != A.ncols: + raise ValueError(f"Adjacency matrix must be square; got {A.nrows} x {A.ncols}") + else: + A = Matrix() self.graph_attr_dict_factory = self.graph_attr_dict_factory self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes self.graph.update(attr) # Graphblas-specific properties - self._A = Matrix() - self._key_to_id = {} + self._A = A + if key_to_id is None: + key_to_id = {i: i for i in range(A.nrows)} + self._key_to_id = key_to_id self._id_to_key = None self._cache = {} # Graphblas-specific methods from_networkx = classmethod(_utils.from_networkx) - from_graphblas = classmethod(_utils.from_graphblas) id_to_key = property(_utils.id_to_key) get_property = _utils.get_property get_properties = _utils.get_properties @@ -586,6 +593,10 @@ def name(self, s): self._A.name = s self.graph["name"] = s + @property + def matrix(self): + return self._A + def __iter__(self): return iter(self._key_to_id) diff --git a/graphblas_algorithms/classes/graph.py b/graphblas_algorithms/classes/graph.py index bc4ba24..a8f14f3 100644 --- a/graphblas_algorithms/classes/graph.py +++ b/graphblas_algorithms/classes/graph.py @@ -1,5 +1,6 @@ from collections import defaultdict +import graphblas as gb from graphblas import Matrix, Vector, select import graphblas_algorithms as ga @@ -153,7 +154,7 @@ def to_undirected_graph(G, weight=None, dtype=None): if isinstance(G, Graph): return G try: - return Graph.from_graphblas(G) + return Graph(G) except TypeError: pass @@ -243,22 +244,28 @@ class Graph: } graph_attr_dict_factory = dict - def __init__(self, incoming_graph_data=None, **attr): + def __init__(self, incoming_graph_data=None, *, key_to_id=None, **attr): if incoming_graph_data is not None: - raise NotImplementedError("incoming_graph_data is not None") + # Does not copy if A is a Matrix! + A = gb.core.utils.ensure_type(incoming_graph_data, Matrix) + if A.nrows != A.ncols: + raise ValueError(f"Adjacency matrix must be square; got {A.nrows} x {A.ncols}") + else: + A = Matrix() self.graph_attr_dict_factory = self.graph_attr_dict_factory self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes self.graph.update(attr) # Graphblas-specific properties - self._A = Matrix() - self._key_to_id = {} + self._A = A + if key_to_id is None: + key_to_id = {i: i for i in range(A.nrows)} + self._key_to_id = key_to_id self._id_to_key = None self._cache = {} # Graphblas-specific methods from_networkx = classmethod(_utils.from_networkx) - from_graphblas = classmethod(_utils.from_graphblas) id_to_key = property(_utils.id_to_key) get_property = _utils.get_property get_properties = _utils.get_properties @@ -292,6 +299,10 @@ def name(self, s): self._A.name = s self.graph["name"] = s + @property + def matrix(self): + return self._A + def __iter__(self): return iter(self._key_to_id) diff --git a/graphblas_algorithms/classes/nodemap.py b/graphblas_algorithms/classes/nodemap.py index bedba8a..7878772 100644 --- a/graphblas_algorithms/classes/nodemap.py +++ b/graphblas_algorithms/classes/nodemap.py @@ -6,20 +6,13 @@ class NodeMap(MutableMapping): - def __init__(self): - raise NotImplementedError() - # .vector, ._key_to_id, ._id_to_key - - @classmethod - def from_graphblas(cls, v, *, key_to_id=None): - rv = object.__new__(cls) - rv.vector = v + def __init__(self, v, *, key_to_id=None): + self.vector = v if key_to_id is None: - rv._key_to_id = {i: i for i in range(v.size)} + self._key_to_id = {i: i for i in range(v.size)} else: - rv._key_to_id = key_to_id - rv._id_to_key = None - return rv + self._key_to_id = key_to_id + self._id_to_key = None id_to_key = property(_utils.id_to_key) # get_property = _utils.get_property @@ -104,15 +97,8 @@ def setdefault(self, key, default=None): class VectorMap(MutableMapping): - def __init__(self): - raise NotImplementedError() - # .vector - - @classmethod - def from_graphblas(cls, v): - rv = object.__new__(cls) - rv.vector = v - return rv + def __init__(self, v): + self.vector = v # Requirements for MutableMapping def __delitem__(self, key): @@ -176,21 +162,14 @@ def setdefault(self, key, default=None): class VectorNodeMap(MutableMapping): - def __init__(self): - raise NotImplementedError() - # .matrix, ._key_to_id, ._id_to_key, ._rows - - @classmethod - def from_graphblas(cls, A, *, key_to_id=None): - rv = object.__new__(cls) - rv.matrix = A + def __init__(self, A, *, key_to_id=None): + self.matrix = A if key_to_id is None: - rv._key_to_id = {i: i for i in range(A.size)} + self._key_to_id = {i: i for i in range(A.size)} else: - rv._key_to_id = key_to_id - rv._id_to_key = None - rv._rows = None - return rv + self._key_to_id = key_to_id + self._id_to_key = None + self._rows = None def _get_rows(self): if self._rows is None: @@ -226,7 +205,7 @@ def __getitem__(self, key): idx = self._key_to_id[key] if self._get_rows().get(idx) is None: raise KeyError(key) - return VectorMap.from_graphblas(self.matrix[idx, :].new()) + return VectorMap(self.matrix[idx, :].new()) def __iter__(self): # Slow if we iterate over one; fast if we iterate over all @@ -273,7 +252,7 @@ def get(self, key, default=None): idx = self._key_to_id[key] if self._get_rows().get(idx) is None: return default - return VectorMap.from_graphblas(self.matrix[idx, :].new()) + return VectorMap(self.matrix[idx, :].new()) # items # keys @@ -285,7 +264,7 @@ def popitem(self): idx = next(rows.ss.iterkeys()) except StopIteration: raise KeyError from None - value = VectorMap.from_graphblas(self.matrix[idx, :].new()) + value = VectorMap(self.matrix[idx, :].new()) del self.matrix[idx, :] del rows[idx] return self.id_to_key[idx], value diff --git a/graphblas_algorithms/classes/nodeset.py b/graphblas_algorithms/classes/nodeset.py index 81f4839..1713a7d 100644 --- a/graphblas_algorithms/classes/nodeset.py +++ b/graphblas_algorithms/classes/nodeset.py @@ -6,20 +6,13 @@ class NodeSet(MutableSet): - def __init__(self): - raise NotImplementedError() - # .vector, ._key_to_id, ._id_to_key - - @classmethod - def from_graphblas(cls, v, *, key_to_id=None): - rv = object.__new__(cls) - rv.vector = v + def __init__(self, v, *, key_to_id=None): + self.vector = v if key_to_id is None: - rv._key_to_id = {i: i for i in range(v.size)} + self._key_to_id = {i: i for i in range(v.size)} else: - rv._key_to_id = key_to_id - rv._id_to_key = None - return rv + self._key_to_id = key_to_id + self._id_to_key = None id_to_key = property(_utils.id_to_key) # get_property = _utils.get_property diff --git a/graphblas_algorithms/nxapi/cluster.py b/graphblas_algorithms/nxapi/cluster.py index 6644189..7ac5618 100644 --- a/graphblas_algorithms/nxapi/cluster.py +++ b/graphblas_algorithms/nxapi/cluster.py @@ -139,6 +139,6 @@ def generalized_degree(G, nodes=None): return G.vector_to_nodemap(result) mask = G.list_to_mask(nodes) result = algorithms.generalized_degree(G, mask=mask) - rv = VectorNodeMap.from_graphblas(result, key_to_id=G._key_to_id) + rv = VectorNodeMap(result, key_to_id=G._key_to_id) rv._id_to_key = G._id_to_key return rv