后端#

NetworkX 可以配置使用独立的第三方后端来提升性能和增加功能。后端是可选的,需要单独安装,并且可以通过用户代码或环境变量来启用。

注意

开发者用于创建自定义 NetworkX 后端的接口正在频繁更新和改进。参与每周的 NetworkX 分派会议 是保持更新并参与正在进行的讨论的绝佳方式。

后端用户文档#

NetworkX 采用了插件分派架构。一个有效的 NetworkX 后端在安装时(而非导入时)会指定 入口点(entry points),命名为 networkx.backends 和一个可选的 networkx.backend_info。这使得 NetworkX 可以将函数调用分派(重定向)到后端,从而将执行流程导向指定的后端实现。这种设计增强了灵活性和集成性,使 NetworkX 更具适应性和效率。

NetworkX 可以显式地(需要修改代码)或自动地(需要设置配置或环境变量)分派到后端。使用后端最佳方式取决于具体的后端、您的用例以及您是否希望自动在 NetworkX 图和后端图之间进行转换。图的自动转换始终是选择加入的。

要显式地分派到后端,请在可分派函数中使用 backend= 关键字参数。这将把输入的 NetworkX 图转换(默认会缓存)为后端图并调用后端实现。另一种显式使用后端的方式是直接创建一个后端图——例如,后端可能有自己的函数用于加载数据和创建图——然后将该图传递给可分派函数,该函数将直接调用后端实现而不进行转换。

使用自动分派需要设置配置选项。每个 NetworkX 配置项也可以通过环境变量设置,并在导入 networkx 时进行处理。支持以下配置变量:

  • nx.config.backend_priority (NETWORKX_BACKEND_PRIORITY 环境变量) 是一个后端列表,控制不返回图的可分派函数,例如 nx.pagerank。当使用 NetworkX 图作为输入调用这些函数时,分派器会遍历此 backend_priority 配置中列出的后端,并使用第一个实现了该函数的后端。输入的 NetworkX 图将被转换(默认会缓存)为后端图。使用此配置可以利用 NetworkX 图的全部灵活性和后端实现的性能,但可能的缺点是创建 NetworkX 图、将其转换为后端图以及缓存后端图都可能很耗时。

  • nx.config.backend_priority.algos (NETWORKX_BACKEND_PRIORITY_ALGOS 环境变量),可以用来代替 nx.config.backend_priority (NETWORKX_BACKEND_PRIORITY 环境变量),以强调该设置仅影响上述算法函数的调用分派。

  • nx.config.backend_priority.generators (NETWORKX_BACKEND_PRIORITY_GENERATORS 环境变量) 是一个后端列表,控制返回图的可分派函数,例如 nx.from_pandas_edgelist 和 nx.empty_graph。当调用这些函数之一时,将使用此 backend_priority 配置中列出的第一个实现了该函数的后端,并返回一个后端图。当这个后端图被传递给其他 NetworkX 可分派函数时,如果后端实现了该函数则会使用后端实现,否则默认会抛出异常,除非 nx.config.fallback_to_nx 设置为 True(默认为 False)。使用此配置避免了创建 NetworkX 图,从而避免了像使用 nx.config.backend_priority.algos 那样需要转换为后端图并缓存,但可能的缺点是后端图的行为可能与 NetworkX 图不同,并且后端可能没有实现您使用的所有算法,这可能会中断您的工作流程。

  • nx.config.fallback_to_nx (NETWORKX_FALLBACK_TO_NX 环境变量) 是一个布尔值(默认为 False),控制当后端图被传递给一个未由该后端实现的可分派函数时会发生什么。默认行为(False 时)是抛出异常。如果设置为 True,则后端图将被转换(默认会缓存)为 NetworkX 图,并使用默认的 NetworkX 实现运行。启用此配置可以在后端未实现工作流程中使用的所有算法时让工作流程继续完成,但一个可能的缺点是它可能需要将输入的后端图转换为 NetworkX 图,这可能很耗时。如果一个后端图是 NetworkX 图的鸭子类型兼容(duck-type compatible),则后端可以选择不转换为 NetworkX 图而直接使用输入的图。

  • nx.config.cache_converted_graphs (NETWORKX_CACHE_CONVERTED_GRAPHS 环境变量) 是一个布尔值(默认为 True),控制是否将图转换结果缓存到 G.__networkx_cache__。缓存可以通过避免重复转换来提高性能,但会占用更多内存。

注意

后端应该遵循 NetworkX 后端命名约定。例如,如果一个后端名为 parallel,并使用 backend=parallelNETWORKX_BACKEND_PRIORITY=parallel 指定,则安装的包是 nx-parallel,如果我们直接导入后端包,则会使用 import nx_parallel

鼓励后端文档说明其推荐的使用方式以及其图类型是否与 NetworkX 图进行鸭子类型兼容。如果后端图与 NetworkX 兼容,并且您希望您的工作流程能自动与后端“正常工作”——必要时进行转换和缓存——那么请使用上述所有配置。图的自动转换是选择加入的,并且配置赋予用户控制权。

示例:#

对 cugraph 支持的每个算法函数使用 cugraph 后端。这将允许对 cugraph 不支持的算法调用回退到默认的 NetworkX 实现,因为图生成器函数仍然返回 NetworkX 图。

bash> NETWORKX_BACKEND_PRIORITY=cugraph python my_networkx_script.py

显式地对某个函数调用使用 parallel 后端。

nx.betweenness_centrality(G, k=10, backend="parallel")

通过将后端图类型的一个实例传递给函数来显式地对某个函数调用使用 parallel 后端。

H = nx_parallel.ParallelGraph(G)
nx.betweenness_centrality(H, k=10)

显式地使用 parallel 后端并传递额外的后端特定参数。这里,get_chunksparallel 后端特有的参数。

nx.betweenness_centrality(G, k=10, backend="parallel", get_chunks=get_chunks)

自动分派 cugraph 后端来处理所有 NetworkX 算法和生成器,并允许将生成器返回的后端图对象传递给后端不支持的 NetworkX 函数。

bash> NETWORKX_BACKEND_PRIORITY_ALGOS=cugraph \
      NETWORKX_BACKEND_PRIORITY_GENERATORS=cugraph \
      NETWORKX_FALLBACK_TO_NX=True \
      python my_networkx_script.py

工作原理是什么?#

如果您查看过 NetworkX 代码库中的函数,您可能会看到大多数函数上都有 @nx._dispatchable 装饰器。这个装饰器允许 NetworkX 函数在可用时分派给相应的后端函数。当调用被装饰的函数时,它首先检查是否有可运行该函数的后端,如果没有指定或没有合适的后端,则运行 NetworkX 版本的函数。

后端关键字参数#

当调用带 backend 关键字参数的被装饰函数时,它会检查指定的后端是否已安装并加载。接下来,它通过查找名为 __networkx_backend__ 的属性来确定每个输入图的后端名称,从而检查是否需要转换输入图。如果所有输入图的后端都与 backend 关键字参数匹配,则使用原始输入调用后端函数。如果任何输入图与 backend 关键字参数不匹配,则在调用之前将其转换为后端图类型。如果任何步骤不可能,例如后端未实现此函数,则会抛出异常。

查找后端#

当调用不带 backend 关键字参数的被装饰函数时,它会尝试查找可分派的后端函数。通过 __networkx_backend__ 属性解析每个输入图参数的后端类型,如果它们都一致,则在可能的情况下调用该后端函数。否则,按照配置 backend_priority 中列出的顺序逐个考虑后端。如果该后端支持该函数并且可以将输入图转换为其后端类型,则调用该后端函数。否则,考虑下一个后端。

在此过程中,后端可以通过其接口中的辅助方法向分派器提供有用的信息。分派器使用后端方法 can_runshould_run 来确定是否使用后端函数。如果节点数量较少,运行 NetworkX 版本的函数可能会更快。后端就是通过这种方式提供是否运行的信息。

回退到 NetworkX#

如果没有合适的后端,我们将“回退”到 NetworkX 函数。这意味着我们解析所有输入图的后端,如果所有都是 NetworkX 图,则调用 NetworkX 函数。如果任何一个不是 NetworkX 图,除非设置了 fallback_to_nx 配置,否则会抛出异常。如果设置了,则在调用 NetworkX 函数之前,将所有图类型转换为 NetworkX 图类型。

修改图的函数#

任何带有指示其修改图的选项装饰的函数,在自动查找后端时会经历 slightly different 的路径。这些函数通常生成图,或者添加属性或改变图结构。配置 backend_priority.generators 包含一个后端名称列表,类似于配置 backend_priority。查找匹配后端的流程类似。一旦找到,将调用后端函数并返回一个后端图(而不是 NetworkX 图)。然后您可以在后端支持的任何函数中使用此后端图。如果将配置 fallback_to_nx 设置为允许在调用函数之前将后端图转换为 NetworkX 图,您也可以将其用于后端不支持的函数。

可选关键字参数#

后端可以向 NetworkX 函数添加可选关键字参数,以允许您控制后端算法的各个方面。因此,函数签名可以扩展,超出 NetworkX 函数签名。例如,parallel 后端可能有一个参数来指定使用多少个 CPU。这些参数在函数调用开始时由可分派装饰器代码收集,并在调用后端函数时使用。

现有后端#

NetworkX 并不了解所有已创建的后端。事实上,NetworkX 库无需知道某个后端的存在即可正常工作。只要后端包创建了 entry_point 并提供了正确的接口,当用户使用上述三种方法之一请求时,该后端就会被调用。一些后端已经与 NetworkX 开发者合作,以确保顺畅运行。

请参阅后端部分,查看已知与当前稳定版 NetworkX 兼容的可用后端列表。

内省和日志记录#

内省技术旨在揭开分派和后端图转换行为的神秘面纱。

查看分派机制正在做什么的主要方法是启用日志记录。这可以帮助您验证您指定的后端是否正在使用。您可以启用 NetworkX 的后端日志记录器,将其打印到 sys.stderr,如下所示:

import logging
nxl = logging.getLogger("networkx")
nxl.addHandler(logging.StreamHandler())
nxl.setLevel(logging.DEBUG)

您可以通过运行此命令来禁用它:

nxl.setLevel(logging.CRITICAL)

请参考 logging 以了解有关 Python 日志记录功能的更多信息。

通过查看 .backends 属性,您可以获取实现特定函数的所有当前安装的后端集合。例如:

>>> nx.betweenness_centrality.backends  
{'parallel'}

函数文档字符串也会显示哪些已安装的后端支持它,以及任何后端特定的注意事项和关键字参数:

>>> help(nx.betweenness_centrality)  
...
Backends
--------
parallel : Parallel backend for NetworkX algorithms
  The parallel computation is implemented by dividing the nodes into chunks
  and computing betweenness centrality for each chunk concurrently.
...

NetworkX 文档网站在函数参考中也包含有关 NetworkX 信任的后端的信息。例如,请参阅 all_pairs_bellman_ford_path_length()

内省功能目前有限,但我们正在努力改进。我们计划使其更容易回答诸如以下问题:

  • 发生了什么(以及为什么)?

  • *将要*发生什么(以及为什么)?

  • 时间花在哪里(包括转换)?

  • 缓存中有什么以及占用了多少内存?

透明度对于更好的理解、调试能力和定制至关重要。毕竟,NetworkX 分派功能非常灵活,可以支持涉及多个后端和精细配置的高级工作流程,而内省可以通过描述*何时*以及*如何*改进您的工作流程来满足您的需求。如果您对如何改进内省功能有建议,请告诉我们

后端开发者文档#

创建自定义后端#

  1. 定义 BackendInterface 对象

    注意,BackendInterface 不需要必须是一个类。它也可以是一个类的实例,或者一个模块。您可以在后端的 BackendInterface 对象中定义以下方法或函数。

    1. convert_from_nxconvert_to_nx 方法或函数是后端分派工作所必需的。 convert_from_nx 的参数是:

      • G : NetworkX 图

      • edge_attrsdict, 可选

        字典,将边属性映射到默认值,如果 G 中缺少该属性。如果为 None,则不会转换任何边属性,默认值可能为 1。

      • node_attrs: dict, 可选

        字典,将节点属性映射到默认值,如果 G 中缺少该属性。如果为 None,则不会转换任何节点属性。

      • preserve_edge_attrsbool

        是否保留所有边属性。

      • preserve_node_attrsbool

        是否保留所有节点属性。

      • preserve_graph_attrsbool

        是否保留所有图属性。

      • preserve_all_attrsbool

        是否保留所有图、节点和边属性。

      • namestr

        算法的名称。

      • graph_namestr

        正在转换的图参数的名称。

    2. can_run (可选)

      如果您的后端只部分实现了某个算法,您可以在 BackendInterface 对象中定义一个 can_run(name, args, kwargs) 函数,该函数返回 True 或 False,指示后端是否可以使用给定的参数运行该算法。除了布尔值,您还可以返回一个字符串消息来告知用户该算法无法运行的原因。

    3. should_run (可选)

      后端还可以定义 should_run(name, args, kwargs),它与 can_run 类似,但回答的是后端是否应该运行。 should_run 仅在执行后端图转换时运行。与 can_run 一样,它接收原始参数,以便通过检查参数来决定是否应该运行。 can_runshould_run 之前运行,因此 should_run 可以假定 can_run 为 True。如果后端未实现,则假定 can_run``和 ``should_run 在后端实现了该算法时始终返回 True。

    4. on_start_tests (可选)

      后端可以定义一个特殊的 on_start_tests(items) 函数。它将在发现 NetworkX 测试列表时被调用。每个项都是一个测试对象,如果后端不支持该测试,可以使用 item.add_marker(pytest.mark.xfail(reason=...)) 将其标记为 xfail。

  2. 添加入口点

    为了能够被 NetworkX 发现,您的包必须在包的元数据中注册一个 入口点(entry-point) networkx.backends,其中 一个键指向您的分派对象 。例如,如果您使用 setuptools 管理您的后端包,您可以将以下内容添加到您的 pyproject.toml 文件中

    [project.entry-points."networkx.backends"]
    backend_name = "your_backend_interface_object"
    

    您还可以添加 backend_info 入口点。它指向 get_info 函数,该函数返回所有后端信息,这些信息随后用于在算法文档页末尾构建“附加后端实现”框。请注意,get_info 函数不应导入您的后端包。

    [project.entry-points."networkx.backend_info"]
    backend_name = "your_get_info_function"
    
    get_info 应该返回一个包含以下键值对的字典:
    • backend_namestr 或 None

      这是在 backend 关键字参数中传递的名称。

    • projectstr 或 None

      您的后端项目的名称。

    • packagestr 或 None

      您的后端包的名称。

    • urlstr 或 None

      这是您的后端代码库或文档的 URL,将作为超链接显示在“附加后端实现”部分中 backend_name 上。

    • short_summarystr 或 None

      您的后端的一行摘要,将显示在“附加后端实现”部分中。

    • default_configdict

      一个字典,将后端配置参数名称映射到其默认值。这用于在导入 networkx 时自动初始化所有已安装后端的默认配置。

      另请参阅

      配置

    • functionsdict 或 None

      一个字典,将函数名称映射到包含该函数信息的字典。该信息可以包括以下键:

      • url : str 或 None 该函数的源代码或文档的 URL。

      • additional_docs : str 或 None 关于后端函数实现的简短描述或注意事项。

      • additional_parameters : dict 或 None 一个字典,将附加参数的标题映射到其简短描述。例如:

        "additional_parameters": {
            'param1 : str, function (default = "chunks")' : "...",
            'param2 : int' : "...",
        }
        

      如果这些键中的任何一个不存在,则相应的信息将不会在 NetworkX 文档网站的“附加后端实现”部分显示。

    注意,只有当您的后端是 NetworkX 的信任后端,并且存在于 NetworkX 仓库中的 circleci/config.ymlgithub/workflows/deploy-docs.yml 文件中时,您的后端文档才会显示在 NetworkX 官方文档上。

  3. 定义后端图类

    后端必须创建一个对象,该对象具有一个属性 __networkx_backend__,其值为包含入口点名称的字符串:

    class BackendGraph:
        __networkx_backend__ = "backend_name"
        ...
    

    后端图实例可以有一个 G.__networkx_cache__ 字典来启用缓存,并且应注意在适当的时候清除缓存。

测试自定义后端#

要测试您的自定义后端,您可以在您的后端上运行 NetworkX 测试套件。这也确保了自定义后端与 NetworkX 的 API 兼容。以下步骤将帮助您运行测试:

  1. 设置后端环境变量
    • NETWORKX_TEST_BACKEND : 将此变量设置为您的后端的 backend_name,将使 NetworkX 的分派机制能够使用 your_backend_interface_object.convert_from_nx(G, ...) 函数自动将常规的 NetworkX GraphDiGraphMultiGraph 等转换为其后端等效项。

    • NETWORKX_FALLBACK_TO_NX (默认为 False) : 将此变量设置为 True 将指示测试对您的自定义后端未实现的算法使用 NetworkX Graph。将其设置为 False 将仅运行由您的自定义后端实现的算法的测试,而其他算法的测试将 xfail(标记为预期失败)。

  2. 运行测试

    您可以使用以下命令为您的自定义后端调用 NetworkX 测试:

    NETWORKX_TEST_BACKEND=<backend_name>
    NETWORKX_FALLBACK_TO_NX=True # or False
    pytest --pyargs networkx
    

测试如何运行?#

  1. 分派到后端实现时使用 _convert_and_call 函数,而在测试时使用 _convert_and_call_for_tests 函数。除了测试之外,它还检查返回 numpy 标量的函数,对于返回图的函数,它会运行后端实现和 networkx 实现,然后将后端图转换为 NetworkX 图再进行比较,最后返回 networkx 图。这可以被视为(实用的)技术债。我们将来可能会替换这些检查。

  2. 运行测试时的转换
    • 使用 <your_backend_interface_object>.convert_from_nx(G, ...) 将 NetworkX 图转换为后端图。

    • 将后端图对象传递给算法的后端实现。

    • 使用 <your_backend_interface_object>.convert_to_nx(result, ...) 将结果转换回 NetworkX 测试期望的形式。

    • 对于 nx_loopback,使用可分派元数据复制图。

  3. NETWORKX_FALLBACK_TO_NX 环境变量设置为 False 时,未由后端实现的可分派算法将导致 pytest.xfail,这表明并非所有测试都在运行,同时避免了引发明确的失败。

_dispatchable([func, name, graphs, ...])

一个装饰器函数,用于将 func 函数的执行重定向到其后端实现。