使用方法#
备注
本章假定读者已了解有关 basic concept of processes 的前一节内容。
本节将解释适用于所有流程的流程工作方面。仅与特定子流程类型有关的细节将在相应章节中记录:
定义 processes#
Process 规范#
一个流程如何定义它所需要的或可选择的输入,取决于流程的类型。 CalcJob
和 WorkChain
的输入由 ProcessSpec
类提供,该类通过 define()
方法定义。对于流程函数, ProcessSpec
由 engine 根据装饰函数的签名动态生成。因此,要确定流程需要哪些输入,只需查看 define
方法中的流程规范或函数签名即可。对于 CalcJob
和 WorkChain
,还有一个 process builder 的概念,即可以通过 shell 中的制表符完成和帮助字符串来检查输入。
The three most important attributes of the ProcessSpec
are:
inputs
outputs
exit_codes
通过这些属性,我们可以定义流程的输入、输出以及出错时可能返回的退出代码。这样,只需查看流程规范,就能确切地知道 会发生什么 ,只是不知道 如何 发生。 inputs
和 outputs
属性是 名称空间 ,包含所谓的 端口 ,每个端口代表一个特定的输入或输出。命名空间可以任意嵌套端口,因此被称为 端口命名空间 。端口和端口命名空间分别由 Port
和 PortNamespace
类实现。
端口 (port) 和端口命名空间 (port namespaces)#
要为 process 规范定义输入,我们只需在 inputs
端口命名空间中添加一个端口,如下所示:
spec = ProcessSpec()
spec.input('parameters')
input
方法将创建一个 InputPort
的实例(基 Port
的子类),并将其添加到规范的 inputs
端口命名空间。创建输出也同样简单,但应使用 output()
方法:
spec = ProcessSpec()
spec.output('result')
这将导致创建一个 CalcJobOutputPort
实例(也是基础 Port
的子类),并将其添加到 outputs
指定属性中。请注意, inputs
和 output
是 PortNamespace
的实例,这意味着它们可以包含任何端口。但 PortNamespace
本身也是一个端口,因此可以将其添加到另一个端口命名空间,从而创建嵌套端口命名空间。例如,在输入命名空间中创建一个新的命名空间非常简单:
spec = ProcessSpec()
spec.input_namespace('namespace')
这将在规范的 inputs
命名空间中创建一个名为 namespace
的新 PortNamespace
。您可以在一条语句中创建任意嵌套的命名空间,方法是用 .
分隔,如图所示:
spec = ProcessSpec()
spec.input_namespace('nested.namespace')
该命令将导致名为 namespace
的 PortNamespace
嵌套在名为 nested
的另一个 PortNamespace
中。
备注
由于句点被保留用于表示不同的嵌套命名空间,因此不能用于终端输入和输出端口的名称中,否则会被误解为嵌套在命名空间中的端口。
从图形上看,这可以形象地理解为一个嵌套字典,如下所示:
'inputs': {
'nested': {
'namespace': {}
}
}
ProcessSpec
的 outputs
属性与 inputs
属性一样,也是 PortNamespace
,唯一不同的是,它将创建 OutputPort
而不是 InputPort
实例。因此,通过 PortNamespaces
进行嵌套的概念同样适用于 ProcessSpec
的输出。
验证和默认值#
在上一节中,我们看到 ProcessSpec
使用 PortNamespace
、 InputPort
和 OutputPort
来定义 Process
的输入和输出结构。允许端口嵌套的基本概念是, PortNamespace
、 InputPort
和 OutputPort
都是 Port
的子类。作为同一个类的不同子类,它们有更多的共同属性,例如与验证和默认值概念相关的属性。三者都具有以下属性( OutputPort
没有 default
属性除外):
default
required
valid_type
validator
这些属性可以在创建端口时设置,也可以在事后设置,只要规范尚未封存,也就是说,只要在相应 Process
的 define
方法中,就可以无限制地修改这些属性。下面是一个明确设置所有这些属性的输入端口示例:
spec.input('positive_number', required=False, default=lambda: Int(1), valid_type=(Int, Float), validator=is_number_positive)
在这里,我们定义了一个名为 positive_number
的输入,其类型应为 Int
或 Float
,并应通过 is_number_positive
验证器的测试。如果没有输入值,将使用默认值。
警告
在 python 中,避免为函数参数设置可变的默认值( since they are instantiated at function definition and reused for each invocation )是一种很好的做法。如果在函数调用之间改变默认值,可能会导致意想不到的结果。在 AiiDA 中,node(包括存储的和未存储的)被认为是 可变的 ,因此**不应该被用作 process 端口的默认值。不过,可以使用 lambda 返回一个 node 实例,如上例所示。这样,每次实例化 process 时,都会用给定的值返回一个新的 node 实例。
请注意,验证器只是一个自由函数,它只接受一个参数,即需要验证的值。如果不返回任何参数,则认为该值有效。如果要提示值无效并引发验证错误,只需返回一个包含验证错误信息的字符串即可,例如
def is_number_positive(number):
if number < 0:
return 'The number has to be greater or equal to zero'
valid_type
可以定义单个类型,也可以定义多个有效类型。
Added in version 2.1: Optional ports can now accept None
如果一个端口通过 required=False
被标记为可选端口并定义了 valid_type
,那么该端口也将接受 None
作为值,而在此之前这将导致验证错误。这是通过在 valid_type
元组中自动添加 NoneType
来实现的。未定义 valid_type
的端口不受影响。
备注
请注意,默认情况下所有端口都是必需的,但指定默认值意味着输入不是必需的,因此在这种情况下不必指定 required=False
。在上面的示例中添加该值只是为了清晰起见。
在 process 的实例化过程中和最终完成时,将分别根据相应端口的规范对输入或输出值进行验证。如果输入无效,将产生相应的异常,process 的实例化将失败。当输出验证失败时,同样会产生异常,process 的状态将被设置为 Excepted
。
动态命名空间#
在上一节中,我们描述了与验证有关的各种属性,并声称所有端口变体都具有这些属性,但我们只明确讨论了 InputPort
和 OutputPort
。但这一说法仍然正确, PortNamespace
具有相同的属性。如果 PortNamespace
只包含 InputPorts
、 OutputPorts
或其他 PortNamespaces
属性,那么您可能会问 valid_type
或 default
对 PortNamespace
有什么意义。这个问题的答案就在 PortNamespace
属性 dynamic
中。
通常,在设计 Process
的规格时,我们无法准确知道我们希望将哪些输入传递给 process。但是,根据 InputPort
和 OutputPort
的概念,我们**需要确切地知道我们至少期望多少个值,因为它们确实需要定义。这就是 PortNamespace
的 dynamic
属性的作用所在。默认情况下,该属性被设置为 False
,但将其设置为 True
后,就表示该命名空间可接受的值的数量在定义规范时是未知的。这就解释了 valid_type
、 validator
和 default
属性在 PortNamespace
中的含义。如果将命名空间标记为动态命名空间,则可能仍需限制可接受的值集,这可以通过指定有效 类型和或验证器来实现。最终传递到端口命名空间的值将根据这些规则进行验证,就像普通输入端口的值一样。
不可储存的投入#
原则上,输入和输出的唯一有效类型应该是 Data
node 的实例或其子类之一,因为这是唯一可以作为 process 的输入或输出记录在 provenance graph 中的数据类型。不过,在某些情况下,您可能希望将输入传递给 process,而您并不关心 process 的 provenance,因此无论如何都希望传递一种非数据库可存储的类型。
备注
AiiDA 允许你打破 provenance 的限制,但总是试图敦促和引导你保留 provenance 的方向。无论如何,都有合理的理由打破它,但一定要考虑其影响,以及你是否真的愿意失去信息。
在这种情况下, InputPort
的属性为 non_db
。默认情况下,该属性被设置为 False
,但通过将其设置为 True
,我们可以指出传递给端口的值不应作为 node 保存在 provenance graph 中,并与 process node 相链接。这样就可以传递任何正常值,而这些值也可以传递给正常函数。
自动输入序列化#
通常情况下,Python 数据类型的输入在传递给 process 之前需要转换为相应的 AiiDA 类型。手动操作会很麻烦,因此可以在定义 process 时定义一个函数,自动完成转换。这个函数作为 spec.input
的 serializer
参数传递,如果给定的输入不是 None
and 不是 AiiDA 类型,它就会被调用。
对于存储在数据库( non_db=False
)中的输入,序列化函数应返回 AiiDA 数据类型。对于 non_db
输入,函数必须是幂等的,因为它可能会被应用多次。
下面的示例工作链接收三个输入 a
、b
、c
,并简单地返回给定的输入。使用 to_aiida_type()
函数作为序列化函数。
from aiida.engine import WorkChain
from aiida.orm import to_aiida_type
class SerializeWorkChain(WorkChain):
@classmethod
def define(cls, spec):
super().define(spec)
spec.input('a', serializer=to_aiida_type)
spec.input('b', serializer=to_aiida_type)
spec.input('c', serializer=to_aiida_type)
spec.outline(cls.echo)
def echo(self):
self.out('a', self.inputs.a)
self.out('b', self.inputs.b)
self.out('c', self.inputs.c)
这个工作链现在可以用本地 Python 类型调用,这些类型会被 to_aiida_type()
函数自动转换为 AiiDA 类型。请注意,必须加载定义相应 AiiDA 类型的模块才能被 to_aiida_type()
识别。
#!/usr/bin/env runaiida
from aiida.engine import run
from serialize_workchain import SerializeWorkChain
if __name__ == '__main__':
print(run(SerializeWorkChain, a=1, b=1.2, c=True))
# Result: {'a': 1, 'b': 1.2, 'c': True}
当然,您也可以使用序列化功能对输入进行更复杂的序列化。
退出代码#
任何 Process
都很可能有一种或多种预期故障模式。为了向调用者明确说明出了什么问题, Process
支持设置 exit_status
。 exit_status
是一个正整数,是 process node 的一个属性,按照惯例,当它为零时表示 process 成功,而任何其他值都表示失败。 is a common concept in programming 这种以正整数作为退出状态的退出代码概念,是程序交流执行结果的标准方式。
Process
的潜在退出代码可以通过 ProcessSpec
进行定义,就像输入和输出一样。任何退出代码都由一个正非零整数、一个用于引用的字符串标签以及触发退出代码的问题的详细描述组成。请看下面的例子:
spec = ProcessSpec()
spec.exit_code(418, 'ERROR_I_AM_A_TEAPOT', 'the process had an identity crisis')
这定义了 Process
的退出代码,退出状态为 418
,退出信息为 the work chain had an identity crisis
。字符串 ERROR_I_AM_A_TEAPOT
是一个标签,开发人员可以用它来引用 Process
代码中的特定退出代码。
只要 Process
通过特定错误代码退出,调用者就可以通过 node 的 exit_status
和 exit_message
属性对其进行反省。例如,假设我们运行的 Process
抛出了上述退出代码,调用者可以执行以下操作:
in[1] node = load_node(<pk>)
in[2] node.exit_status
out[2] 418
in[2] node.exit_message
out[2] 'the process had an identity crisis'
这非常有用,因为调用者现在可以根据 exit_status
,以编程方式决定如何继续。与解析基于文本的日志或报告相比,这种向非人类传达特定错误的方式要强大得多。此外,退出代码使查询带有特定错误代码的失败 processes 变得非常容易。
参见
有关特定 process 型号的其他文档,请参阅以下章节:
退出代码惯例#
原则上,对退出代码的退出状态的唯一限制是它应该是一个正整数或零。不过,为了有效使用退出代码,在决定使用何种整数时,有一些指导原则和约定。请注意,由于以下规则是 指导原则 ,因此您可以选择忽略它们,目前 engine 不会抱怨,但将来可能会发生变化。无论如何,我们建议您遵守这些准则,因为这将提高您的代码与其他现有插件的互操作性。保留或建议使用以下整数范围:
0 - 99:保留供 aiida-core 内部使用
100 - 199:为从计算作业的调度程序输出中解析出的错误预留(注:尚未实施)
200 - 299:建议用于 process 输入验证错误
300 - 399:建议用于处理关键的 process 错误
对于其他退出代码,可以使用 400 及以上的整数。
Process 元数据#
每个 process 除了通过其 process 规范定义的正常输入外,还可以使用可选的 ‘metadata’。这些元数据与输入的不同之处在于,它们不是 node ,不会在执行的 process 的 provenance graph 中显示为输入。相反,这些输入数据可稍微修改 process 的行为,或允许在代表其执行的 process node 上设置属性。以下元数据输入适用于 * 所有 process 类别:
label
:将在ProcessNode
上设置标签description
:将在ProcessNode
上设置说明store_provenance
:布尔标志,默认为True
,设置为False
时,将确保 process 的执行 不 存储在 provenance graph 中。
Sub classes of the Process
class can specify further metadata inputs, refer to their specific documentation for details.
To pass any of these metadata options to a process, simply pass them in a dictionary under the key metadata
in the inputs when launching the process.
How a process can be launched is explained the following section.
启动 processes#
任何 process 都可以通过 ‘running’ 或 ‘submitting’ 启动。运行指的是在当前的 python 解释器中以阻塞方式运行 process,而提交指的是通过 RabbitMQ 将其发送给守护进程工作者。对于长时间运行的 process,如计算作业或复杂的 workflow,建议最好将其提交给守护进程。这样做的额外好处是,它将直接将控制权交还给您的解释器,并允许守护进程在检查点期间保存中间进度,并在需要重新启动时从这些进度重新加载 process。运行 processes 可用于琐碎的计算任务,如简单的 calcfunctions 或 workfunctions,或用于调试和测试目的。
Process 发射#
要启动 process,可以使用 aiida.engine
模块中的 import 免费功能。共有四种不同的功能:
顾名思义,前三者将 ‘run’ process,后者将 ‘submit’ process 发送给守护进程。运行意味着 process 将在启动它的同一解释器中执行,阻塞解释器,直到 process 终止。相反,向守护进程提交意味着 process 将被发送到守护进程执行,解释器将被直接释放。
所有功能的接口都与 launch(process, inputs)
完全相同:
process
是 process 级或 process function,用于发射inputs
将输入字典传递给 process。
在 2.5 版本发生变更: 在 AiiDA v2.5 之前,输入只能作为关键字参数传递。现在仍然支持这种行为,例如,可以将 process 作为 launch(process, **inputs)
或 launch(process, input_a=value_a, input_b=value_b)
启动。不过,现在推荐的方法是使用作为第二个位置参数传递的输入字典。原因是某些启动器自己定义的参数可能与 process 的输入重叠。例如, submit
方法定义了 wait
关键字。如果被启动的 process 也**定义了名为 wait
的输入,启动器方法就无法将它们区分开来。
可以传递哪些输入取决于要启动的 process 类。例如,当我们要运行一个 ArithmeticAddCalculation
process 的实例时,它需要两个 Int
node 作为输入,名称分别为 x
和 y
[1] ,我们将执行以下操作:
from aiida import orm, plugins
from aiida.engine import submit
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
node = submit(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
该函数将向守护进程提交计算,并立即将控制权返回给解释器,同时返回用于表示 provenance graph 中的 process 的 node。
警告
要使 process 可以提交,类或函数需要在守护进程环境中 import 可用,方法是:a) 赋予其 associated entry point 或 b) 在守护进程工作者将拥有的 PYTHONPATH
中赋予其 including its module path 。
Added in version 2.5: Waiting on a process
调用 submit
时,使用 wait=True
等待 process 完成后再返回 node。这对于交互式笔记本中的教程和演示非常有用,因为在 process 完成之前,用户不应继续操作。当然,也可以使用 run
(见下文),但如果解释器意外关闭,process 就会丢失。如果使用 submit
,process 就会由守护进程运行,守护进程会保存检查点,以便在出现问题时可以随时重启。如果需要并行启动多个 process,并希望等待所有 process 运行完毕,只需使用 submit
和默认的 wait=False
,并在列表中收集返回的 node。然后,您可以将它们传递给 aiida.engine.launch.await_processes()
,一旦所有 process 都结束,它就会返回:
from aiida.engine import submit, await_processes
nodes = []
for i in range(5):
node = submit(...)
nodes.append(node)
await_processes(nodes, wait_interval=10)
await_processes
函数将每 wait_interval
秒循环一次,检查所有 processes(由 nodes
列表中的 ProcessNode
表示)是否都已终止。
run
函数的调用方式相同:
from aiida import orm, plugins
from aiida.engine import run
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
result = run(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
但它不会将 process 提交给守护进程,而是在当前解释器中执行,阻塞解释器直到 process 终止。 run
函数的返回值也***不是代表已执行的 process 的 node,而是 process 返回的结果,即作为输出产生的 node 的字典。如果您仍想获得 process node 或 process node 的 pk,可以使用以下变体之一:
from aiida import orm, plugins
from aiida.engine import run_get_node, run_get_pk
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
result, node = run_get_node(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
result, pk = run_get_pk(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
最后, run()
启动器有两个属性 get_node
和 get_pk
,它们是 run_get_node()
和 run_get_pk()
方法的简单代理。这是一个方便的快捷方式,因为现在只需一个 import 就可以选择使用这三种变体中的任何一种:
from aiida import orm, plugins
from aiida.engine import run
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
result = run(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
result, node = run.get_node(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
result, pk = run.get_pk(ArithmeticAddCalculation, x=orm.Int(1), y=orm.Int(2))
如果要启动一个 process 类,需要更多输入,通常可以在字典中定义这些输入,并使用 python 语法 **
,自动将其展开为关键字参数和值对。上面使用的示例如下:
from aiida import orm, plugins
from aiida.engine import submit
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
inputs = {'x': orm.Int(1), 'y': orm.Int(2)}
node = submit(ArithmeticAddCalculation, inputs)
如上所述,Process function,即 calculation functions 和 work functions ,可以像其他 process 一样发射。Process function 还有两种发射方法:
只需 调用 函数
使用内部运行方法属性
以两个数字相加的计算函数为例,这两种方法如下:
from aiida.engine import calcfunction
from aiida.orm import Int
@calcfunction
def add(x, y):
return x + y
x = Int(1)
y = Int(2)
result = add(x, y)
result, node = add.run_get_node(x, y)
result, pk = add.run_get_pk(x, y)
Process 建设者#
正如 previous section 中所解释的, CalcJob
和 WorkChain
的输入在 define()
方法中定义。要想知道它们的输入是什么,就必须阅读实现方法,如果您不是开发人员,这可能会很烦人。为了简化 process,这两个 process 类提供了一个名为 process 生成器``的工具。process 生成器本质上是一个工具,可以帮助你为要运行的特定 process 类构建输入。要为特定的 ``CalcJob
或 WorkChain
实现获取 生成器 ,您所需要的只是类本身,可分别通过 CalculationFactory
和 WorkflowFactory
加载。让我们以 ArithmeticAddCalculation
为例::
ArithmeticAddCalculation = CalculationFactory('core.arithmetic.add')
builder = ArithmeticAddCalculation.get_builder()
字符串 core.arithmetic.add
是 ArithmeticAddCalculation
的 entry point,将其传递给 CalculationFactory
将返回相应的类。调用该类的 get_builder
方法将返回一个为 ArithmeticAddCalculation
量身定制的 ProcessBuilder
类实例。生成器将帮助您定义 ArithmeticAddCalculation
所需的输入,并提供一些方便的工具来简化这一过程 process。
要想知道生成器提供了哪些输入,只需使用制表符完成。在交互式 python shell 中,只需键入 builder.
并按下 tab 键,就会显示所有可用输入的完整列表。生成器的每个输入还可以显示有关其期望输入类型的附加信息。在交互式 shell 中,可以显示如下信息::
builder.code?
Type: property
String form: <property object at 0x7f04c8ce1c00>
Docstring:
"name": "code",
"required": "True"
"non_db": "False"
"valid_type": "<class 'aiida.orm.nodes.data.code.abstract.AbstractCode'>"
"help": "The Code to use for this job.",
在 Docstring
中,你将看到一个 help
字符串,其中包含有关输入端口的更详细信息。此外,它还将显示 valid_type
,定义后可显示预期的数据类型。如果定义了默认值,也会显示出来。如果提交了 process, non_db
属性将定义特定输入是否将作为正确输入 node 保存在数据库中。
通过 builder 定义输入只需为属性赋值即可。下面的示例显示了如何设置 parameters
输入以及 description
和 label
元数据输入:
builder.metadata.label = 'This is my calculation label'
builder.metadata.description = 'An example calculation to demonstrate the process builder'
builder.x = Int(1)
builder.y = Int(2)
如果对 builder
实例进行评估,只需键入变量名并点击回车键,就会显示 builder 输入端的当前值:
builder
{
'metadata': {
'description': 'An example calculation to demonstrate the process builder',
'label': 'This is my calculation label',
'options': {},
},
'x': Int<uuid='a1798492-bbc9-4b92-a630-5f54bb2e865c' unstored>,
'y': Int<uuid='39384da4-6203-41dc-9b07-60e6df24e621' unstored>
}
在本例中,您可以看到我们刚刚为 description
和 label
设置的值。此外,它还会显示任何命名空间,因为 processes 的输入支持嵌套命名空间,例如本例中的 metadata.options
命名空间。请注意,嵌套名称空间也都是自动补全的,您可以使用制表符补全功能对其进行递归遍历。
剩下的工作就是填写所有需要的输入,然后我们就可以启动 process 生成器了。为生成器定义好所有输入后,就可以实际启动 Process
。process 可以通过将构建器传递给任何一个自由函数 launch
模块来启动,就像启动普通的 process(如 described above )一样,即
from aiida import orm, plugins
from aiida.engine import submit
ArithmeticAddCalculation = plugins.CalculationFactory('core.arithmetic.add')
builder = ArithmeticAddCalculation.get_builder()
builder.x = orm.Int(1)
builder.y = orm.Int(2)
node = submit(builder)
请注意,process 生成器原则上是为在交互式 shell 中使用而设计的,因为它的制表符补全和自动输入文档功能才是真正的亮点。不过,也完全可以在脚本中使用相同的生成器,将其用作输入容器,而不是普通的 python 字典。
监测 processes#
当您启动 process 时,您可能希望调查其状态、进程和结果。 verdi 命令行工具提供了各种命令来实现这一目的。
verdi process list#
第一个入口是 verdi
命令 verdi process list
。该命令将打印数据库中存储的所有活动 processes 到 ProcessNode
的列表。典型的示例如下:
PK Created State Process label Process status
---- ---------- ------------ -------------------------- ----------------------
151 3h ago ⏵ Running ArithmeticAddCalculation
156 1s ago ⏹ Created ArithmeticAddCalculation
Total results: 2
‘State’ 列是 process_state
和 ProcessNode
的 exit_status
的连接。默认情况下,该命令只显示活动项目,即尚未达到终端状态的 ProcessNodes
。如果还想显示处于终端状态的 node,可以使用 -a
标志并调用 verdi process list -a
:
PK Created State Process label Process status
---- ---------- --------------- -------------------------- ----------------------
143 3h ago ⏹ Finished [0] add
146 3h ago ⏹ Finished [0] multiply
151 3h ago ⏵ Running ArithmeticAddCalculation
156 1s ago ⏹ Created ArithmeticAddCalculation
Total results: 4
有关 ‘state’ 列含义的更多信息,请参阅 process state 的文档。通过 -S
标志可以查询特定的 process 状态,即发出 verdi process list -S created
将返回:
PK Created State Process label Process status
---- ---------- ------------ -------------------------- ----------------------
156 1s ago ⏹ Created ArithmeticAddCalculation
Total results: 1
要查询特定的退出状态,可以使用 verdi process list -E 0
:
PK Created State Process label Process status
---- ---------- ------------ -------------------------- ----------------------
143 3h ago ⏹ Finished [0] add
146 3h ago ⏹ Finished [0] multiply
Total results: 2
通过这个简单的工具,您可以很好地了解正在运行的 processes 和已终止的 processes 的当前状态。有关所有可用选项的完整列表,请参阅 verdi process 文档。
如果要查找特定 process node 的信息,可以使用以下三个命令:
verdi process report
提供了附加到 process 的日志信息列表verdi process status
打印 process 的呼叫层级及其所有 node 的状态verdi process show
打印有关 process 的状态、输入、输出、调用者和调用者的详细信息
在下面的章节中,我们将简要说明这些命令的工作原理。为了举例说明,我们将展示 aiida-quantumespresso
插件完成的 PwBaseWorkChain
的命令输出,该插件只需调用 PwCalculation
。
verdi process report#
process 的开发人员可以通过 report()
方法将日志信息附加到 process 的 node 上。 verdi process report
命令将按时间顺序显示所有日志信息:
2018-04-08 21:18:51 [164 | REPORT]: [164|PwBaseWorkChain|run_calculation]: launching PwCalculation<167> iteration #1
2018-04-08 21:18:55 [164 | REPORT]: [164|PwBaseWorkChain|inspect_calculation]: PwCalculation<167> completed successfully
2018-04-08 21:18:56 [164 | REPORT]: [164|PwBaseWorkChain|results]: work chain completed after 1 iterations
2018-04-08 21:18:56 [164 | REPORT]: [164|PwBaseWorkChain|on_terminated]: remote folders will not be cleaned
日志信息将包括一个时间戳,之后是日志级别,始终为 REPORT
。第二个区块的格式为 pk|class name|function name
,详细描述了工作链本身的信息,以及触发消息的步骤。最后,显示报文本身。当然,process 开发人员可以自行决定记录多少消息以及这些消息有多大用处。一般来说,这些信息对用户了解 process 执行过程中发生的情况非常有用,但我们必须意识到,每个条目都存储在数据库中,因此过度使用会使数据库不必要地膨胀。
verdi process states#
该命令对 WorkChain
实例最有用,但也适用于 CalcJobs
。工作链的强大功能之一是可以调用 CalcJobs
和其他 WorkChains
来创建嵌套调用层次结构。如果要查看工作链及其调用的所有子工作链的状态, verdi process status
是首选工具。输出示例如下
PwBaseWorkChain <pk=164> [ProcessState.FINISHED] [4:results]
└── PwCalculation <pk=167> [FINISHED]
该命令打印出分层调用结构的树形表示,并一直向下递归。在本例中,只有一个 PwBaseWorkChain
调用了 PwCalculation
,缩进了一级。除了调用树之外,每个 node 还显示了其当前的 process 状态,以及工作链在大纲中的哪一步。这个工具非常有用,可以在工作链运行时检查它目前处于大纲中的哪一步,以及它调用的所有子计算的状态。
verdi process show#
最后,还有一条命令可显示 ProcessNode
的详细信息,如输入、输出以及调用或被调用的其他 processes 的可选信息。 PwBaseWorkChain
的输出示例如下:
Property Value
------------- ------------------------------------
type WorkChainNode
pk 164
uuid 08bc5a3c-da7d-44e0-a91c-dda9ddcb638b
label
description
ctime 2018-04-08 21:18:50.850361+02:00
mtime 2018-04-08 21:18:50.850372+02:00
process state ProcessState.FINISHED
exit status 0
code pw-v6.1
Inputs PK Type
-------------- ---- -------------
parameters 158 Dict
structure 140 StructureData
kpoints 159 KpointsData
pseudo_family 161 Str
max_iterations 163 Int
clean_workdir 160 Bool
options 162 Dict
Outputs PK Type
----------------- ---- -------------
output_band 170 BandsData
remote_folder 168 RemoteData
output_parameters 171 Dict
output_array 172 ArrayData
Called PK Type
-------- ---- -------------
CALL 167 PwCalculation
Log messages
---------------------------------------------
There are 4 log messages for this calculation
Run 'verdi process report 164' to see them
如果您想更详细地检查 process 的输入和输出,本概述将为您提供所有信息,因为它提供了它们的 pk。
操纵 processes#
要了解如何操作运行中的 processes,必须先了解 process/node distinction 和 process’ lifetime 的原理,因此请务必先阅读这些章节。
verdi process pause/play/kill#
verdi
命令行界面提供三条与 ‘live’ processes 交互的命令。
verdi process pause
verdi process play
verdi process kill
第一条命令是暂时中止 process,第二条命令是恢复任何已暂停的 process,第三条命令是永久关闭 process。子命令的名称似乎已经告诉了你这些,而且看起来这就是需要知道的全部内容,但其中的功能相当复杂,值得进一步解释。
正如 the distinction between the process and the node 部分所解释的,操作process意味着与运行process的运行程序内存中的process实例交互。顾名思义,这些运行程序总是运行在不同的系统 process 中,而不是你要与之交互的系统 process 中,否则,你就是运行程序,因为一个解释器中只能有一个运行程序,如果运行程序正在运行,解释器将被阻止执行任何其他操作。这意味着,要与实时 process 进行交互,就必须与运行在不同系统 process 中的另一个解释器进行交互。RabbitMQ 消息代理再次为此提供了便利。当运行程序开始运行 process 时,它还会为通过 RabbitMQ 发送给该特定 process 的传入消息添加监听器。
备注
这不仅适用于守护进程运行程序,也适用于本地运行程序。如果您在本地运行程序中启动 process,该解释器将被阻止,但它仍会在 RabbitMQ 上为该 process 设置侦听器。这意味着您可以从另一个终端操作 process,就像您操作由守护进程运行的 process 一样。
在 ‘pause’、’play’ 和 ‘kill’ 的情况中,我们正在通过 RabbitMQ 发送所谓的远程过程调用 (RPC)。该 RPC 将包括操作所针对的 process 标识符,RabbitMQ 将把它发送给注册为监听该特定 process 的人,在本例中就是运行 process 的运行程序。这立即暴露了一个潜在的问题:如果没有人监听,RPC 将被置若罔闻,这可能有多种原因。例如,正如 process’ lifetime 章节所解释的,已提交的 process 可能会出现这种情况,由于所有可用的 process 插槽都被占用,相应的任务仍在队列中。但是,即使该任务在运行程序中,它也可能因为太忙而无法响应 RPC,process 看起来也是不可达的。只要 RPC 无法访问 process,命令就会返回错误:
Error: Process<100> is unreachable
根据 process 无法访问的原因,问题可能会随着时间的推移自动解决,人们可以在稍后时间再试一次,例如在运行程序太忙无法响应的情况下。为了尽量减少这些问题,运行程序被设计为在单独的线程上进行通信,并为主线程上的任何必要操作安排回调,主线程执行所有繁重的工作。遗憾的是,我们无法轻松判断 process 无法访问的实际问题所在。如果运行程序只是无法及时响应,或者任务由于错误而意外永远丢失,问题的表现形式都是一样的,尽管这是两种完全不同的情况。
这就涉及到与 processes 交互的另一个可能不直观的方面。上一段已经顺便提到,当远程过程调用被发送时,首先需要由负责运行的运行程序(如果适用)进行响应,但它不会 直接执行 调用。这是因为调用会在通信线程上发出,而通信线程不允许直接访问 process 实例,而是会在主线程上调度回调,由主线程执行操作。不过,回调不一定会被直接执行,因为可能还有其他操作等待执行。因此,当你暂停、播放或杀死 process 时,并不是直接执行,而是 调度 一个请求来执行。如果运行程序成功接收到请求并安排了回调,命令将显示如下内容:
Success: scheduled killing Process<100>
‘scheduled’ 表示实际的删除操作可能还没有发生。这意味着,即使在调用 verdi process kill
并得到成功信息后,相应的 process 仍可能在 verdi process list
的输出中被列为活动。
默认情况下, pause
、 play
和 kill
命令只会要求运行程序确认请求已被调度,而不会实际等待命令执行。要改变这种行为,可以使用 --wait
标志来实际等待操作完成。如果 Worker 负载很重,它们可能需要一些时间来响应请求和完成命令。如果知道守护进程运行程序的负载可能很重,也可以使用 -t/--timeout
标志增加命令超时前的等待时间。
脚注
processes 应用程序接口#
现在可通过 aiida.engine.processes.control()
模块使用 verdi process
至 play
、 pause
和 kill
的功能。Processes 可分别通过 play_processes()
、 pause_processes()
和 kill_processes()
运行、暂停或终止:
from aiida.engine.processes import control
processes = [load_node(<PK1>), load_node(<PK2>)]
pause_processes(processes) # Pause the processes
play_processes(processes) # Play them again
kill_processes(processes) # Kill the processes
这些函数也使用 all_entries
关键字参数,而不是指定一个明确的 processes 列表:
pause_processes(all_entries=True) # Pause all running processes