diff --git a/include/mgp.py b/include/mgp.py index 30fc5bec3..b2719d8fc 100644 --- a/include/mgp.py +++ b/include/mgp.py @@ -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 diff --git a/query_modules/CMakeLists.txt b/query_modules/CMakeLists.txt index f10203bfe..d7b6cef9a 100644 --- a/query_modules/CMakeLists.txt +++ b/query_modules/CMakeLists.txt @@ -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) diff --git a/query_modules/graph_analyzer.py b/query_modules/graph_analyzer.py index e1b050b9b..43ce83ad5 100644 --- a/query_modules/graph_analyzer.py +++ b/query_modules/graph_analyzer.py @@ -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 diff --git a/query_modules/mgp_networkx.py b/query_modules/mgp_networkx.py new file mode 100644 index 000000000..d53d5f60d --- /dev/null +++ b/query_modules/mgp_networkx.py @@ -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 diff --git a/query_modules/nxalg.py b/query_modules/nxalg.py new file mode 100644 index 000000000..42abbdd42 --- /dev/null +++ b/query_modules/nxalg.py @@ -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)) diff --git a/query_modules/pagerank.py b/query_modules/pagerank.py deleted file mode 100644 index 0cd24072c..000000000 --- a/query_modules/pagerank.py +++ /dev/null @@ -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()]