Skip to content

Commit

Permalink
Merge pull request #271 from DerwenAI/issue-261-graph-algebra
Browse files Browse the repository at this point in the history
Improvement in networks module
  • Loading branch information
ceteri authored Sep 16, 2022
2 parents 1c78a6a + 4464ebb commit 48130af
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 136 deletions.
402 changes: 388 additions & 14 deletions examples/graph_algebra/gla_ex0_0.ipynb

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions kglab/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# see license https://github.com/DerwenAI/kglab#license-and-copyright

"""
see license https://github.com/DerwenAI/kglab#license-and-copyright
"""
from .kglab import KnowledgeGraph

from .graph import NodeRef, PropertyStore
Expand Down
15 changes: 12 additions & 3 deletions kglab/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import networkx as nx
from networkx import DiGraph

class AlgebraMixin:
from kglab.util import Mixin

class AlgebraMixin(Mixin):
"""
Provides methods to work with graph algebra using `SubgraphMatrix` data.
Expand All @@ -19,6 +21,12 @@ class AlgebraMixin:
nx_graph: typing.Optional[DiGraph] = None

def to_undirected(self):
"""
Return the undirected adjancency matrix of the directed graph.
returns:
`numpy.array`: the array representation in `numpy` standard
"""
return nx.to_numpy_array(self.nx_graph.to_undirected())

def to_adjacency(self):
Expand Down Expand Up @@ -46,7 +54,8 @@ def to_incidence(self):
def to_laplacian(self):
"""
Return Laplacian matrix for the KG. Graph is turned into undirected.
[docs](https://networkx.org/documentation/stable/reference/generated/networkx.linalg.laplacianmatrix.laplacian_matrix.html)
[docs](https://networkx.org/documentation/stable/reference/generated/networkx.linalg.laplacianmatrix.laplacian_matrix.html).
Lapliacian is also known as vertices degrees matrix.
returns:
`numpy.array`: the array representation in `numpy` standard
Expand All @@ -59,7 +68,7 @@ def to_scipy_sparse(self):
Return graph in CSR format (optimized for matrix-matrix operations).
returns:
SciPy sparse matrix: Graph adjacency matrix.
SciPy sparse matrix: Graph adjacency matrix.
"""
self.check_attributes()
return nx.to_scipy_sparse_array(self.nx_graph)
38 changes: 19 additions & 19 deletions kglab/kglab.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class KnowledgeGraph(QueryingMixin, SerdeMixin, ShaclOwlRdfSkosMixin):
Core feature areas include:
* namespace management: ontology, controlled vocabularies
* namespace management: ontology, controlled vocabularies
* graph construction
* serialization-deserilization (see `serde` module)
* SPARQL querying (see `query.mixin` module)
Expand All @@ -65,12 +65,12 @@ def __init__ (
self,
*,
name: str = "generic",
base_uri: str = None,
base_uri: typing.Optional[str] = None,
language: str = "en",
store: str = None,
store: typing.Optional[str] = None,
use_gpus: bool = True,
import_graph: typing.Optional[GraphLike] = None,
namespaces: dict = None,
namespaces: typing.Optional[dict] = None,
) -> None:
"""
Constructor for a `KnowledgeGraph` object.
Expand Down Expand Up @@ -112,17 +112,17 @@ def __init__ (
if import_graph is not None:
self._g = import_graph
else:
self._g = self.build_blank_graph()
self._g = self.build_blank_graph() # pylint: disable=E1101

# initialize the namespaces
self._ns: dict = {}

for prefix, iri in self._DEFAULT_NAMESPACES.items():
self.add_ns(prefix, iri)
self.add_ns(prefix, iri) # pylint: disable=E1101

if namespaces:
for prefix, iri in namespaces.items():
self.add_ns(prefix, iri)
self.add_ns(prefix, iri) # pylint: disable=E1101

# backwards compatibility for class refactoring
self.sparql = kglab.query.sparql.SparqlQueryable(self)
Expand Down Expand Up @@ -203,7 +203,7 @@ def add_ns (
if replace or prefix not in self._ns:
self._ns[prefix] = rdflib.Namespace(iri)

self._g.namespace_manager.bind(
self._g.namespace_manager.bind( # type: ignore
prefix,
self._ns[prefix],
override=override,
Expand Down Expand Up @@ -241,7 +241,7 @@ def get_ns_dict (
for prefix, ns in self._ns.items()
}

nm = self._g.namespace_manager
nm = self._g.namespace_manager # type: ignore

for prefix, uri in nm.namespaces():
ns_dict[prefix] = str(uri)
Expand Down Expand Up @@ -314,7 +314,7 @@ def encode_date (
[`rdflib.Literal`](https://rdflib.readthedocs.io/en/stable/rdf_terms.html#literals) formatted as an XML Schema 2 `dateTime` value
"""
date_tz = dup.parse(dt, tzinfos=tzinfos)
return rdflib.Literal(date_tz, datatype=self.get_ns("xsd").dateTime)
return rdflib.Literal(date_tz, datatype=self.get_ns("xsd").dateTime) # pylint: disable=E1101


def add (
Expand Down Expand Up @@ -342,7 +342,7 @@ def add (
must be a [`rdflib.term.Node`](https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html?highlight=Node#rdflib.term.Node) or [`rdflib.term.Terminal`](https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html?highlight=Node#rdflib.term.Literal); otherwise throws a `TypeError` exception
"""
try:
self._g.add((s, p, o,))
self._g.add((s, p, o,)) # type: ignore
except AssertionError as e:
traceback.print_exc()
ic(s)
Expand Down Expand Up @@ -376,7 +376,7 @@ def remove (
must be a [`rdflib.term.Node`](https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html?highlight=Node#rdflib.term.Node) or [`rdflib.term.Terminal`](https://rdflib.readthedocs.io/en/stable/apidocs/rdflib.html?highlight=Node#rdflib.term.Literal); otherwise throws a `TypeError` exception
"""
try:
self._g.remove((s, p, o,))
self._g.remove((s, p, o,)) # type: ignore
except AssertionError as e:
traceback.print_exc()
ic(s)
Expand All @@ -386,17 +386,17 @@ def remove (


def graph_factory(self, name, graph):
"""
"""
Utility function to generate graphs from mixins
name:
name of the graph
graph:
initial graph
"""
return KnowledgeGraph(
name=name,
namespaces=self.get_ns_dict(),
import_graph=graph,
)
"""
return KnowledgeGraph(
name=name,
namespaces=self.get_ns_dict(),
import_graph=graph,
)
60 changes: 48 additions & 12 deletions kglab/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,30 @@
see license https://github.com/DerwenAI/kglab#license-and-copyright
"""

import sknetwork as skn
import networkx as nx
from networkx.exception import NetworkXError
from scipy.spatial.distance import pdist, squareform

class NetAnalysisMixin:
from kglab.util import Mixin

class NetAnalysisMixin(Mixin):
"""
Provides methods for network analysis tools to work with `KnowledgeGraph`.
"""
def get_distances(self, adj_mtx):
"""
Compute distances according to an adjacency matrix.
adj_mtx:
numpy.array: square matrix of distances.
"""
self.check_attributes()
return skn.path.get_distances(adj_mtx)
return squareform(pdist(adj_mtx, metric='euclidean'))

def get_shortest_path(self, adj_matx, src, dst):
def get_shortest_path(self, src, dst):
"""
Return shortest path from sources to destinations according to an djacency matrix.
Return shortest path from sources to destinations.
adj_mtx:
numpy.array: adjacency matrix for the graph.
src:
int or iterable: indices of source nodes
dst:
Expand All @@ -34,10 +39,41 @@ def get_shortest_path(self, adj_matx, src, dst):
list of int: a path of indices
"""
self.check_attributes()
return skn.path.get_shortest_path(adj_matx, src, dst)
return nx.shortest_path(self.nx_graph, source=src, target=dst)

def describe(self):
"""
Return a summary for subgraph statistics.
NOTE: we may cache these methods calls if we create something like a `GraphFrame` object.
see kglab#273, same for adjacency and other matrices.
# number of nodes, number of edges
# density
# triangles
# reciprocity
return:
dict: a dictionary with stats
"""
def msg_if_raise(f, g, r):
"""Handle error messages by adding a message key in the results"""
try:
return f(g)
except NetworkXError as e:
r[f"{str(f.__name__)}_msg"] = str(e)
return None

results = {
"n_nodes": self._get_n_nodes(),
"n_edges": self._get_n_edges(),
}

return { **results, **{
"center": msg_if_raise(nx.center, self.nx_graph, results),
"diameter": msg_if_raise(nx.diameter, self.nx_graph, results),
"eccentricity": msg_if_raise(nx.eccentricity, self.nx_graph, results)
}}

def describe_more(self):
"""
Return a summary with more graph statistics.
"""
# density
# triangles
# reciprocity
raise NotImplementedError()
11 changes: 5 additions & 6 deletions kglab/query/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@
import typing

### third-parties libraries
from icecream import ic # type: ignore
import pandas as pd # type: ignore
import pyvis # type: ignore

import rdflib # type: ignore
import rdflib.plugin # type: ignore
import rdflib.plugins.parsers.notation3 as rdf_n3 # type: ignore

## kglab - core classes
from kglab.pkg_types import RDF_Node
from kglab.gpviz import GPViz
from kglab.util import get_gpu_count
from kglab.version import _check_version
from kglab.util import Mixin


## pre-constructor set-up
Expand All @@ -31,7 +30,7 @@
import cudf # type: ignore # pylint: disable=E0401


class QueryingMixin:
class QueryingMixin(Mixin):
"""
This class implements querying for `KnowledgeGraph`
Expand Down Expand Up @@ -65,7 +64,7 @@ def query (
if not bindings:
bindings = {}

for row in self._g.query(
for row in self._g.query( # type: ignore
sparql,
initBindings=bindings,
):
Expand Down Expand Up @@ -102,7 +101,7 @@ def query_as_df (
if not bindings:
bindings = {}

row_iter = self._g.query(sparql, initBindings=bindings)
row_iter = self._g.query(sparql, initBindings=bindings) # type: ignore

if simplify:
rows_list = [ self.n3fy_row(r.asdict(), pythonify=pythonify) for r in row_iter ]
Expand Down Expand Up @@ -136,7 +135,7 @@ def visualize_query (
returns:
PyVis network object, to be rendered
"""
return GPViz(sparql, self._ns).visualize_query(notebook=notebook)
return GPViz(sparql, self._ns).visualize_query(notebook=notebook) # type: ignore


def n3fy (
Expand Down
Loading

0 comments on commit 48130af

Please sign in to comment.