pyiron_workflow - 基于图和节点的工作流程工具。
项目描述
pyiron_workflow
概述
pyiron_workflow
是一个框架,它从简单的Python函数构建工作流程作为计算图。它的目标是尽可能容易地创建可靠、可重用和可共享的工作流程,特别关注HPC环境中的研究工作流程。
节点由使用简单装饰器的Python函数形成,生成的节点可以连接其数据输入和输出。与常规Python不同,它们以延迟的方式操作。
通过允许(但在数据DAG的情况下不强制)用户指定执行流程,支持循环和无环图。
通过从装饰函数中抓取类型提示,新数据值和新图连接(可选)需要符合提示,使工作流程强类型。
单个节点计算可以分配给并行进程以实现可扩展性。标准的Python执行器如concurrent.futures.ThreadPoolExecutor
和ProcessPoolExecutor
可以工作,例如,来自executorlib
的Executor
执行器也可以,它有助于在HPC上运行。通过在本地保存图并在远程加载、运行和重新保存,也可以轻松地在远程进程上运行整个图,例如SLURM分配。有关一些简单示例,请参阅这个笔记本。
一旦你对工作流程满意,就可以轻松地将它转换成宏,以便在其他工作流程中使用。这允许通过组合更简单的图来构建越来越复杂的计算图。
节点(包括宏)可以以Python代码的形式存储为纯文本,并由未来的工作流程导入以便于访问。这鼓励并支持一个有用的节点生态系统,因此你不需要重新发明轮子。当这些Python文件位于正确管理的git仓库中,并通过稳定的渠道(例如conda-forge)发布时,它们满足了FAIR原则的大部分要求。
执行或部分执行的图可以存储到文件中,可以通过显式调用或自动运行后执行。这些可以重新加载(在工作流程的情况下,在实例化时自动),并检查/重新运行等。如果你的工作流程失败,它将(默认情况下)为你保存一个恢复文件,以便在失败时恢复。
安装
conda install -c conda-forge pyiron_workflow
用户介绍
pyiron_workflow
以Workflow
对象的形式提供一个单一的入口点,并使用装饰器来简化将常规Python函数转换为可以放入计算图的“节点”的过程。
将你的Python函数装饰为节点意味着它现在是一个类,所以在你可以调用它之前需要实例化它——但除此之外,它非常像常规的Python函数。你可以在其中放入常规Python代码,并且这些代码将在运行节点时执行。
>>> from pyiron_workflow import Workflow
>>>
>>> @Workflow.wrap.as_function_node
... def HelloWorld(greeting="Hello", subject="World"):
... hello = f"{greeting} {subject}"
... return hello
>>>
>>> hello_node = HelloWorld() # Instantiate a node instance
>>> hello_node(greeting="Salutations") # Use it just like a function
'Salutations World'
这种节点形式的目的是将一系列函数调用构建成一个有向图,该图给出了你工作流程的正式定义。在底层,上述节点具有标记的输入和输出数据通道。
>>> print(hello_node.inputs.labels)
['greeting', 'subject']
>>> hello_node.outputs.hello.value
'Salutations World'
每次运行时,Function
节点都会获取其输入,将其传递给装饰的函数,执行它,然后将结果放入节点的输出通道中。这些输入和输出可以链接在一起形成一个计算图。输入和输出实际上不仅仅是它们所持有的数据——它们是数据通道——但你可以对它们执行大多数Python操作,就像它们是原始对象一样。如果一个节点只有一个输出,你可以直接引用它,而不是它的单个输出通道。这将动态创建一个新的节点以延迟操作并在运行时处理它。
>>> first = HelloWorld("Welcome", "One")
>>> second = HelloWorld("Greetings", "All")
>>> combined = first + " and " + second
>>> print(type(combined))
<class 'pyiron_workflow.nodes.standard.Add'>
>>> combined()
'Welcome One and Greetings All'
一组节点可以收集在活着的Workflow
对象之下,该对象可以添加和删除节点。让我们将上面的图构建成一个Workflow
,并利用内置的standard
节点来存储输入并将其分支到两个不同的下游节点。
>>> wf = Workflow("readme")
>>> wf.greeting = Workflow.create.standard.UserInput("Hi")
>>> wf.first = HelloWorld(greeting=wf.greeting)
>>> wf.second = HelloWorld(greeting=wf.greeting)
>>> wf.combined = wf.first + " and " + wf.second
>>> wf()
{'combined__add': 'Hi World and Hi World'}
在这里我们可以看到输出是一个字典,键是根据节点标签('combined'
)和通道名称('add'
)。工作流返回所有未连接的输出,并将任何未连接的输入作为具有类似关键字规则的输入参数。让我们利用这一点,轻松地使用不同的值重新运行我们的工作流。
>>> wf(greeting__user_input="Hey", first__subject="you")
{'combined__add': 'Hey you and Hey World'}
一旦我们有一个喜欢且认为有用的工作流,我们可能希望将其打包为一个宏
节点。这些与工作流非常相似,但“固化”。就像函数
节点一样,它们有一个固定的输入和输出集合。它们还允许你对暴露为IO的内容有更多的控制,而工作流(默认情况下)则暴露所有未连接的部分。定义一个宏
也与定义一个函数
非常相似——可以通过装饰一个简单的Python函数来完成。然而,与函数
节点在每个运行时执行其装饰函数并可以包含任意Python代码不同,宏
节点装饰一个定义它们持有的图的函数,它在实例化时执行一次,输入值本身是所有数据通道而不是原始数据,从那时起,运行该节点将运行整个图。
>>> @Workflow.wrap.as_macro_node
... def Combined(wf, greeting="Hey", subject1="You", subject2="World"):
... wf.first = HelloWorld(greeting=greeting, subject=subject1)
... wf.second = HelloWorld(greeting=greeting, subject=subject2)
... wf.combined = wf.first + " and " + wf.second
... return wf.combined
>>>
>>> hello_macro = Combined()
>>> hello_macro(subject2="everyone")
{'combined': 'Hey You and Hey everyone'}
这不仅让我们在如何让人们与图交互(即暴露哪些IO,使用哪些默认值(如果有))方面有更多控制,而且宏
节点是可组合的——我们可以将它们作为节点放入其他宏或工作流中,即我们可以在我们的图中嵌套子图。让我们这样做,并给出一个具有多个输出的节点的第一个例子。
>>> @Workflow.wrap.as_macro_node
... def Composition(self, greeting):
... self.compose = Combined(greeting=greeting)
... self.simple = greeting + " there"
... return self.compose, self.simple
>>>
>>> composed = Composition()
>>> composed(greeting="Hi")
{'compose': 'Hi You and Hi World', 'simple': 'Hi there'}
(注意,我们还把第一个变量重命名为Python的规范self
。第一个变量的名称并不重要——但它必须存在,并代表宏实例!如果你使用Python的self
更容易,那就这么做吧;如果你是从你编写的工作流中复制粘贴,wf
或你的工作流变量将更容易。)
尽管宏只暴露特定的数据用于IO,但你总是可以深入了解对象以查看发生了什么。
>>> composed.compose.second.outputs.hello.value
'Hi World'
这让我们能够通过组合更简单的块来构建越来越复杂的工作流。这些构建块可以通过将你的宏存储在.py
文件中,或者甚至作为Python包发布来共享和重用。这些工作流是正式定义的,因此与普通的Python脚本不同,它们很容易给出非代码表示,例如,我们可以在高层次上.draw
我们的工作流或节点。
或者深入解析宏节点到指定的深度
要探索pyiron_workflow
的其它好处,请查看演示中的quickstart.ipynb
。在那里我们探索了
- 使节点(可选)强类型化
- 保存和加载(可能部分)执行的工作流
- 通过将执行器分配给特定节点来并行化工作流计算
- 使用for循环遍历数据
有关更高级的主题,如循环图,请查看deepdive.ipynb
笔记本,探索文档字符串,或查看
结构
本节针对希望为pyiron_workflow
的核心平台做出贡献的读者,或者遇到他们想要更深入理解的行为或边缘情况的高级用户。
负责将工作流作为图结构化的核心类是pyiron_workflow.channels.Channel
和pyiron_workflow.node.Node
,它们形成了计算图的边和(不出所料)节点。每个节点都持有多个通道,指定了它如何与其他节点交互。
在尽可能的情况下,节点不同的任务和角色已经被分解,使得pyiron_workflow.node.Node
(以及在一定程度上pyiron_workflow.channels.Channel
)从更简单、更专业的类中继承了个别的行为。所有这些位于节点(和通道)上游的类都存储在pyiron_workflow.mixin
子模块中。这些类从极其简单的混入类pyiron_workflow.has_interface_mixins.has_label
(它所做的只是确保子类有label: str
和full_label: str
属性)到复杂的混入类pyiron_workflow.run.Runnable
,后者提供了子类使用run()
方法的能力,并指定了要实现哪些其他功能才能实现这一点,并提供了其他相关工具和方法。每个混入模块都有其存在的理由——即“为什么”——作为模块级别的文档字符串,并为混入作为类级别的文档字符串提供的承诺提供见解。节点类将这些个别能力组合在一起并控制它们之间的交互,但要理解每个继承的能力,请查看这些模块。
从节点继承树中的一切(*)都在pyiron_workflow.nodes
子模块中。在pyiron_workflow.mixin
定义了pyiron_workflow.nodes.Node
的核心能力之后,pyiron_workflow.nodes
提供了角色的专业化和多样化。一个关键的角色是pyiron_workflow.nodes.static_io.StaticNode
,它包含工具,以确保节点IO在类级别上进行指定——如果我们开始考虑评估节点互操作性和引导工作流程设计,这是一个关键功能,我们不想首先实例化节点来找出它们是否可以工作!它还包括用户熟悉节点,如pyiron_workflow.nodes.function.Function
,它确保节点每次运行都执行特定的Python函数,以及从更通用的pyiron_workflow.nodes.composite.Composite
继承的pyiron_workflow.nodes.macro.Macro
,它包含自己的子图并在运行节点时执行那个子图。除了包含确保我们可以进行像@as_function_node
和@as_function_node()
这样的调用的辅助器的pyiron_workflow.nodes.multiple_dispatch
之外,这里的子模块都定义了在抽象混入或基类和用户界面极为具体的pyiron_workflow.nodes.standard
节点库之间的不同节点类,以便包含在用户工作流程中!
(*)好吧,不是从pyiron_workflow.node.Node
下游的一切都包含在pyiron_workflow.nodes
中。还有一个pyiron_workflow.workflow.Workflow
——这是用户的主要入口点。与其他所有节点不同,工作流程对象具有动态和可变的IO,并且不能作为任何其他工作流程的一部分插入(即,它们必须是它们工作流程图中的最高层对象)。否则,它们与宏一样从pyiron_workflow.nodes.composite.Composite
继承,并表现出类似的行为。原则上,应该重构这些类,以便节点和工作流程都继承自共享的能力,但工作流程本身不是直接节点——即,将pyiron_workflow.nodes.static_io.StaticNode
的行为直接拉入pyiron_workflow.node.Node
,并为pyiron_workflow.node.Node
和pyiron_workflow.workflow.Workflow
产生一个原始祖先。有一个关于此问题的开放问题:https://github.com/pyiron/pyiron_workflow/issues/360。
与节点类似,pyiron_workflow.channels.Channel
也有更具体的子节点。通常,它们可以沿两组轴进行划分:输入/输出和数据/信号。输入/输出划分是清晰的,只是为了确保例如两个输入节点之间没有连接。当我们确实需要在输入之间传递数据时,例如从“围园”宏的外部IO层向下到其子节点(参阅用户文档以获取更多详细信息),我们利用pyiron_workflow.channels.DataChannel.value_receiver: pyiron_workflow.channel.DataChannel | None
属性来保持两个类似类型的通道同步。对于pyiron_workflow.channels.SignalChannel
,没有等效的属性——它们控制执行流程,在两个输入或两个输出之间建立连接是没有意义的。事实上,大多数时候你甚至不会考虑信号通道;对于有向无环图(DAG),合适的执行流程可以自动从数据通道连接和信号通道连接的结构中确定。仅当存在循环图或条件执行模式(参阅pyiron_workflow.nodes.standard.If
)时,才需要直接管理信号通道。
pyiron_workflow.executors
子模块包含对pyiron_workflow.node.Node.executor: concurrent.futures.Executor
属性的引用,其余顶层模块相当独立,可以单独探索。有些是简单的事情,我们将其放在节点上,例如pyiron_workflow.io
,它只是用于存储通道的容器,或者对节点进行操作的事情,例如pyiron_workflow.draw
。
虽然不适合叙述流程,但仍然有趣的内容
- 节点(除工作流程外)需要有一个静态的接口,但它们内部的行为是自由的!一个简单的例子是考虑一个函数节点,它可能在某些输入条件下返回
True
,在其他条件下返回False
。一个更复杂且值得研究的例子是pyiron_workflow.nodes.for_loop.For
节点。它是组合的,执行其子图,但在每次运行时,它都会内部修改其子节点,以适应它所提供的输入长度!这是大胆的,但完全允许,因为for节点的IO通道在整个过程中保持不变。 pyiron_workflow
的主要用户界面是pyiron_workflow.Workflow
,但在pyiron_workflow.__init__
中还有其他工具可用,指定了完整的API,可能对高级用户和节点开发者有用;不会影响那里公开工具的接口或行为的更改不受语义版本控制约束,但如果更改触及那里列出的任何内容,我们应该努力实现向后兼容性。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分布
构建分发
pyiron_workflow-0.11.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 11f77b4f248008531a4f7496a65505f1788be1d13f23f3d9ee9c743294a67ec8 |
|
MD5 | a6deec82a0ef1e0fb25af426d4dab52c |
|
BLAKE2b-256 | b488c513c4b2fc036ff1187bdc1979a4b5ab6793fb26075f14cedf65bd94e99b |
pyiron_workflow-0.11.0-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | a2b796f5782dc83a85f806d99786ae019bfe7315c3a14cd4c7bc401db26cfbdb |
|
MD5 | b074aa3f19cc444d6c84430ab600f5dd |
|
BLAKE2b-256 | d9ac0c2e671328fcbc0c612ae7babd473b2ed2a57fc964ef123429bdd313c457 |