Add the NetworkX query module (#5)
* Add __deepcopy__ for Properties * Fix Vertices __contains__ * Check the incoming type in Vertex and Edge __eq__ * Add NetworkX support code * Add NetworkX algorithms * PageRank is now included in the nxalg module * Rewrite graph analyzer to use NetworkX support code * Don't make the error case be the "default" case
This commit is contained in:
parent
f7f861ca71
commit
9d6b578237
@ -64,6 +64,13 @@ class Properties:
|
||||
self._len = None
|
||||
self._vertex_or_edge = vertex_or_edge
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# This is the same as the shallow copy, as the underlying C API should
|
||||
# not support deepcopy. Besides, it doesn't make much sense to actually
|
||||
# copy _mgp.Edge and _mgp.Vertex types as they are actually references
|
||||
# to graph elements and not proper values.
|
||||
return Properties(self._vertex_or_edge)
|
||||
|
||||
def get(self, property_name: str, default=None) -> object:
|
||||
'''Get the value of a property with the given name or return default.
|
||||
|
||||
@ -236,6 +243,8 @@ class Edge:
|
||||
'''Raise InvalidContextError.'''
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
if not isinstance(other, Edge):
|
||||
return NotImplemented
|
||||
return self._edge == other._edge
|
||||
|
||||
def __hash__(self) -> int:
|
||||
@ -325,6 +334,8 @@ class Vertex:
|
||||
'''Raise InvalidContextError'''
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
if not isinstance(other, Vertex):
|
||||
return NotImplemented
|
||||
return self._vertex == other._vertex
|
||||
|
||||
def __hash__(self) -> int:
|
||||
@ -372,8 +383,8 @@ class Path:
|
||||
pass
|
||||
# This is the same as the shallow copy, as the underlying C API should
|
||||
# not support deepcopy. Besides, it doesn't make much sense to actually
|
||||
# copy Edge and Vertex types as they are actually references to graph
|
||||
# elements and not proper values.
|
||||
# copy _mgp.Edge and _mgp.Vertex types as they are actually references
|
||||
# to graph elements and not proper values.
|
||||
path = self.__copy__()
|
||||
memo[id(self._path)] = path._path
|
||||
return path
|
||||
@ -471,7 +482,7 @@ class Vertices:
|
||||
|
||||
def __contains__(self, vertex):
|
||||
try:
|
||||
_ = self.graph.get_vertex_by_id(vertex.id)
|
||||
_ = self._graph.get_vertex_by_id(vertex.id)
|
||||
return True
|
||||
except IndexError:
|
||||
return False
|
||||
|
@ -32,7 +32,8 @@ install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_exampl
|
||||
|
||||
# Install the Python modules
|
||||
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES pagerank.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES mgp_networkx.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES nxalg.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES wcc.py DESTINATION lib/memgraph/query_modules)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
|
@ -4,6 +4,7 @@ from collections import OrderedDict
|
||||
from itertools import chain, repeat
|
||||
from inspect import cleandoc
|
||||
from typing import List, Tuple, Optional
|
||||
from mgp_networkx import MemgraphMultiDiGraph
|
||||
import networkx as nx
|
||||
|
||||
|
||||
@ -47,7 +48,7 @@ def analyze(context: mgp.ProcCtx,
|
||||
Example call (with parameter):
|
||||
CALL graph_analyzer.analyze(['nodes', 'edges']) YIELD *;
|
||||
'''
|
||||
g = _convert_to_multidigraph(context)
|
||||
g = MemgraphMultiDiGraph(ctx=context)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
@ -81,7 +82,11 @@ def analyze_subgraph(context: mgp.ProcCtx,
|
||||
YIELD *
|
||||
RETURN name, value;
|
||||
'''
|
||||
g = _convert_to_subgraph_multidigraph(context, vertices, edges)
|
||||
vertices, edges = map(set, [vertices, edges])
|
||||
g = nx.subgraph_view(
|
||||
MemgraphMultiDiGraph(ctx=context),
|
||||
lambda n: n in vertices,
|
||||
lambda n1, n2, e: e in edges)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
@ -263,36 +268,3 @@ def _strongly_components(g: nx.MultiDiGraph):
|
||||
'''Returns number of strongly connected components.'''
|
||||
comps = nx.algorithms.components.number_strongly_connected_components(g)
|
||||
return 'Number of strongly connected components', comps
|
||||
|
||||
|
||||
def _convert_to_multidigraph(context: mgp.ProcCtx,
|
||||
) -> Optional[nx.MultiDiGraph]:
|
||||
g = nx.MultiDiGraph()
|
||||
|
||||
for v in context.graph.vertices:
|
||||
context.check_must_abort()
|
||||
g.add_node(v.id)
|
||||
|
||||
for v in context.graph.vertices:
|
||||
context.check_must_abort()
|
||||
for e in v.out_edges:
|
||||
g.add_edge(e.from_vertex.id, e.to_vertex.id)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def _convert_to_subgraph_multidigraph(context: mgp.ProcCtx,
|
||||
vertices: mgp.List[mgp.Vertex],
|
||||
edges: mgp.List[mgp.Edge]
|
||||
) -> Optional[nx.MultiDiGraph]:
|
||||
g = nx.MultiDiGraph()
|
||||
|
||||
for v in vertices:
|
||||
context.check_must_abort()
|
||||
g.add_node(v.id)
|
||||
|
||||
for e in edges:
|
||||
context.check_must_abort()
|
||||
g.add_edge(e.from_vertex.id, e.to_vertex.id)
|
||||
|
||||
return g
|
||||
|
280
query_modules/mgp_networkx.py
Normal file
280
query_modules/mgp_networkx.py
Normal file
@ -0,0 +1,280 @@
|
||||
import mgp
|
||||
|
||||
import collections
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class MemgraphAdjlistOuterDict(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx', '_succ', '_multi')
|
||||
|
||||
def __init__(self, ctx, succ=True, multi=True):
|
||||
self._ctx = ctx
|
||||
self._succ = succ
|
||||
self._multi = multi
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return MemgraphAdjlistInnerDict(key, succ=self._succ,
|
||||
multi=self._multi)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ctx.graph.vertices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ctx.graph.vertices)
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._ctx.graph.vertices
|
||||
|
||||
|
||||
class MemgraphAdjlistInnerDict(collections.abc.Mapping):
|
||||
__slots__ = ('_node', '_succ', '_multi', '_neighbors')
|
||||
|
||||
def __init__(self, node, succ=True, multi=True):
|
||||
self._node = node
|
||||
self._succ = succ
|
||||
self._multi = multi
|
||||
self._neighbors = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
# NOTE: NetworkX 2.4, classes/coreviews.py:143. UnionAtlas expects a
|
||||
# KeyError when indexing with a vertex that is not a neighbor.
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
if not self._multi:
|
||||
return UnhashableProperties(self._get_edge(key).properties)
|
||||
return MemgraphEdgeKeyDict(self._node, key, self._succ)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._get_neighbors()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._get_neighbors())
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._get_neighbors()
|
||||
|
||||
def _get_neighbors(self):
|
||||
if not self._neighbors:
|
||||
if self._succ:
|
||||
self._neighbors = set(
|
||||
e.to_vertex for e in self._node.out_edges)
|
||||
else:
|
||||
self._neighbors = set(
|
||||
e.from_vertex for e in self._node.in_edges)
|
||||
return self._neighbors
|
||||
|
||||
def _get_edge(self, neighbor):
|
||||
if self._succ:
|
||||
edge = list(filter(lambda e: e.to_vertex == neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
edge = list(filter(lambda e: e.from_vertex == neighbor,
|
||||
self._node.in_edges))
|
||||
|
||||
assert len(edge) >= 1
|
||||
if len(edge) > 1:
|
||||
raise RuntimeError('Graph contains multiedges but '
|
||||
'is of non-multigraph type: {}'.format(edge))
|
||||
|
||||
return edge[0]
|
||||
|
||||
|
||||
class MemgraphEdgeKeyDict(collections.abc.Mapping):
|
||||
__slots__ = ('_node', '_neighbor', '_succ', '_edges')
|
||||
|
||||
def __init__(self, node, neighbor, succ=True):
|
||||
self._node = node
|
||||
self._neighbor = neighbor
|
||||
self._succ = succ
|
||||
self._edges = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return UnhashableProperties(key.properties)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._get_edges()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._get_edges())
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Edge):
|
||||
raise TypeError
|
||||
return key in self._get_edges()
|
||||
|
||||
def _get_edges(self):
|
||||
if not self._edges:
|
||||
if self._succ:
|
||||
self._edges = list(filter(
|
||||
lambda e: e.to_vertex == self._neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
self._edges = list(filter(
|
||||
lambda e: e.from_vertex == self._neighbor,
|
||||
self._node.in_edges))
|
||||
return self._edges
|
||||
|
||||
|
||||
class UnhashableProperties(collections.abc.Mapping):
|
||||
__slots__ = ('_properties')
|
||||
|
||||
def __init__(self, properties):
|
||||
self._properties = properties
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._properties[key]
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._properties
|
||||
|
||||
def __len__(self):
|
||||
return len(self._properties)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._properties
|
||||
|
||||
# NOTE: Explicitly disable hashing. See the comment in MemgraphNodeDict.
|
||||
__hash__ = None
|
||||
|
||||
|
||||
class MemgraphNodeDict(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx',)
|
||||
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
# NOTE: NetworkX 2.4, classes/digraph.py:484. NetworkX expects the
|
||||
# tuples provided to add_nodes_from to be unhashable and cause a
|
||||
# TypeError when trying to index the node dictionary. This happens
|
||||
# because the data dictionary element of the tuple is unhashable. We do
|
||||
# the same thing by returning an unhashable data dictionary.
|
||||
return UnhashableProperties(key.properties)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ctx.graph.vertices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ctx.graph.vertices)
|
||||
|
||||
def __contains__(self, key):
|
||||
# NOTE: NetworkX 2.4, graph.py:425. Graph.__contains__ relies on
|
||||
# self._node's (i.e. the dictionary produced by node_dict_factory)
|
||||
# __contains__ to raise a TypeError when indexing with something weird,
|
||||
# e.g. with sets. This is the behavior of dict.
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._ctx.graph.vertices
|
||||
|
||||
|
||||
class MemgraphDiGraphBase:
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, multi=True,
|
||||
**kwargs):
|
||||
# NOTE: We assume that our graph will never be given any initial data
|
||||
# because we already pull our data from the Memgraph database. This
|
||||
# assert is triggered by certain NetworkX procedures because they
|
||||
# create a new instance of our class and try to populate it with their
|
||||
# own data.
|
||||
assert incoming_graph_data is None
|
||||
|
||||
# NOTE: We allow for ctx to be None in order to allow certain NetworkX
|
||||
# procedures (such as `subgraph`) to work. Such procedures directly
|
||||
# modify the graph's internal attributes and don't try to populate it
|
||||
# with initial data or modify it.
|
||||
|
||||
self.node_dict_factory = lambda: MemgraphNodeDict(ctx) \
|
||||
if ctx else self._error
|
||||
self.node_attr_dict_factory = self._error
|
||||
|
||||
self.adjlist_outer_dict_factory = \
|
||||
lambda: MemgraphAdjlistOuterDict(ctx, multi=multi) \
|
||||
if ctx else self._error
|
||||
self.adjlist_inner_dict_factory = self._error
|
||||
self.edge_key_dict_factory = self._error
|
||||
self.edge_attr_dict_factory = self._error
|
||||
|
||||
# NOTE: We forbid any mutating operations because our graph is
|
||||
# immutable and pulls its data from the Memgraph database.
|
||||
for f in ['add_node', 'add_nodes_from', 'remove_node',
|
||||
'remove_nodes_from', 'add_edge', 'add_edges_from',
|
||||
'add_weighted_edges_from', 'new_edge_key', 'remove_edge',
|
||||
'remove_edges_from', 'update', 'clear']:
|
||||
setattr(self, f, lambda *args, **kwargs: self._error())
|
||||
|
||||
super().__init__(None, **kwargs)
|
||||
|
||||
# NOTE: This is a necessary hack because NetworkX assumes that the
|
||||
# customizable factory functions will only ever return *empty*
|
||||
# dictionaries. In our case, the factory functions return our custom,
|
||||
# already populated, dictionaries. Because self._pred and self._end are
|
||||
# initialized by the same factory function, they end up storing the
|
||||
# same adjacency lists which is not good. We correct that here.
|
||||
self._pred = MemgraphAdjlistOuterDict(ctx, succ=False, multi=multi)
|
||||
|
||||
def _error(self):
|
||||
raise RuntimeError('Modification operations are not supported')
|
||||
|
||||
|
||||
class MemgraphMultiDiGraph(MemgraphDiGraphBase, nx.MultiDiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=True, **kwargs)
|
||||
|
||||
|
||||
def MemgraphMultiGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphMultiDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class MemgraphDiGraph(MemgraphDiGraphBase, nx.DiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=False, **kwargs)
|
||||
|
||||
|
||||
def MemgraphGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class PropertiesDictionary(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx', '_prop', '_len')
|
||||
|
||||
def __init__(self, ctx, prop):
|
||||
self._ctx = ctx
|
||||
self._prop = prop
|
||||
self._len = None
|
||||
|
||||
def __getitem__(self, vertex):
|
||||
if vertex not in self:
|
||||
raise KeyError
|
||||
try:
|
||||
return vertex.properties[self._prop]
|
||||
except KeyError:
|
||||
raise KeyError(("{} doesn\t have the required " +
|
||||
"property '{}'").format(vertex, self._prop))
|
||||
|
||||
def __iter__(self):
|
||||
for v in self._ctx.graph.vertices:
|
||||
if self._prop in v.properties:
|
||||
yield v
|
||||
|
||||
def __len__(self):
|
||||
if not self._len:
|
||||
self._len = sum(1 for _ in self)
|
||||
return self._len
|
||||
|
||||
def __contains__(self, vertex):
|
||||
if not isinstance(vertex, mgp.Vertex):
|
||||
raise TypeError
|
||||
return self._prop in vertex.properties
|
811
query_modules/nxalg.py
Normal file
811
query_modules/nxalg.py
Normal file
@ -0,0 +1,811 @@
|
||||
import mgp
|
||||
from mgp_networkx import (MemgraphMultiDiGraph, MemgraphDiGraph,
|
||||
MemgraphMultiGraph, MemgraphGraph,
|
||||
PropertiesDictionary)
|
||||
import networkx as nx
|
||||
|
||||
|
||||
# networkx.algorithms.approximation.connectivity.node_connectivity
|
||||
@mgp.read_proc
|
||||
def node_connectivity(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.Vertex] = None,
|
||||
target: mgp.Nullable[mgp.Vertex] = None
|
||||
) -> mgp.Record(connectivity=int):
|
||||
return mgp.Record(connectivity=nx.node_connectivity(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, target))
|
||||
|
||||
|
||||
# networkx.algorithms.assortativity.degree_assortativity_coefficient
|
||||
@mgp.read_proc
|
||||
def degree_assortativity_coefficient(
|
||||
ctx: mgp.ProcCtx,
|
||||
x: str = 'out',
|
||||
y: str = 'in',
|
||||
weight: mgp.Nullable[str] = None,
|
||||
nodes: mgp.Nullable[mgp.List[mgp.Vertex]] = None
|
||||
) -> mgp.Record(assortativity=float):
|
||||
return mgp.Record(assortativity=nx.degree_assortativity_coefficient(
|
||||
MemgraphMultiDiGraph(ctx=ctx), x, y, weight, nodes))
|
||||
|
||||
|
||||
# networkx.algorithms.asteroidal.is_at_free
|
||||
@mgp.read_proc
|
||||
def is_at_free(ctx: mgp.ProcCtx) -> mgp.Record(is_at_free=bool):
|
||||
return mgp.Record(is_at_free=nx.is_at_free(MemgraphGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.bipartite.basic.is_bipartite
|
||||
@mgp.read_proc
|
||||
def is_bipartite(ctx: mgp.ProcCtx) -> mgp.Record(is_bipartite=bool):
|
||||
return mgp.Record(is_bipartite=nx.is_bipartite(
|
||||
MemgraphMultiDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.boundary.node_boundary
|
||||
@mgp.read_proc
|
||||
def node_boundary(ctx: mgp.ProcCtx,
|
||||
nbunch1: mgp.List[mgp.Vertex],
|
||||
nbunch2: mgp.Nullable[mgp.List[mgp.Vertex]] = None
|
||||
) -> mgp.Record(boundary=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(boundary=list(nx.node_boundary(
|
||||
MemgraphMultiDiGraph(ctx=ctx), nbunch1, nbunch2)))
|
||||
|
||||
|
||||
# networkx.algorithms.bridges.bridges
|
||||
@mgp.read_proc
|
||||
def bridges(ctx: mgp.ProcCtx,
|
||||
root: mgp.Nullable[mgp.Vertex] = None
|
||||
) -> mgp.Record(bridges=mgp.List[mgp.Edge]):
|
||||
g = MemgraphMultiGraph(ctx=ctx)
|
||||
return mgp.Record(
|
||||
bridges=[next(iter(g[u][v]))
|
||||
for u, v in nx.bridges(MemgraphGraph(ctx=ctx),
|
||||
root=root)])
|
||||
|
||||
|
||||
# networkx.algorithms.centrality.betweenness_centrality
|
||||
@mgp.read_proc
|
||||
def betweenness_centrality(ctx: mgp.ProcCtx,
|
||||
k: mgp.Nullable[int] = None,
|
||||
normalized: bool = True,
|
||||
weight: mgp.Nullable[str] = None,
|
||||
endpoints: bool = False,
|
||||
seed: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
betweenness=mgp.Number):
|
||||
return [mgp.Record(node=n, betweenness=b)
|
||||
for n, b in nx.betweenness_centrality(
|
||||
MemgraphDiGraph(ctx=ctx), k=k, normalized=normalized,
|
||||
weight=weight, endpoints=endpoints, seed=seed).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.chains.chain_decomposition
|
||||
@mgp.read_proc
|
||||
def chain_decomposition(ctx: mgp.ProcCtx,
|
||||
root: mgp.Nullable[mgp.Vertex] = None
|
||||
) -> mgp.Record(chains=mgp.List[mgp.List[mgp.Edge]]):
|
||||
g = MemgraphMultiGraph(ctx=ctx)
|
||||
return mgp.Record(
|
||||
chains=[[next(iter(g[u][v])) for u, v in d]
|
||||
for d in nx.chain_decomposition(MemgraphGraph(ctx=ctx),
|
||||
root=root)])
|
||||
|
||||
|
||||
# networkx.algorithms.chordal.is_chordal
|
||||
@mgp.read_proc
|
||||
def is_chordal(ctx: mgp.ProcCtx) -> mgp.Record(is_chordal=bool):
|
||||
return mgp.Record(is_chordal=nx.is_chordal(MemgraphGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.clique.find_cliques
|
||||
@mgp.read_proc
|
||||
def find_cliques(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(cliques=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(cliques=list(nx.find_cliques(
|
||||
MemgraphMultiGraph(ctx=ctx))))
|
||||
|
||||
|
||||
# networkx.algorithms.cluster.clustering
|
||||
@mgp.read_proc
|
||||
def clustering(ctx: mgp.ProcCtx,
|
||||
nodes: mgp.Nullable[mgp.List[mgp.Vertex]] = None,
|
||||
weight: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(node=mgp.Vertex, clustering=mgp.Number):
|
||||
return [mgp.Record(node=n, clustering=c)
|
||||
for n, c in nx.clustering(
|
||||
MemgraphDiGraph(ctx=ctx), nodes=nodes,
|
||||
weight=weight).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.coloring.greedy_color
|
||||
@mgp.read_proc
|
||||
def greedy_color(ctx: mgp.ProcCtx,
|
||||
strategy: str = 'largest_first',
|
||||
interchange: bool = False
|
||||
) -> mgp.Record(node=mgp.Vertex, color=int):
|
||||
return [mgp.Record(node=n, color=c) for n, c in nx.greedy_color(
|
||||
MemgraphMultiDiGraph(ctx=ctx), strategy, interchange).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.communicability_alg.communicability
|
||||
@mgp.read_proc
|
||||
def communicability(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(node1=mgp.Vertex, node2=mgp.Vertex,
|
||||
communicability=mgp.Number):
|
||||
return [mgp.Record(node1=n1, node2=n2, communicability=v)
|
||||
for n1, d in nx.communicability(MemgraphGraph(ctx=ctx)).items()
|
||||
for n2, v in d.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.community.kclique.k_clique_communities
|
||||
@mgp.read_proc
|
||||
def k_clique_communities(
|
||||
ctx: mgp.ProcCtx,
|
||||
k: int,
|
||||
cliques: mgp.Nullable[mgp.List[mgp.List[mgp.Vertex]]] = None
|
||||
) -> mgp.Record(communities=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(communities=[
|
||||
list(s) for s in nx.community.k_clique_communities(
|
||||
MemgraphMultiGraph(ctx=ctx), k, cliques)])
|
||||
|
||||
|
||||
# networkx.algorithms.approximation.kcomponents.k_components
|
||||
@mgp.read_proc
|
||||
def k_components(ctx: mgp.ProcCtx,
|
||||
density: mgp.Number = 0.95
|
||||
) -> mgp.Record(k=int,
|
||||
components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
kcomps = nx.k_components(MemgraphMultiGraph(ctx=ctx), density)
|
||||
|
||||
return [mgp.Record(k=k, components=[list(s) for s in comps])
|
||||
for k, comps in kcomps.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.components.biconnected_components
|
||||
@mgp.read_proc
|
||||
def biconnected_components(
|
||||
ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
comps = nx.biconnected_components(MemgraphMultiGraph(ctx=ctx))
|
||||
return mgp.Record(components=[list(s) for s in comps])
|
||||
|
||||
|
||||
# networkx.algorithms.components.strongly_connected_components
|
||||
@mgp.read_proc
|
||||
def strongly_connected_components(
|
||||
ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
comps = nx.strongly_connected_components(MemgraphMultiDiGraph(ctx=ctx))
|
||||
return mgp.Record(components=[list(s) for s in comps])
|
||||
|
||||
|
||||
# networkx.algorithms.connectivity.edge_kcomponents.k_edge_components
|
||||
#
|
||||
# NOTE: NetworkX 2.4, algorithms/connectivity/edge_kcompnents.py:367. We create
|
||||
# a *copy* of the graph because the algorithm copies the graph using
|
||||
# __class__() and tries to modify it.
|
||||
@mgp.read_proc
|
||||
def k_edge_components(
|
||||
ctx: mgp.ProcCtx,
|
||||
k: int
|
||||
) -> mgp.Record(components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(components=[list(s) for s in nx.k_edge_components(
|
||||
nx.DiGraph(MemgraphDiGraph(ctx=ctx)), k)])
|
||||
|
||||
|
||||
# networkx.algorithms.core.core_number
|
||||
@mgp.read_proc
|
||||
def core_number(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(node=mgp.Vertex, core=mgp.Number):
|
||||
return [mgp.Record(node=n, core=c)
|
||||
for n, c in nx.core_number(MemgraphDiGraph(ctx=ctx)).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.covering.is_edge_cover
|
||||
@mgp.read_proc
|
||||
def is_edge_cover(ctx: mgp.ProcCtx, cover: mgp.List[mgp.Edge]
|
||||
) -> mgp.Record(is_edge_cover=bool):
|
||||
cover = set([(e.from_vertex, e.to_vertex) for e in cover])
|
||||
return mgp.Record(is_edge_cover=nx.is_edge_cover(
|
||||
MemgraphMultiGraph(ctx=ctx), cover))
|
||||
|
||||
|
||||
# networkx.algorithms.cycles.find_cycle
|
||||
@mgp.read_proc
|
||||
def find_cycle(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.List[mgp.Vertex]] = None,
|
||||
orientation: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(cycle=mgp.Nullable[mgp.List[mgp.Edge]]):
|
||||
try:
|
||||
return mgp.Record(cycle=[e for _, _, e in nx.find_cycle(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, orientation)])
|
||||
except nx.NetworkXNoCycle:
|
||||
return mgp.Record(cycle=None)
|
||||
|
||||
|
||||
# networkx.algorithms.cycles.simple_cycles
|
||||
#
|
||||
# NOTE: NetworkX 2.4, algorithms/cycles.py:183. We create a *copy* of the graph
|
||||
# because the algorithm copies the graph using type() and tries to pass initial
|
||||
# data.
|
||||
@mgp.read_proc
|
||||
def simple_cycles(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(cycles=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(cycles=list(nx.simple_cycles(
|
||||
nx.MultiDiGraph(MemgraphMultiDiGraph(ctx=ctx)).copy())))
|
||||
|
||||
|
||||
# networkx.algorithms.cuts.node_expansion
|
||||
@mgp.read_proc
|
||||
def node_expansion(ctx: mgp.ProcCtx, s: mgp.List[mgp.Vertex]
|
||||
) -> mgp.Record(node_expansion=mgp.Number):
|
||||
return mgp.Record(node_expansion=nx.node_expansion(
|
||||
MemgraphMultiDiGraph(ctx=ctx), set(s)))
|
||||
|
||||
|
||||
# networkx.algorithms.dag.topological_sort
|
||||
@mgp.read_proc
|
||||
def topological_sort(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(nodes=mgp.Nullable[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(nodes=list(nx.topological_sort(
|
||||
MemgraphMultiDiGraph(ctx=ctx))))
|
||||
|
||||
|
||||
# networkx.algorithms.dag.ancestors
|
||||
@mgp.read_proc
|
||||
def ancestors(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex
|
||||
) -> mgp.Record(ancestors=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(ancestors=list(nx.ancestors(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source)))
|
||||
|
||||
|
||||
# networkx.algorithms.dag.descendants
|
||||
@mgp.read_proc
|
||||
def descendants(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex
|
||||
) -> mgp.Record(descendants=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(descendants=list(nx.descendants(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source)))
|
||||
|
||||
|
||||
# networkx.algorithms.distance_measures.center
|
||||
#
|
||||
# NOTE: Takes more parameters.
|
||||
@mgp.read_proc
|
||||
def center(ctx: mgp.ProcCtx) -> mgp.Record(center=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(center=list(nx.center(MemgraphMultiDiGraph(ctx=ctx))))
|
||||
|
||||
|
||||
# networkx.algorithms.distance_measures.diameter
|
||||
#
|
||||
# NOTE: Takes more parameters.
|
||||
@mgp.read_proc
|
||||
def diameter(ctx: mgp.ProcCtx) -> mgp.Record(diameter=int):
|
||||
return mgp.Record(diameter=nx.diameter(MemgraphMultiDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.distance_regular.is_distance_regular
|
||||
@mgp.read_proc
|
||||
def is_distance_regular(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(is_distance_regular=bool):
|
||||
return mgp.Record(is_distance_regular=nx.is_distance_regular(
|
||||
MemgraphMultiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.strongly_regular.is_strongly_regular
|
||||
@mgp.read_proc
|
||||
def is_strongly_regular(ctx: mgp.ProcCtx
|
||||
) -> mgp.Record(is_strongly_regular=bool):
|
||||
return mgp.Record(is_strongly_regular=nx.is_strongly_regular(
|
||||
MemgraphMultiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.dominance.dominance_frontiers
|
||||
@mgp.read_proc
|
||||
def dominance_frontiers(ctx: mgp.ProcCtx, start: mgp.Vertex,
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
frontier=mgp.List[mgp.Vertex]):
|
||||
return [mgp.Record(node=n, frontier=list(f))
|
||||
for n, f in nx.dominance_frontiers(
|
||||
MemgraphMultiDiGraph(ctx=ctx), start).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.dominance.immediate_dominatorss
|
||||
@mgp.read_proc
|
||||
def immediate_dominators(ctx: mgp.ProcCtx, start: mgp.Vertex,
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
dominator=mgp.Vertex):
|
||||
return [mgp.Record(node=n, dominator=d)
|
||||
for n, d in nx.immediate_dominators(
|
||||
MemgraphMultiDiGraph(ctx=ctx), start).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.dominating.dominating_set
|
||||
@mgp.read_proc
|
||||
def dominating_set(ctx: mgp.ProcCtx, start: mgp.Vertex,
|
||||
) -> mgp.Record(dominating_set=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(dominating_set=list(nx.dominating_set(
|
||||
MemgraphMultiDiGraph(ctx=ctx), start)))
|
||||
|
||||
|
||||
# networkx.algorithms.efficiency_measures.local_efficiency
|
||||
@mgp.read_proc
|
||||
def local_efficiency(ctx: mgp.ProcCtx) -> mgp.Record(local_efficiency=float):
|
||||
return mgp.Record(local_efficiency=nx.local_efficiency(
|
||||
MemgraphMultiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.efficiency_measures.global_efficiency
|
||||
@mgp.read_proc
|
||||
def global_efficiency(ctx: mgp.ProcCtx) -> mgp.Record(global_efficiency=float):
|
||||
return mgp.Record(global_efficiency=nx.global_efficiency(
|
||||
MemgraphMultiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.euler.is_eulerian
|
||||
@mgp.read_proc
|
||||
def is_eulerian(ctx: mgp.ProcCtx) -> mgp.Record(is_eulerian=bool):
|
||||
return mgp.Record(is_eulerian=nx.is_eulerian(
|
||||
MemgraphMultiDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.euler.is_semieulerian
|
||||
@mgp.read_proc
|
||||
def is_semieulerian(ctx: mgp.ProcCtx) -> mgp.Record(is_semieulerian=bool):
|
||||
return mgp.Record(is_semieulerian=nx.is_semieulerian(
|
||||
MemgraphMultiDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.euler.has_eulerian_path
|
||||
@mgp.read_proc
|
||||
def has_eulerian_path(ctx: mgp.ProcCtx) -> mgp.Record(has_eulerian_path=bool):
|
||||
return mgp.Record(has_eulerian_path=nx.has_eulerian_path(
|
||||
MemgraphMultiDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.hierarchy.flow_hierarchy
|
||||
@mgp.read_proc
|
||||
def flow_hierarchy(ctx: mgp.ProcCtx,
|
||||
weight: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(flow_hierarchy=float):
|
||||
return mgp.Record(flow_hierarchy=nx.flow_hierarchy(
|
||||
MemgraphMultiDiGraph(ctx=ctx), weight=weight))
|
||||
|
||||
|
||||
# networkx.algorithms.isolate.isolates
|
||||
@mgp.read_proc
|
||||
def isolates(ctx: mgp.ProcCtx) -> mgp.Record(isolates=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(isolates=list(nx.isolates(
|
||||
MemgraphMultiDiGraph(ctx=ctx))))
|
||||
|
||||
|
||||
# networkx.algorithms.isolate.is_isolate
|
||||
@mgp.read_proc
|
||||
def is_isolate(ctx: mgp.ProcCtx, n: mgp.Vertex
|
||||
) -> mgp.Record(is_isolate=bool):
|
||||
return mgp.Record(is_isolate=nx.is_isolate(
|
||||
MemgraphMultiDiGraph(ctx=ctx), n))
|
||||
|
||||
|
||||
# networkx.algorithms.isomorphism.is_isomorphic
|
||||
@mgp.read_proc
|
||||
def is_isomorphic(ctx: mgp.ProcCtx,
|
||||
nodes1: mgp.List[mgp.Vertex],
|
||||
edges1: mgp.List[mgp.Edge],
|
||||
nodes2: mgp.List[mgp.Vertex],
|
||||
edges2: mgp.List[mgp.Edge]
|
||||
) -> mgp.Record(is_isomorphic=bool):
|
||||
nodes1, edges1, nodes2, edges2 = map(set, [nodes1, edges1, nodes2, edges2])
|
||||
g = MemgraphMultiDiGraph(ctx=ctx)
|
||||
g1 = nx.subgraph_view(
|
||||
g, lambda n: n in nodes1, lambda n1, n2, e: e in edges1)
|
||||
g2 = nx.subgraph_view(
|
||||
g, lambda n: n in nodes2, lambda n1, n2, e: e in edges2)
|
||||
return mgp.Record(is_isomorphic=nx.is_isomorphic(g1, g2))
|
||||
|
||||
|
||||
# networkx.algorithms.link_analysis.pagerank_alg.pagerank
|
||||
@mgp.read_proc
|
||||
def pagerank(ctx: mgp.ProcCtx,
|
||||
alpha: mgp.Number = 0.85,
|
||||
personalization: mgp.Nullable[str] = None,
|
||||
max_iter: int = 100,
|
||||
tol: mgp.Number = 1e-06,
|
||||
nstart: mgp.Nullable[str] = None,
|
||||
weight: mgp.Nullable[str] = 'weight',
|
||||
dangling: mgp.Nullable[str] = None,
|
||||
) -> mgp.Record(node=mgp.Vertex, rank=float):
|
||||
def to_properties_dictionary(prop):
|
||||
return None if prop is None else PropertiesDictionary(ctx, prop)
|
||||
|
||||
pg = nx.pagerank(MemgraphDiGraph(ctx=ctx), alpha=alpha,
|
||||
personalization=to_properties_dictionary(personalization),
|
||||
max_iter=max_iter, tol=tol,
|
||||
nstart=to_properties_dictionary(nstart), weight=weight,
|
||||
dangling=to_properties_dictionary(dangling))
|
||||
|
||||
return [mgp.Record(node=k, rank=v) for k, v in pg.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.link_prediction.jaccard_coefficient
|
||||
@mgp.read_proc
|
||||
def jaccard_coefficient(
|
||||
ctx: mgp.ProcCtx,
|
||||
ebunch: mgp.Nullable[mgp.List[mgp.List[mgp.Vertex]]] = None
|
||||
) -> mgp.Record(u=mgp.Vertex, v=mgp.Vertex,
|
||||
coef=float):
|
||||
return [mgp.Record(u=u, v=v, coef=c) for u, v, c
|
||||
in nx.jaccard_coefficient(MemgraphGraph(ctx=ctx), ebunch)]
|
||||
|
||||
|
||||
# networkx.algorithms.lowest_common_ancestors.lowest_common_ancestor
|
||||
@mgp.read_proc
|
||||
def lowest_common_ancestor(ctx: mgp.ProcCtx, node1: mgp.Vertex,
|
||||
node2: mgp.Vertex
|
||||
) -> mgp.Record(ancestor=mgp.Nullable[mgp.Vertex]):
|
||||
return mgp.Record(ancestor=nx.lowest_common_ancestor(
|
||||
MemgraphDiGraph(ctx=ctx), node1, node2))
|
||||
|
||||
|
||||
# networkx.algorithms.matching.maximal_matching
|
||||
@mgp.read_proc
|
||||
def maximal_matching(ctx: mgp.ProcCtx) -> mgp.Record(edges=mgp.List[mgp.Edge]):
|
||||
g = MemgraphMultiDiGraph(ctx=ctx)
|
||||
return mgp.Record(edges=list(
|
||||
next(iter(g[u][v])) for u, v in nx.maximal_matching(g)))
|
||||
|
||||
|
||||
# networkx.algorithms.planarity.check_planarity
|
||||
#
|
||||
# NOTE: Returns a graph.
|
||||
@mgp.read_proc
|
||||
def check_planarity(ctx: mgp.ProcCtx) -> mgp.Record(is_planar=bool):
|
||||
return mgp.Record(is_planar=nx.check_planarity(
|
||||
MemgraphMultiDiGraph(ctx=ctx))[0])
|
||||
|
||||
|
||||
# networkx.algorithms.non_randomness.non_randomness
|
||||
@mgp.read_proc
|
||||
def non_randomness(ctx: mgp.ProcCtx,
|
||||
k: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(non_randomness=float,
|
||||
relative_non_randomness=float):
|
||||
nn, rnn = nx.non_randomness(
|
||||
MemgraphGraph(ctx=ctx), k=k)
|
||||
return mgp.Record(non_randomness=nn, relative_non_randomness=rnn)
|
||||
|
||||
|
||||
# networkx.algorithms.reciprocity.reciprocity
|
||||
@mgp.read_proc
|
||||
def reciprocity(ctx: mgp.ProcCtx,
|
||||
nodes: mgp.Nullable[mgp.List[mgp.Vertex]] = None
|
||||
) -> mgp.Record(node=mgp.Nullable[mgp.Vertex],
|
||||
reciprocity=mgp.Nullable[float]):
|
||||
rp = nx.reciprocity(MemgraphMultiDiGraph(ctx=ctx), nodes=nodes)
|
||||
if nodes is None:
|
||||
return mgp.Record(node=None, reciprocity=rp)
|
||||
else:
|
||||
return [mgp.Record(node=n, reciprocity=r) for n, r in rp.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.generic.shortest_path
|
||||
@mgp.read_proc
|
||||
def shortest_path(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.Vertex] = None,
|
||||
target: mgp.Nullable[mgp.Vertex] = None,
|
||||
weight: mgp.Nullable[str] = None,
|
||||
method: str = 'dijkstra'
|
||||
) -> mgp.Record(source=mgp.Vertex, target=mgp.Vertex,
|
||||
path=mgp.List[mgp.Vertex]):
|
||||
sp = nx.shortest_path(MemgraphMultiDiGraph(ctx=ctx), source=source,
|
||||
target=target, weight=weight, method=method)
|
||||
|
||||
if source and target:
|
||||
sp = {source: {target: sp}}
|
||||
elif source and not target:
|
||||
sp = {source: sp}
|
||||
elif not source and target:
|
||||
sp = {source: {target: p} for source, p in sp.items()}
|
||||
|
||||
return [mgp.Record(source=s, target=t, path=p)
|
||||
for s, d in sp.items()
|
||||
for t, p in d.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.generic.shortest_path_length
|
||||
@mgp.read_proc
|
||||
def shortest_path_length(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.Vertex] = None,
|
||||
target: mgp.Nullable[mgp.Vertex] = None,
|
||||
weight: mgp.Nullable[str] = None,
|
||||
method: str = 'dijkstra'
|
||||
) -> mgp.Record(source=mgp.Vertex, target=mgp.Vertex,
|
||||
length=mgp.Number):
|
||||
sp = nx.shortest_path_length(MemgraphMultiDiGraph(ctx=ctx), source=source,
|
||||
target=target, weight=weight, method=method)
|
||||
|
||||
if source and target:
|
||||
sp = {source: {target: sp}}
|
||||
elif source and not target:
|
||||
sp = {source: sp}
|
||||
elif not source and target:
|
||||
sp = {source: {target: l} for source, l in sp.items()}
|
||||
else:
|
||||
sp = dict(sp)
|
||||
|
||||
return [mgp.Record(source=s, target=t, length=l)
|
||||
for s, d in sp.items()
|
||||
for t, l in d.items()]
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.generic.all_shortest_paths
|
||||
@mgp.read_proc
|
||||
def all_shortest_paths(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
target: mgp.Vertex,
|
||||
weight: mgp.Nullable[str] = None,
|
||||
method: str = 'dijkstra'
|
||||
) -> mgp.Record(paths=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(paths=list(nx.all_shortest_paths(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source=source, target=target,
|
||||
weight=weight, method=method)))
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.generic.has_path
|
||||
@mgp.read_proc
|
||||
def has_path(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
target: mgp.Vertex) -> mgp.Record(has_path=bool):
|
||||
return mgp.Record(has_path=nx.has_path(MemgraphMultiDiGraph(ctx=ctx),
|
||||
source, target))
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.weighted.multi_source_dijkstra_path
|
||||
@mgp.read_proc
|
||||
def multi_source_dijkstra_path(ctx: mgp.ProcCtx,
|
||||
sources: mgp.List[mgp.Vertex],
|
||||
cutoff: mgp.Nullable[int] = None,
|
||||
weight: str = 'weight'
|
||||
) -> mgp.Record(target=mgp.Vertex,
|
||||
path=mgp.List[mgp.Vertex]):
|
||||
return [mgp.Record(target=t, path=p)
|
||||
for t, p in nx.multi_source_dijkstra_path(
|
||||
MemgraphMultiDiGraph(ctx=ctx), sources, cutoff=cutoff,
|
||||
weight=weight).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.shortest_paths.weighted.multi_source_dijkstra_path_length
|
||||
@mgp.read_proc
|
||||
def multi_source_dijkstra_path_length(ctx: mgp.ProcCtx,
|
||||
sources: mgp.List[mgp.Vertex],
|
||||
cutoff: mgp.Nullable[int] = None,
|
||||
weight: str = 'weight'
|
||||
) -> mgp.Record(target=mgp.Vertex,
|
||||
length=mgp.Number):
|
||||
return [mgp.Record(target=t, length=l)
|
||||
for t, l in nx.multi_source_dijkstra_path_length(
|
||||
MemgraphMultiDiGraph(ctx=ctx), sources, cutoff=cutoff,
|
||||
weight=weight).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.simple_paths.is_simple_path
|
||||
@mgp.read_proc
|
||||
def is_simple_path(ctx: mgp.ProcCtx,
|
||||
nodes: mgp.List[mgp.Vertex]
|
||||
) -> mgp.Record(is_simple_path=bool):
|
||||
return mgp.Record(is_simple_path=nx.is_simple_path(
|
||||
MemgraphMultiDiGraph(ctx=ctx), nodes))
|
||||
|
||||
|
||||
# networkx.algorithms.simple_paths.all_simple_paths
|
||||
@mgp.read_proc
|
||||
def all_simple_paths(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
target: mgp.Vertex,
|
||||
cutoff: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(paths=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
return mgp.Record(paths=list(nx.all_simple_paths(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, target, cutoff=cutoff)))
|
||||
|
||||
|
||||
# networkx.algorithms.tournament.is_tournament
|
||||
@mgp.read_proc
|
||||
def is_tournament(ctx: mgp.ProcCtx) -> mgp.Record(is_tournament=bool):
|
||||
return mgp.Record(is_tournament=nx.tournament.is_tournament(
|
||||
MemgraphDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.breadth_first_search.bfs_edges
|
||||
@mgp.read_proc
|
||||
def bfs_edges(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
reverse: bool = False,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(edges=mgp.List[mgp.Edge]):
|
||||
return mgp.Record(edges=list(nx.bfs_edges(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, reverse=reverse,
|
||||
depth_limit=depth_limit)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.breadth_first_search.bfs_tree
|
||||
@mgp.read_proc
|
||||
def bfs_tree(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
reverse: bool = False,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(tree=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(tree=list(nx.bfs_tree(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, reverse=reverse,
|
||||
depth_limit=depth_limit)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.breadth_first_search.bfs_predecessors
|
||||
@mgp.read_proc
|
||||
def bfs_predecessors(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
predecessor=mgp.Vertex):
|
||||
return [mgp.Record(node=n, predecessor=p)
|
||||
for n, p in nx.bfs_predecessors(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source,
|
||||
depth_limit=depth_limit)]
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.breadth_first_search.bfs_successors
|
||||
@mgp.read_proc
|
||||
def bfs_successors(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
successors=mgp.List[mgp.Vertex]):
|
||||
return [mgp.Record(node=n, successors=s)
|
||||
for n, s in nx.bfs_successors(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source,
|
||||
depth_limit=depth_limit)]
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.depth_first_search.dfs_tree
|
||||
@mgp.read_proc
|
||||
def dfs_tree(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(tree=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(tree=list(nx.dfs_tree(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, depth_limit=depth_limit)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.depth_first_search.dfs_predecessors
|
||||
@mgp.read_proc
|
||||
def dfs_predecessors(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
predecessor=mgp.Vertex):
|
||||
return [mgp.Record(node=n, predecessor=p)
|
||||
for n, p in nx.dfs_predecessors(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source,
|
||||
depth_limit=depth_limit).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.depth_first_search.dfs_successors
|
||||
@mgp.read_proc
|
||||
def dfs_successors(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(node=mgp.Vertex,
|
||||
successors=mgp.List[mgp.Vertex]):
|
||||
return [mgp.Record(node=n, successors=s)
|
||||
for n, s in nx.dfs_successors(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source,
|
||||
depth_limit=depth_limit).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.depth_first_search.dfs_preorder_nodes
|
||||
@mgp.read_proc
|
||||
def dfs_preorder_nodes(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(nodes=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(nodes=list(nx.dfs_preorder_nodes(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, depth_limit=depth_limit)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.depth_first_search.dfs_postorder_nodes
|
||||
@mgp.read_proc
|
||||
def dfs_postorder_nodes(ctx: mgp.ProcCtx,
|
||||
source: mgp.Vertex,
|
||||
depth_limit: mgp.Nullable[int] = None
|
||||
) -> mgp.Record(nodes=mgp.List[mgp.Vertex]):
|
||||
return mgp.Record(nodes=list(nx.dfs_postorder_nodes(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source, depth_limit=depth_limit)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.edgebfs.edge_bfs
|
||||
@mgp.read_proc
|
||||
def edge_bfs(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.Vertex] = None,
|
||||
orientation: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(edges=mgp.List[mgp.Edge]):
|
||||
return mgp.Record(edges=list(e for _, _, e in nx.edge_bfs(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source=source,
|
||||
orientation=orientation)))
|
||||
|
||||
|
||||
# networkx.algorithms.traversal.edgedfs.edge_dfs
|
||||
@mgp.read_proc
|
||||
def edge_dfs(ctx: mgp.ProcCtx,
|
||||
source: mgp.Nullable[mgp.Vertex] = None,
|
||||
orientation: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(edges=mgp.List[mgp.Edge]):
|
||||
return mgp.Record(edges=list(e for _, _, e in nx.edge_dfs(
|
||||
MemgraphMultiDiGraph(ctx=ctx), source=source,
|
||||
orientation=orientation)))
|
||||
|
||||
|
||||
# networkx.algorithms.tree.recognition.is_tree
|
||||
@mgp.read_proc
|
||||
def is_tree(ctx: mgp.ProcCtx) -> mgp.Record(is_tree=bool):
|
||||
return mgp.Record(is_tree=nx.is_tree(MemgraphDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.tree.recognition.is_forest
|
||||
@mgp.read_proc
|
||||
def is_forest(ctx: mgp.ProcCtx) -> mgp.Record(is_forest=bool):
|
||||
return mgp.Record(is_forest=nx.is_forest(MemgraphDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.tree.recognition.is_arborescence
|
||||
@mgp.read_proc
|
||||
def is_arborescence(ctx: mgp.ProcCtx) -> mgp.Record(is_arborescence=bool):
|
||||
return mgp.Record(is_arborescence=nx.is_arborescence(
|
||||
MemgraphDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.tree.recognition.is_branching
|
||||
@mgp.read_proc
|
||||
def is_branching(ctx: mgp.ProcCtx) -> mgp.Record(is_branching=bool):
|
||||
return mgp.Record(is_branching=nx.is_branching(MemgraphDiGraph(ctx=ctx)))
|
||||
|
||||
|
||||
# networkx.algorithms.tree.mst.minimum_spanning_tree
|
||||
@mgp.read_proc
|
||||
def minimum_spanning_tree(ctx: mgp.ProcCtx,
|
||||
weight: str = 'weight',
|
||||
algorithm: str = 'kruskal',
|
||||
ignore_nan: bool = False
|
||||
) -> mgp.Record(nodes=mgp.List[mgp.Vertex],
|
||||
edges=mgp.List[mgp.Edge]):
|
||||
gres = nx.minimum_spanning_tree(MemgraphMultiGraph(ctx=ctx),
|
||||
weight, algorithm, ignore_nan)
|
||||
return mgp.Record(nodes=list(gres.nodes()),
|
||||
edges=[e for _, _, e in gres.edges(keys=True)])
|
||||
|
||||
|
||||
# networkx.algorithms.triads.triadic_census
|
||||
@mgp.read_proc
|
||||
def triadic_census(ctx: mgp.ProcCtx) -> mgp.Record(triad=str, count=int):
|
||||
return [mgp.Record(triad=t, count=c)
|
||||
for t, c in nx.triadic_census(MemgraphDiGraph(ctx=ctx)).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.voronoi.voronoi_cells
|
||||
@mgp.read_proc
|
||||
def voronoi_cells(ctx: mgp.ProcCtx,
|
||||
center_nodes: mgp.List[mgp.Vertex],
|
||||
weight: str = 'weight'
|
||||
) -> mgp.Record(center=mgp.Vertex,
|
||||
cell=mgp.List[mgp.Vertex]):
|
||||
return [mgp.Record(center=c1, cell=list(c2))
|
||||
for c1, c2 in nx.voronoi_cells(
|
||||
MemgraphMultiDiGraph(ctx=ctx), center_nodes,
|
||||
weight=weight).items()]
|
||||
|
||||
|
||||
# networkx.algorithms.wiener.wiener_index
|
||||
@mgp.read_proc
|
||||
def wiener_index(ctx: mgp.ProcCtx, weight: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(wiener_index=mgp.Number):
|
||||
return mgp.Record(wiener_index=nx.wiener_index(
|
||||
MemgraphMultiDiGraph(ctx=ctx), weight=weight))
|
@ -1,134 +0,0 @@
|
||||
import mgp
|
||||
import networkx as nx
|
||||
from itertools import chain
|
||||
|
||||
|
||||
class VertexDictionary:
|
||||
def __init__(self, graph, prop):
|
||||
self.graph = graph
|
||||
self.prop = prop
|
||||
self.len = None
|
||||
|
||||
def get(self, vertex, default=None):
|
||||
return vertex.properties.get(self.prop, default=default)
|
||||
|
||||
def items(self):
|
||||
for v in self.graph.vertices:
|
||||
if self.prop in v.properties:
|
||||
yield v, v.properties[self.prop]
|
||||
|
||||
def keys(self):
|
||||
for k, v in self.items():
|
||||
yield k
|
||||
|
||||
def values(self):
|
||||
for k, v in self.items():
|
||||
yield v
|
||||
|
||||
def __len__(self):
|
||||
if not self.len:
|
||||
self.len = sum(1 for _ in self.items())
|
||||
return self.len
|
||||
|
||||
def __iter__(self):
|
||||
for k, v in self.items():
|
||||
yield k
|
||||
|
||||
def __getitem__(self, vertex):
|
||||
try:
|
||||
return vertex.properties[self.prop]
|
||||
except KeyError:
|
||||
raise KeyError(("Vertex {} doesn\t have the required " +
|
||||
"property '{}'").format(vertex.id, self.prop))
|
||||
|
||||
def __contains__(self, vertex):
|
||||
return vertex in self.graph.vertices and self.prop in vertex.properties
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def pagerank(ctx: mgp.ProcCtx,
|
||||
alpha: mgp.Number = 0.85,
|
||||
personalization: mgp.Nullable[str] = None,
|
||||
max_iter: int = 100,
|
||||
tol: mgp.Number = 1e-06,
|
||||
nstart: mgp.Nullable[str] = None,
|
||||
weight: mgp.Nullable[str] = 'weight',
|
||||
dangling: mgp.Nullable[str] = None
|
||||
) -> mgp.Record(node=mgp.Vertex, rank=float):
|
||||
'''Run the PageRank algorithm on the whole graph.
|
||||
|
||||
The available parameters are:
|
||||
|
||||
- `alpha` -- Damping parameter.
|
||||
|
||||
- `personalization` -- The "personalization vector". A string specifying
|
||||
the property that will be looked up for every node to give the
|
||||
personalization value. If a node doesn't have the specified property, its
|
||||
personalization value will be zero. By default, a uniform distribution is
|
||||
used.
|
||||
|
||||
- `max_iter` -- Maximum number of iterations in the power method eigenvalue
|
||||
solver.
|
||||
|
||||
- `tol` -- Error tolerance used to check for convergence in the power
|
||||
eigenvalue method solver.
|
||||
|
||||
- `nstart` -- A string specifying the property that will be looked up for
|
||||
every node to give the starting value for the iteration.
|
||||
|
||||
- `weight` -- A string specifying the property that will be looked up for
|
||||
every edge to give the weight. If None or if the property doesn't exist,
|
||||
weights are set to 1.
|
||||
|
||||
- `dangling` -- The outedges to be assigned to any "dangling" nodes, i.e.,
|
||||
nodes without any outedges. A string specifying the property that will be
|
||||
looked up for every node to give the weight of the outedge that points to
|
||||
that node. By default, dangling nodes are given outedges according to the
|
||||
personalization vector. This must be selected to result in an irreducible
|
||||
transition matrix. It may be common to have the dangling dictionary be
|
||||
the same as the personalization dictionary.
|
||||
|
||||
Return a single record for every node. Each record has two fields, `node`
|
||||
and `rank`, which together give the calculated rank for the node.
|
||||
|
||||
As an example, the following openCypher query calculates the ranks of all
|
||||
the nodes in the graph using the PageRank algorithm. The personalization
|
||||
value for every node is taken from its property named 'personalization',
|
||||
while the `alpha` and `max_iter` parameters are set to 0.85 and 150
|
||||
respectively:
|
||||
|
||||
CALL pagerank.pagerank(0.85, 'personalization', 150) YIELD *;
|
||||
|
||||
'''
|
||||
def to_vertex_dictionary(prop):
|
||||
return None if prop is None else VertexDictionary(ctx.graph, prop)
|
||||
|
||||
def make_and_check_vertex(v):
|
||||
for prop in (personalization, nstart, dangling):
|
||||
if prop is None:
|
||||
continue
|
||||
if not isinstance(v.properties.get(prop, default=1), (int, float)):
|
||||
raise TypeError(("Property '{}' of vertex '{}' needs to " +
|
||||
"be a number").format(prop, v.id))
|
||||
return v
|
||||
|
||||
def make_and_check_edge(e):
|
||||
if (weight is not None and
|
||||
not isinstance(e.properties.get(weight, default=1), (int, float))):
|
||||
raise TypeError("Property '{}' of edge '{}' needs to be a number"
|
||||
.format(weight, e.id))
|
||||
return e.from_vertex, e.to_vertex, e.properties
|
||||
|
||||
g = nx.DiGraph()
|
||||
g.add_nodes_from(make_and_check_vertex(v) for v in ctx.graph.vertices)
|
||||
g.add_edges_from(make_and_check_edge(e)
|
||||
for v in ctx.graph.vertices
|
||||
for e in chain(v.in_edges, v.out_edges))
|
||||
|
||||
pg = nx.pagerank(g, alpha=alpha,
|
||||
personalization=to_vertex_dictionary(personalization),
|
||||
max_iter=max_iter, tol=tol,
|
||||
nstart=to_vertex_dictionary(nstart), weight=weight,
|
||||
dangling=to_vertex_dictionary(dangling))
|
||||
|
||||
return [mgp.Record(node=k, rank=v) for k, v in pg.items()]
|
Loading…
Reference in New Issue
Block a user