面向NetworkX用户的rustworkx#

这是面向现有NetworkX用户的rustworkx入门指南,介绍如何使用rustworkx、它与NetworkX的区别以及需要注意的关键差异。

一些关键区别#

rustworkx (正如其名称所示) 的灵感来源于 networkx,该项目旨在提供与 networkx 相似的功能性和实用性,但具有更快的性能。然而,由于 rust 和 python 之间边界的限制,该库的设计决策和其他方面并不完全相同。

需要记住的最大区别是networkx在使用方式上非常动态化。它允许你以一种关联性的方式处理图对象(类似于Python字典),并使用放置在图中的对象与图进行交互。例如:

import networkx as nx

graph = nx.MultiDiGraph()
graph.add_node('my_node_a')
graph.add_node('my_node_b')
graph.add_edge('my_node_a', 'my_node_b')

虽然 rustworkx 采用 Rust 语言编写给图的交互方式带来了更多限制。通过 rustworkx 你依然可以在图上附加任意 Python 对象,但每个节点和边都会被分配一个整数索引。必须使用该索引来访问图中的节点和边。 在 rustworkx 中,上述示例如下所示:

import rustworkx as rx

graph = rx.PyDiGraph()
node_a = graph.add_node('my_node_a')
node_b = graph.add_node('my_node_b')
graph.add_edge(node_a, node_b, None)

其中 node_a == 0node_b == 1。这些节点索引可以通过图对象使用,通过 python 映射协议(序列协议,因为在节点或边从图中移除后,索引不保证是连续的)来访问设置的有效载荷对象。继续 上述 rustworkx 示例:

assert 'my_node_a' == graph[node_a]
assert 'my_node_b' == graph[node_b]

现有networkx用户在迁移到rustworkx时通常需要适应的最大差异就是所有对象都采用整数索引。

类似地,当存在对节点或边数据进行操作的算法函数时,rustworkx 使用回调函数从节点或边的载荷中返回静态类型数据,以供各种算法使用。在 networkx 中,这通常通过节点或边的命名属性来实现(例如,默认情况下需要数值权重的函数通常使用名为 weight 的节点或边属性)。

例如,在networkx中:

import networkx as nx

graph = nx.MultiDiGraph()
graph.add_edges_from([(0, 1, {'weight': 1}), (0, 2, {'weight': 2}),
                      (1, 3, {'weight': 2}), (3, 0, {'weight': 3})])
dist_matrix = nx.floyd_warshall_numpy(graph, weight='weight')

而在rustworkx中你会使用:

import rustworkx as rx

graph = rx.PyDiGraph()
graph.extend_from weighted_edge_list(
    [(0, 1, {'weight': 1}), (0, 2, {'weight': 2}),
     (1, 3, {'weight': 2}), (3, 0, {'weight': 3})])
dist_matrix = rx.digraph_floyd_warshall_numpy(
    graph, weight_fn=lambda edge: edge[weight])
或更简洁地:
import rustworkx as rx

graph = rx.PyDiGraph()
graph.extend_from weighted_edge_list(
    [(0, 1, 1), (0, 2, 2),
     (1, 3, 2), (3, 0, 3)])
dist_matrix = rx.digraph_floyd_warshall_numpy(graph,
                                              weight_fn=lambda edge: edge)

另一大需要注意的区别是,rustworkx中的许多函数都具有显式类型。这意味着它们要么总是返回或接受PyDiGraph,要么是PyGraph,但不会同时接受两者。通常,以graph_*digraph_*为前缀的函数都是显式类型的。显式类型函数还会在文档字符串中表明其类型。这与networkx不同,在networkx中,几乎所有内容都是动态类型的,您可以将图形对象传递给任何函数,并且它会按预期工作(除非它不受支持,那么会抛出异常)。

图形数据与属性#

节点#

在网络分析库networkx中,节点可以是任何可哈希的Python对象。该对象随后用于访问或引用节点。此外,您可以在节点上设置可选属性。下文将对此进行更详细的说明。

在 rustworkx 任何 Python 对象(可哈希或不可哈希)都可以作为节点,但是节点只能通过整数节点索引(由任何添加节点的函数返回)进行访问。节点没有可选属性。如果需要,预计应将其添加到节点的数据负载中。

边连接 #

在networkx中,可通过节点之间的元组访问边。 边仅具有可选属性(如下所述),不包含其他对象负载。

在rustworkx中,任何python对象都可以作为边,并分配有一个唯一的整数索引,就像节点一样。然而,在大多数函数/方法中,边是通过节点索引元组(即边两端的节点索引)来引用的,而不是通过边的索引。

属性#

networkx 具备 节点、 以及 边属性的概念, 除此以外还包括用于节点负载的可哈希对象。Rustworkx 拥有与 NetworkX 类似的图属性,然而它并不像图对象本身上的字典那样处理,而是通过专门的 attrs 属性进行访问。该属性可以是任何 Python 对象,因此您可以使用它来拥有除字典以外的不同容器。例如,像这样:

import networkx as nx

graph = nx.Graph(day="Friday")
graph['day'] = "Monday"

可以用 rustworkx 来实现:

import rustworkx as rx

graph = rx.PyGraph(attrs=dict(day="Friday"))
graph.attrs['day'] = "Monday"

此外,你可以像这样使用 rustworkx 的自定义类:

class Day:

    def __init__(self, day):
        self.day = day

graph = rx.PyGraph(attrs=Day("Friday"))
graph.attrs = Day("Monday")

但对于节点和边,rustworkx 没有类似的概念。相反,节点和边的有效载荷可以是任何 python 对象(可哈希或不可哈希)。这使您能够构建类似于属性概念的结构,同时也可以使用针对您特定用例的替代结构。

例如,类似的内容:

import networkx as nx

graph = nx.Graph()
graph.add_node(1, time='5pm')
graph.add_nodes_from([3], time='2pm')
graph.nodes[1]['room'] = 714

可以通过使用dict作为节点权重来实现:

import rustworkx as rx

graph = rx.PyGraph()
node_a = graph.add_node({'time': '5pm'})
node_b = graph.add_nodes_from([{'time': '2pm'}])
graph[node_a]['room'] = 714

检查图元素#

networkx 在图对象上提供了4个属性:nodesedgesadjdegree,它们分别作为节点、边、邻居和节点度数的类集合视图。这些属性提供了对图的不同属性的实时视图,并且为这些属性提供额外的方法,以便以不同的方式查看图属性。

rustworkx不提供视图,而是提供不同的访问器方法返回类似数据的副本。有不同的函数/方法提供对该数据的不同视图。例如,edge_list()类似于networkx的edges视图,而weighted_edge_list()等同于networkx的edges(data=True)

此外,由于rustworkx中的所有内容均使用整数索引,为访问节点数据,PyDiGraphPyGraph 类实现了Python映射协议,因此您可以使用节点的索引来访问其数据。

API 等效项#

类构造函数#

networkx

rustworkx

注释

Graph()

PyGraph(multigraph=False)

仅在 rustworkx>=0.8.0 版本中添加多重图标志,先前版本总是允许多重边

DiGraph()

PyDiGraph(multigraph=False)

仅当rustworkx>=0.8.0版本启用了multigraph标志时可用,在先前的版本中始终允许多重边。

多图()

PyGraph()

多向图()

PyDiGraph()

这里还需要注意的是,rustworkx 不允许在调用构造函数时初始化图形。你需要调用对象的相应方法来添加节点或边,或者使用替代的构造函数方法:

networkx

rustworkx

说明

Graph([(0, 1), (1, 0)])
graph = PyGraph()
graph.extend_from_edge_list([(0, 1), (1, 0)])

rustworkx 的输入必须是二元组列表,而 networkx 可以是一个迭代器

Graph([(0, 1, {'weight': 2}), (1, 0, {'weight': 1})])
graph = PyGraph()
graph.extend_from_edge_list([(0, 1, 2), (1, 0, 1)])

rustworkx 输入必须是三元组列表, 而 networkx 可以是迭代器

Graph(np.array([[0, 1, 1], [1, 0, 1], [1, 0, 1]]))
PyGraph.from_adjacency_matrix(np.array([[0, 1, 1], [1, 0, 1], [1, 0, 1]], dtype=np.float64))

rustworkx from_adjacency_matrix() 只能接受 浮点数据类型的numpy数组,您可以使用 .astype(np.float64, copy=False) 来适配非浮点数组。

图修饰符#

网络X

rustworkx

注释

add_node()

add_node()

rustworkx 返回新创建节点的节点索引

add_nodes_from

add_nodes_from()

rustworkx要求输入为对象列表,并将返回新创建节点的节点索引列表

add_edge

add_edge()

rustworkx 需要使用 3 个参数,即 2 个节点索引和 payload (networkx 支持 2 个或 3 个参数)

add_edges_from

add_edges_from(), add_edges_from_no_data(), extend_from_edge_list(), extend_from_weighted_edge_list()

rustworkx需要一个3元组或2元组列表(取决于是否需要权重/数据)。rustworkx中extend_from*add_edges_from*方法的区别在于,extend_from*会在缺少任何节点索引时创建新节点,并赋予其权重/数据载荷为None

(注意 rustworkx 版本链接到 PyDiGraph 版本, 但也有等效的 PyGraph 方法可用)

矩阵转换器函数#

NetworkX 提供了多个函数用于在其图结构与其他库的矩阵之间进行相互转换。这包括 to_numpy_matrix(), to_numpy_array(), to_numpy_recarray(), to_scipy_sparse_matrix(), to_pandas_adjacency(), 以及 adjacency_matrix()(等效于 to_scipy_sparse_matrix() 并返回一个 scipy csr 稀疏形式的邻接矩阵)。

然而,在rustworkx中只有一个adjacency_matrix() 函数(及其按类型变体digraph_adjacency_matrix()graph_adjacency_matrix())将返回邻接矩阵的numpy数组 (不是像networkx函数那样的scipy csr稀疏矩阵)。此函数等同于networkx的to_numpy_array() 函数。

这种区别主要因为numpy公开了C接口,而rustworkx可以直接与其交互,而其他库和类型仅公开Python API。

可视化函数#

NetworkX 提供一个基于 matplotlib 绘图的本地绘图器(即 networkx_drawer* 函数),并提供了与 pygraphvizpydot 交互的函数,以便通过这些库使用 graphviz 来实现可视化(此外还有将图序列化为其他图可视化工具可用的格式的函数)。NetworkX 还提供若干 布局函数 用于生成可用于可视化图的不同布局。

rustworkx 具备两种可视化后端的绘图功能,matplotlib (mpl_draw()) 与 graphviz (graphviz_draw())。与 networkx 不同的是, graphviz_draw() 将处理调用 graphviz 并 生成图像文件。在布局函数方面,rustworkx 拥有类似多样的 Layout Functions,但需要注意 rustworkx 的函数 严格限定为二维。它们还返回一个 Pos2DMapping 自定义返回类型,该类型充当只读字典(这与 networkx 返回可修改的普通字典有所不同)。

Matplotlib 绘图器#

retworkx 函数 mpl_draw() 功能上基本等同于 networkx 函数 draw_networkx(实际上最初是从 networkx 绘图器分支而来)。然而,需要注意的是,networkx 和 rustworkx 的 matplotlib 绘图器之间存在一些关键区别。

networkx.draw_networkxrustworkx.mpl_draw 的区别:

networkx

rustworkx

注释

nodelist

node_list

边列表

edge_list

箭头大小

arrow_size

标签

标签

对于 networkx_drawer labels 是将节点映射到其标签的字典, 而 rustworkx 的 mpl_drawer labels 是一个回调函数, 它将接收节点的数据负载并期望返回节点的标签

networkx.draw_networkx_edge_labels()

edge_labels

NetworkX的networkx_drawer没有边标签的选项, 而添加标签只能通过一个单独的函数 draw_networkx_edge_labels()暴露,该函数需要使用来自原始可视化的pos字典。 rustworkx的edge_labels 关键字参数接受一个回调函数,该函数将被传递一个边的数据 负载,并期望返回标签。

从 networkx 图转换而来#

如果你使用的函数或外部库已经在生成networkx图,那么你可以使用rustworkx.networkx_converter()将该networkx Graph对象转换为等效的rustworkx PyGraphPyDiGraph对象。请注意,networkx不是rustworkx的依赖项,你需要自行安装networkx才能使用此函数。因此,没有提供等效的函数进行反向转换(因为这样做会添加对networkx的不必要依赖,即使它是可选的)。不过,编写这样的函数是简单的,例如:

import networkx as nx
import rustworkx as rx


def convert_rustworkx_to_networkx(graph):
    """Convert a rustworkx PyGraph or PyDiGraph to a networkx graph."""
    edge_list = [(
        graph[x[0]], graph[x[1]],
        {'weight': x[2]}) for x in graph.weighted_edge_list()]

    if isinstance(graph, rx.PyGraph):
        if graph.multigraph:
            return nx.MultiGraph(edge_list)
        else:
            return nx.Graph(edge_list)
    else:
        if graph.multigraph:
            return nx.MultiDiGraph(edge_list)
        else:
            return nx.DiGraph(edge_list)

功能差距#

networkx 是一个成熟的库,拥有广泛的用户群和丰富的功能集, 相比之下,rustworkx 则是一个年轻得多的库,缺少很多 networkx 提供的功能。如果你遇到 networkx 提供但 rustworkx 缺失的 功能,并希望使用,请在以下地址提交“功能请求”问题: https://github.com/Qiskit/rustworkx/issues/new/choose 一旦问题被提交,我们将优先考虑为 rustworkx 添加等效功能。