后端和配置#

后端允许你执行一个替代的后端实现,而不是使用NetworkX的纯Python字典实现。配置提供了库级别的配置设置存储,这些设置也可以来自环境变量。

Note

NetworkX的后端和配置系统正在频繁更新和改进。使用后端的用户界面通常是稳定的。在极少数情况下,如果需要对后端或配置API进行兼容性破坏的更改,可能不会遵循NetworkX的 标准弃用政策。此灵活性旨在允许我们快速响应用户反馈并改进可用性,同时将尽量避免不必要的中断。NetworkX后端的开发人员应定期监控更新以保持兼容性。参与每周的 NX-dispatch会议 是保持更新并参与持续讨论的绝佳方式。

Backends#

后端用户文档#

NetworkX采用插件调度架构,这意味着我们可以通过最小的代码更改来插入和拔出后端。一个有效的NetworkX后端指定了 入口点 ,命名为 networkx.backends ,并在安装时(不是导入时)提供一个可选的 networkx.backend_info 。这允许NetworkX将函数调用分派(重定向)到后端,使执行流程流向指定的后端实现,类似于将充电器插入插座将电力重定向到手机的方式。这种设计增强了灵活性和集成性,使NetworkX更具适应性和效率。

在安装包后,有三种主要方式使用后端。您可以设置环境变量并运行与NetworkX相同的代码。您可以使用NetworkX函数的 backend=... 关键字参数。或者,您可以将NetworkX图转换为后端图类型,并调用该后端支持的NetworkX函数。环境变量和后端关键字会自动将您的NetworkX图转换为后端类型。手动转换它允许您在多个函数调用中使用相同的后端图,减少转换时间。

例如,您可以在启动python之前设置环境变量,以请求所有可分派函数自动分派到给定后端:

bash> NETWORKX_AUTOMATIC_BACKENDS=cugraph python my_networkx_script.py

或者您可以指定后端作为关键字参数:

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

或者您可以将NetworkX图对象 G 转换为特定于后端的类似图对象,然后将其传递给NetworkX函数:

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

第一种方法在您不想更改NetworkX代码并只想在不同后端上运行代码时很有用。第二种方法在需要传递额外的后端特定参数时很方便,例如:

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

这里, get_chunks 不是NetworkX参数,而是nx_parallel特定的参数。

NetworkX还提供了一个非常基本的日志系统,可以帮助您验证您指定的后端是否正在实现。这很可能会在未来变得更加强大。您可以启用networkx的后端日志记录器,如下所示:

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

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

nxl.setLevel(logging.CRITICAL)

参考 这里 以了解更多关于Python中的日志记录功能。

这是如何工作的?#

您可能在代码库中的许多NetworkX函数上看到了 @nx._dispatchable 装饰器。这个装饰器函数通过将NetworkX函数分派到指定的后端(如果可用),或者在没有指定或可用后端时使用NetworkX运行它来工作。它检查指定的后端是否有效并已安装。如果没有,它会引发一个 ImportError 。它还解析从提供的 argskwargs 中的图参数,处理图作为位置参数或关键字参数传递的情况。然后,它检查解析的图中是否有来自后端的图,方法是检查它们是否具有 __networkx_backend__ 属性。属性 __networkx_backend__ 包含一个字符串,其中包含 entry_point 的名称(稍后会详细介绍)。如果有来自后端的图,它根据 backend_priority 配置确定后端的优先级。如果有可分派的图(即来自后端的图),它会检查所有图是否来自同一个后端。如果没有,它会引发一个 TypeError 。如果指定了后端并且与图的后端匹配,它会加载后端并调用后端上的相应函数以及额外的后端特定 backend_kwargs 。调用函数后,如果启用了日志记录,networkx记录器会显示 DEBUG 消息。如果没有找到兼容的后端或后端未实现该函数,它会引发一个 NetworkXNotImplemented 异常。如果函数改变了输入图或返回图、图生成器或加载器,它会尝试使用自动转换的后端转换并运行该函数。并且只有在 backend.should_run(...) 返回 True 时才会转换并运行。如果没有使用后端,它会回退到使用NetworkX运行原始函数。有关更多详细信息,请参阅 _dispatchable 类的 __call__ 方法。

NetworkX库不需要知道后端的存在就能工作。只要后端包创建了 entry_point 并提供了正确的接口,当用户使用上述三种方法之一请求时,它就会被调用。一些后端已经与NetworkX开发人员合作,以确保顺利运行。它们如下:

  • graphblas :

    启用OpenMP的稀疏线性代数后端。

  • cugraph :

    GPU加速后端。

  • parallel :

    NetworkX算法的并行后端。

  • loopback :

    仅用于测试目的,不是一个真正的后端。

请注意, backend_name 例如是 parallel ,安装的包是 nx-parallel ,我们在导入包时使用 nx_parallel

后端开发者文档#

创建自定义后端#

  1. 定义一个 BackendInterface 对象:

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

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

      • G : NetworkX图

      • edge_attrsdict, 可选

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

      • node_attrsdict, 可选

        字典映射节点属性到默认值,如果 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_runshould_run 假设总是返回True,如果后端实现了该算法。

    4. on_start_tests (可选):

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

  2. 添加入口点

    为了被NetworkX发现,您的包必须在包的元数据中注册一个 入口点 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

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

    • functionsdict 或 None

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

      • url : str 或 None 指向 function 的源代码或文档的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的调度机制自动将常规NetworkX GraphDiGraphMultiGraph 等转换为它们的后端等效物,使用 your_backend_interface_object.convert_from_nx(G, ...) 函数。

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

  2. 运行测试:

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

    NETWORKX_TEST_BACKEND=<backend_name>
    NETWORKX_FALLBACK_TO_NX=True # 或 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. 未由后端实现的

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

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

Configs#

config#

alias of NetworkXConfig(backend_priority=[], backends=Config(graphblas=Config(), parallel=Config()), cache_converted_graphs=True)

class NetworkXConfig(**kwargs)[source]#

网络配置,用于控制行为,例如如何使用后端。

支持使用属性和括号表示法进行配置的获取和设置:

>>> nx.config.backend_priority == nx.config["backend_priority"]
True
Parameters:
backend_priority后端名称列表

启用图形的自动转换,以便为后端实现的算法使用后端图形。优先级给予列表中较早的后端。 默认是空列表。

backends后端名称到后端配置的映射

配置映射的键是所有已安装的 NetworkX 后端的名称,值是它们的配置作为配置映射。

cache_converted_graphs布尔值

如果为 True,则将转换后的图形保存到输入图形的缓存中。当自动使用 backend_priority 中的后端或在使用函数调用的 backend= 关键字参数时,可能会发生图形转换。缓存可以通过避免重复转换来提高性能,但会使用更多内存。应注意不要手动修改具有缓存图形的图;例如, G[u][v][k] = val 会更改图形,但不会清除缓存。使用诸如 G.add_edge(u, v, weight=val) 的方法将清除缓存以保持一致性。 G.__networkx_cache__.clear() 手动清除缓存。 默认是 True。

Notes

可以使用环境变量来控制一些默认配置:

  • NETWORKX_BACKEND_PRIORITY : 从逗号分隔的名称设置 backend_priority

  • NETWORKX_CACHE_CONVERTED_GRAPHS : 如果非空,则将 cache_converted_graphs 设置为 True。

这是一个全局配置。在多线程使用时请谨慎。

class Config(**kwargs)[source]#

NetworkX 配置的基础类。

有两种方法可以使用这个类来创建配置。推荐的方法是通过子类化 Config 并添加文档和注解。

>>> class MyConfig(Config):
...     '''早餐!'''
...
...     eggs: int
...     spam: int
...
...     def _check_config(self, key, value):
...         assert isinstance(value, int) and value >= 0
>>> cfg = MyConfig(eggs=1, spam=5)

另一种方法是直接将初始配置作为关键字参数传递给 Config 实例:

>>> cfg1 = Config(eggs=1, spam=5)
>>> cfg1
Config(eggs=1, spam=5)

一旦定义,配置项可以被修改,但默认情况下不能被添加或删除。 Config 是一个 Mapping ,可以通过属性或方括号获取和设置配置:

>>> cfg.eggs = 2
>>> cfg.eggs
2
>>> cfg["spam"] = 42
>>> cfg["spam"]
42

为了方便,还可以在 “with” 语句中设置配置:

>>> with cfg(spam=3):
...     print("spam (在上下文中):", cfg.spam)
spam (在上下文中): 3
>>> print("spam (上下文后):", cfg.spam)
spam (上下文后): 42

子类也可以定义 _check_config (如上面的例子所示)来确保被赋值的值是有效的:

>>> cfg.spam = -1
Traceback (most recent call last):
    ...
AssertionError

如果需要一个更灵活的配置对象,允许添加和删除配置,那么在定义子类时传递 strict=False

>>> class FlexibleConfig(Config, strict=False):
...     default_greeting: str = "Hello"
>>> flexcfg = FlexibleConfig()
>>> flexcfg.name = "Mr. Anderson"
>>> flexcfg
FlexibleConfig(default_greeting='Hello', name='Mr. Anderson')