如何使用数据#

Importing 数据#

AiiDA 允许用户从他们的数据库导出数据到导出档案文件,这个档案文件可以 import 到任何其他 AiiDA 数据库。如果你有一个 AiiDA 导出归档文件,你可以使用 verdi archive import 命令(详见 the reference section)。

备注

有关通过 AiiDA 存档导出和 import 数据的信息,请参阅 “How to share data”

如果你现有的数据还不是 AiiDA 导出存档的一部分,如文件、文件夹、表格数据、数组或任何其他类型的数据,本指南将告诉你如何将它们 import 导入 AiiDA。

要在 AiiDA 中存储任何数据,都需要用 Data node 进行封装,以便用 provenance graph 表示。这个 Data 类有不同的种类或子类,适用于不同类型的数据。AiiDA 内置了许多数据类型。你可以使用 verdi plugin 命令列出这些类型。执行 verdi plugin list aiida.data 应该会显示如下内容::

Registered entry points for aiida.data:
* core.array
* core.bool
* core.code
* core.dict
* core.float
* core.folder
* core.list
* core.singlefile

Info: Pass the entry point as an argument to display detailed information

正如输出结果所示,您可以通过在命令中添加名称来获取每种类型的更多信息,例如 verdi plugin list aiida.data singlefile::

Description:

The ``singlefile`` data type is designed to store a single file in its entirety.
A ``singlefile`` node can be created from an existing file on the local filesystem in two ways.
By passing the absolute path of the file:

    singlefile = SinglefileData(file='/absolute/path/to/file.txt')

or by passing a filelike object:

    with open('/absolute/path/to/file.txt', 'rb') as handle:
        singlefile = SinglefileData(file=handle)

The filename of the resulting file in the database will be based on the filename passed in the ``file`` argument.
This default can be overridden by passing an explicit name for the ``filename`` argument to the constructor.

如你所见, singlefile 类型对应于 SinglefileData 类,设计用于封装存储在本地文件系统的单个文件。如果你想在 AiiDA 中存储这样一个文件,你可以使用 verdi shell 来创建它:

SinglefileData = DataFactory('core.singlefile')
singlefile = SinglefileData(file='/absolute/path/to/file.txt')
singlefile.store()

第一步是加载与数据类型相对应的类,只需将名称(由 verdi plugin list aiida.data 列出)传递给 DataFactory 即可。然后,我们只需构建该类的实例,并将感兴趣的文件作为参数传递即可。

备注

构建任何特定数据类型实例的具体方式取决于类型。使用 verdi plugin list aiida.data <ENTRY_POINT> 命令可获取任何特定类型的更多信息。

请注意,在构建后,您将得到一个 未存储 的 node。这意味着此时您的数据尚未存储到数据库中,您可以先检查数据,也可以选择修改数据。如果您对结果满意,可以通过调用 store() 方法永久存储新数据。每个 node 在创建时都会分配一个通用唯一标识符(UUID),存储后还会分配一个主键(PK),可分别通过 node.uuidnode.pk 属性进行检索。您可以使用这些标识符来引用或检索 node。查找和检索先前已 imported 的数据的方法在第 “How to find data” 节中有描述。

如果 verdi plugin list 列出的现有数据类型都不符合您的需求,您也可以创建自己的自定义类型。详情请参阅下一节 “How to add support for custom data types”

Provenance#

虽然 AiiDA 会自动保留通过计算和 workflow 创建的 provenance 数据,但如上节所述,在手动创建数据 node 时,情况显然不是这样。通常情况下,手动创建数据发生在项目开始时,即从外部数据库获取数据作为进一步计算的起点。为了保持某种形式的 provenance,Data 基类允许记录所含数据的 _来源_ 。在构建任何类型的新数据 node 时,都可以在 source 关键字参数下传递一个包含源信息的字典:

data = Data(source={'uri': 'http://some.domain.org/files?id=12345', 'id': '12345'})

这些数据一旦存储,就可以随时通过 source 属性进行检索:

data.source   # Will return the ``source`` dictionary that was passed in the constructor, if any

以下列表显示了允许在 source 字典中设置的所有键:

  • db_name:外部数据库的名称。

  • db_uri:外部数据库的基本 URI。

  • uri:可检索数据的确切 URI。理想情况下,这是一个持久 URI。

  • id: 外部数据库中识别数据的外部 ID。

  • version:数据版本(如果有)。

  • extras:可选字典,包含其他字段的来源说明。

  • source_md5:数据的 MD5 校验和。

  • description: 关于数据来源的人可读的自由格式描述。

  • license:一个字符串,包含适用于数据的许可证类型(如果有)。

如果定义了任何其他键,构造函数将引发异常。

整理数据#

如何分组 nodes#

AiiDA 的数据库非常适合自动存储所有数据,但有时浏览这个平面数据存储可能会很棘手。为了在海量数据中创建一些秩序,你可以将nodes组*合在一起,就像你在文件系统中的文件夹中创建文件一样。在这个类比中,文件夹由 Group 类表示。每个组实例可以容纳任意数量的 node,任何 node 都可以包含在任意数量的组中。一个典型的用例是在一个组中存储所有具有共同属性的 node。

下面我们将展示如何对组执行一系列典型的操作。

创建新组#

从命令行界面

$ verdi group create test_group

从 Python 界面

In [1]: group = Group(label='test_group')

In [2]: group.store()
Out[2]: <Group: "test_group" [type core], of user xxx@xx.com>

列出可用组别#

例如

$ verdi group list

组有不同的类型,由其类型字符串表示。默认情况下,verdi group list 只显示 core 类型的组。如果要显示其他类型的组,请使用 -T/--type-string 选项。如果要显示所有类型的组,请使用 -a/--all-types 选项。

例如,要列出 core.auto 类型的组,请使用

$ verdi group list -T core.auto

同样,我们可以使用 type_string 密钥来过滤 QueryBuilder 组:

In [1]: QueryBuilder().append(Group, filters={'type_string': 'core'}).all(flat=True)
Out[1]:
[<Group: "another_group" [type core], of user xxx@xx.com>,
<Group: "old_group" [type core], of user xxx@xx.com>,
<Group: "new_group" [type core], of user xxx@xx.com>]

将 nodes 添加到群组#

创建 test_group 后,我们就可以向其中添加 node。例如,要将 pk=1 的 node 添加到组中,我们可以使用命令行界面:

$ verdi group add-nodes -G test_group 1
Do you really want to add 1 nodes to Group<test_group>? [y/N]: y

或 Python 界面:

In [1]: group.add_nodes(load_node(pk=1))

显示小组信息#

从命令行界面

$ verdi group show test_group

-----------------  ----------------
Group label        test_group
Group type_string  user
Group description  <no description>
-----------------  ----------------
# Nodes:
PK    Type    Created
----  ------  ---------------
 1    Code    26D:21h:45m ago

从一个组中删除 nodes#

从命令行界面

$ verdi group remove-nodes -G test_group 1
Do you really want to remove 1 nodes from Group<test_group>? [y/N]: y

从 Python 界面

In [1]: group = load_group(label='test_group')

In [2]: group.remove_nodes([load_node(1)])

或者,你也可以将所有 node 从组中删除。在命令行中,只需在 verdi group remove-nodes .. 中添加 -c/--clear 选项即可

$ verdi group remove-nodes -c -G test_group
Do you really want to remove ALL the nodes from Group<test_group>? [y/N]:

在 Python 界面中,您可以使用 .clear() 方法来实现同样的目标:

In [1]: group = load_group(label='test_group')

In [2]: group.clear()

重命名组#

从命令行界面

$ verdi group relabel test_group old_group
Success: Label changed to old_group

从 Python 界面

In [1]: group = load_group(label='old_group')

In [2]: group.label = 'another_group'

删除组#

从命令行界面

$ verdi group delete another_group
Are you sure to delete Group<another_group>? [y/N]: y
Success: Group<another_group> deleted.

默认情况下,任何与组相关的删除操作都不会影响 nodes 本身。例如,如果删除一个组,属于该组的 node 将保留在数据库中。如果从组中删除 node,也会发生同样的情况–它们会保留在数据库中,但不再属于该组。

如果还想删除 node,请在删除组时使用 --delete-nodes 选项:

$ verdi group delete another_group --delete-nodes

将一个组复制到另一个组#

此操作将把源组的 nodes 复制到目标组。如果目标组尚不存在,则会自动创建。

从命令行界面

$ verdi group copy source_group dest_group
Success: Nodes copied from group<source_group> to group<dest_group>

从 Python 界面

In [1]: src_group = Group.collection.get(label='source_group')

In [2]: dest_group = Group(label='destination_group').store()

In [3]: dest_group.add_nodes(list(src_group.nodes))

使用小组的示例#

在本节中,我们将举例说明如何使用组来结构化和组织数据库中的 node。

将具有相似属性的结构分组#

假设我们想把计算带隙大于 1.0 eV 的所有结构归为一个名为 promising_structures 的组,可以采用以下方法:

# Finding the structures with the bandgap > 1.0.
qb = QueryBuilder()
qb.append(StructureData,  tag='structure', project='*') # Here we are projecting the entire structure object
qb.append(CalcJobNode, with_incoming='structure', tag='calculation')
qb.append(Dict, with_incoming='calculation', filters={'attributes.bandgap': {'>': 1.0}})

# Adding the structures in 'promising_structures' group.
group = load_group(label='promising_structures')
group.add_nodes(q.all(flat=True))

备注

任何 node 都只能被包含在组中一次,如果再次添加,则会被忽略。这意味着 add_nodes 可以安全地调用多次,而且只有尚未加入组的 node 才会被加入。

使用分组数据进行进一步处理#

在此,我们将演示如何提交都属于一个名为 promising_structures 的结构组的计算结果:

# Querying the structures that belong to the 'promising_structures' group.
qb = QueryBuilder()
qb.append(Group, filters={'label': 'promising_structures'}, tag='group')
qb.append(StructureData, with_group='group')

# Submitting the simulations.
for structure in qb.all(flat=True):
    builder = SomeWorkChain.get_builder()
    builder.structure = structure
    ...
    submit(builder)

不过请注意,我们也可以使用 group.nodes 来访问组中的 node。要获得与上述相同的结果,需要执行如下操作:

group = load_group(label='promising_structures')

# Here make sure to include only structures, as group can contain any nodes.
structures = [node for node in group.nodes if isinstance(node, StructureData)]
for structure in structures:
    builder = SomeWorkChain.get_builder()
    builder.structure = structure
    ...
    submit(builder)

要查找所有属性 property_a 值小于 1,同时属于 promising_structures 组的结构,可以建立如下查询:

qb = QueryBuilder()
qb.append(Group, filters={'label': 'promising_structures'}, tag='group')
qb.append(StructureData, with_group='group', tag='structure', project='*')
qb.append(SomeWorkChain, with_incoming='structure', tag='calculation')
qb.append(Dict, with_incoming='calculation', filters={'attributes.property_a': {'<': 1}})

返回值 qb.all(flat=True) 将包含符合上述条件的所有结构。

按等级组织小组#

AiiDA 中的组本身就是 “flat” ,因为组只能包含 node,而不能包含其他组。不过,可以使用 GroupPath 工具,根据分隔的组标签构建 虚拟 组层次结构。

GroupPath is designed to work in much the same way as Python’s pathlib.Path, whereby paths are denoted by forward slash characters ‘/’ in group labels.

例如,我们有这样几个小组

$ verdi group list

PK    Label                    Type string    User
----  -----------------        -------------  --------------
1     base1/sub_group1         core           user@email.com
2     base1/sub_group2         core           user@email.com
3     base2/other/sub_group3   core           user@email.com

我们还可以通过命令行访问它们:

$ verdi group path ls -l
Path         Sub-Groups
---------  ------------
base1                 2
base2                 1
$ verdi group path ls base1
base1/sub_group1
base1/sub_group2

或从 python 界面:

In [1]: from aiida.tools.groups import GroupPath
In [2]: path = GroupPath("base1")
In [3]: print(list(path.children))
Out[3]: [GroupPath('base1/sub_group2', cls='<class 'aiida.orm.groups.Group'>'),
         GroupPath('base1/sub_group1', cls='<class 'aiida.orm.groups.Group'>')]

GroupPath 可通过索引或 ``divisors`` 构建:

In [4]: path = GroupPath()
In [5]: path["base1"] == path / "base1"
Out[5]: True

使用 browse() 属性,还可以将路径构建为前面的属性。这在交互式环境中非常有用,可用路径将显示在选项卡完成中:

In [6]: path.browse.base1.sub_group2()
Out[6]: GroupPath('base1/sub_group2', cls='<class 'aiida.orm.groups.Group'>')

检查路径元素是否存在:

In [7]: "base1" in path
Out[7]: True

组可能是 ``virtual``,在这种情况下,其标签与组没有直接关系,也可以使用 get_group() 方法检索组:

In [8]: path.is_virtual
Out[8]: True
In [9]: path.get_group() is None
Out[9]: True
In [10]: path["base1/sub_group1"].is_virtual
Out[10]: False
In [11]: path["base1/sub_group1"].get_group()
Out[11]: <Group: "base1/sub_group1" [type core], of user user@email.com>

可以创建和销毁群组:

In [12]: path["base1/sub_group1"].delete_group()
In [13]: path["base1/sub_group1"].is_virtual
Out[13]: True
In [14]: path["base1/sub_group1"].get_or_create_group()
Out[14]: (<Group: "base1/sub_group1" [type core], of user user@email.com>, True)
In [15]: path["base1/sub_group1"].is_virtual
Out[15]: False

要遍历路径,请使用 children() 属性;要进行递归遍历,请使用 walk()

In [16]: for subpath in path.walk(return_virtual=False):
    ...:     print(subpath)
    ...:
GroupPath('base1/sub_group1', cls='<class 'aiida.orm.groups.Group'>')
GroupPath('base1/sub_group2', cls='<class 'aiida.orm.groups.Group'>')
GroupPath('base2/other/sub_group3', cls='<class 'aiida.orm.groups.Group'>')

您还可以直接遍历路径上的 node,选择性地按 node 类别和 QueryBuilder 允许的任何其他过滤器进行过滤:

In [17]: from aiida.orm import Data
In [18]: data = Data()
In [19]: data.base.extras.set("key", "value")
In [20]: data.store()
Out[20]: <Data: uuid: 0adb5224-585d-4fd4-99ae-20a071972ddd (pk: 1)>
In [21]: path["base1/sub_group1"].get_group().add_nodes(data)
In [21]: next(path.walk_nodes(node_class=Data, filters={"extras.key": "value"}))
Out[21]: WalkNodeResult(group_path=GroupPath('base1/sub_group1', cls='<class 'aiida.orm.groups.Group'>'),
node=<Data: uuid: 0adb5224-585d-4fd4-99ae-20a071972ddd (pk: 1)>)

最后,您还可以指定 Group 子类(如上所述):

In [22]: from aiida.orm import UpfFamily
In [23]: path2 = GroupPath(cls=UpfFamily)
In [24]: path2["base1"].get_or_create_group()
Out[24]: (<UpfFamily: "base1" [type core.upf], of user user@email.com>, True)

重要

A GroupPath instance will only recognise groups of the instantiated cls type. The default cls is aiida.orm.Group:

In [25]: orm.UpfFamily(label="a").store()
Out[25]: <UpfFamily: "a" [type core.upf], of user user@email.com>
In [26]: GroupPath("a").is_virtual
Out[26]: True
In [27]: GroupPath("a", cls=orm.UpfFamily).is_virtual
Out[27]: False

删除数据#

默认情况下,每次你运行或提交一个新的计算,AiiDA会在数据库中为你创建新的node,而不会替换或删除数据。然而,在某些情况下,删除不再有用的node可能是有用的,例如测试运行或不正确/错误的数据和计算。对于这种情况,AiiDA 提供了 verdi node delete 命令和 delete_nodes() 函数,用于删除 provenance graph 中的 node。

小心

数据一旦删除,就无法恢复(除非你做了备份)。

重要的是,请注意,即使你只要求删除一个 node,verdi node delete 通常也会删除一些额外的链接 node,以保持 provenance graph 的一致状态。例如,如果你删除了一个计算的输入,AiiDA 也会删除计算本身(否则,你将有效地改变 provenance graph 中该计算的输入)。完整的一致性规则在 here 有详细解释。

因此:请务必检查 verdi node delete 的输出,以确保删除的内容没有超出预期。您也可以使用 verdi node delete--dry-run 标志,查看命令在不执行任何实际操作的情况下会做什么。

此外,为了确保一致性,还有一些附加规则不是强制性的,但用户可以进行切换。例如,在删除计算时,如果希望同时删除计算产生的数据,可以设置 --create-forward (使用 --no-create-forward 将只删除计算,保留输出数据:请注意,这实际上删除了输出数据的 provenance 信息)。这些标志的完整列表可以在帮助命令 verdi node delete -h 中找到。

from aiida.tools import delete_nodes
pks_to_be_deleted = delete_nodes(
    [1, 2, 3], dry_run=True, create_forward=True, call_calc_forward=True, call_work_forward=True
)

删除计算机#

要删除计算机,可以使用 verdi computer delete。如果在创建计算机后立即发现错误并想要删除它,这条命令就非常有用。特别要注意的是,如果计算机已被至少一个 node 使用过,verdi computer delete 将无法执行。在这种情况下,您需要使用 verdi node delete 先删除相应的 node。

删除可变数据#

AiiDA 中的数据子集在存储 node 之后也是可变的,方便用户对数据进行标记/分组/评论。这些数据可以随时安全删除。主要包括

完全删除 AiiDA 配置文件#

如果你不想选择性地删除一些 node,而是想完全删除整个 AiiDA 配置文件,请使用 verdi profile delete 命令。这条命令将同时删除文件库和数据库。

危险

除非以前备份过,否则无法恢复已删除的配置文件!

传输数据#

危险

该功能仍处于测试版本,其 API 在不久的将来可能会发生变化。因此,不建议在公共/生产型 workflow 中使用该功能。

此外,我们也非常欢迎对其实施情况提出反馈意见(网址:aiidateam/aiida-core#4811)。

当计算作业启动时,AiiDA 将创建一个 RemoteData node,作为输出 node 附加到标号为 remote_folder 的计算 node。CalcJob 插件生成的输入文件被复制到该远程文件夹,由于作业也在该文件夹中执行,因此代码也将在同一远程文件夹中生成输出文件。由于 RemoteData node 只明确存储远程计算机上的文件路径,而非实际内容,因此其功能或多或少类似于符号链接。这意味着,如果远程文件夹被删除,将无法检索其内容。因此,CalcJob 插件可以指定一些文件,这些文件应被 retrieved 并存储在本地的 FolderData node 中以确保安全,该文件将作为标号为 retrieved_folder 的输出附加到计算 node 中。

虽然 retrieve_list 允许指定在本地检索哪些输出文件,但这必须在提交计算之前完成。为了提供更多的灵活性来决定完成计算工作的文件在本地存储,甚至在计算结束后,AiiDA 提供了一个 TransferCalculation 插件。这个计算插件可以从远程机器获取文件并保存到本地 FolderData。拷贝内容的规格通过一个类型为

In [1]: instructions_cont = {}
    ... instructions_cont['retrieve_files'] = True
    ... instructions_cont['symlink_files'] = [
    ...     ('node_keyname', 'source/path/filename', 'target/path/filename'),
    ... ]
    ... instructions_node = orm.Dict(dict=instructions_cont)

'source/path/filename''target/path/filename' 都是相对路径(指向各自的文件夹)。node_keyname 是一个字符串,在为计算提供源 RemoteData node 时使用。您还需要提供进行传输的计算机:

In [2]: transfer_builder = CalculationFactory('core.transfer').get_builder()
    ... transfer_builder.instructions = instructions_node
    ... transfer_builder.source_nodes = {'node_keyname': source_node}
    ... transfer_builder.metadata.computer = source_node.computer

这里的变量 source_node 对应于需要检索其内容的 RemoteData node。最后,您只需运行或提交计算,就像处理其他计算一样:

In [2]: from aiida.engine import submit
    ... submit(transfer_builder)

您也可以用它将本地文件复制到新的 RemoteData 文件夹中。为此,您首先需要调整说明,将 'retrieve_files' 设置为 False,并使用 'local_files' 列表代替 'symlink_files'

In [1]: instructions_cont = {}
    ... instructions_cont['retrieve_files'] = False
    ... instructions_cont['local_files'] = [
    ...     ('node_keyname', 'source/path/filename', 'target/path/filename'),
    ... ]
    ... instructions_node = orm.Dict(dict=instructions_cont)

还需要注意的是,在这种情况下,source_node 的类型为 FolderData,因此您必须手动选择要将文件复制到哪台计算机。您可以查看运行 verdi computer list 的可用计算机,并使用所示标签将其加载到 load_computer() 中:

In [2]: transfer_builder.metadata.computer = load_computer('some-computer-label')

无论是上传还是检索,您都可以将多个文件分别添加到输入指令中的 local_filessymlink_files 键列表中,从而复制多个文件。还可以通过提供多个 source_node``s(每个都有不同的 ``'node_keyname'),从任意数量的 node 中复制文件。目标 node 将始终为一个(因此您可以在一次调用中 ``gather`` 文件,但不能 ``distribute`` 它们)。