Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: add documentation for GroupPath #3952

Closed
105 changes: 105 additions & 0 deletions docs/source/working_with_aiida/groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,108 @@ This is what AiiDA uses to load the correct class when reloading the group from
assert isinstance(group, SubClassGroup)

If the entry point is not currently registered, because the corresponding plugin package is not installed for example, AiiDA will issue a warning and fallback onto the ``Group`` base class.

Group hierarchies with `GroupPath`
----------------------------------

Groups in AiiDA are inherently "flat", in that groups may only contain nodes and not other groups.
However, the `GroupPath` utility allows one to construct *virtual* group hierarchies based on delimited group labels.

`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.
For example say we have the groups::

$ 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

We can also access them as from the command-line as::

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

Or from the python interface::

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'>')]

The `GroupPath` can be constructed using indexing or "divisors"::

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

Using the `browse` attribute, you can also construct the paths as preceding attributes.
This is useful in interactive environments, whereby available paths will be shown in the tab-completion::

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

To check the existence of a path element::

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

A group may be "virtual", in which case its label does not directly relate to a group,
or the group can be retrieved with the `get_group()` method::

In [8]: path.is_virtual
Out[8]: True
In [9]: path.get_group() is None
Out[9]: True
In [10]: path["base1/sub_group1"].get_group() is None
Out[10]: True
In [11]: path["base1/sub_group1"].get_group()
Out[11]: <Group: "base1/sub_group1" [type core], of user user@email.com>
chrisjsewell marked this conversation as resolved.
Show resolved Hide resolved

Groups can be created and destroyed::

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

To traverse paths, use the `children` attribute - for recursive traversal, use `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'>')

You can also traverse directly through the nodes of a path,
optionally filtering by node class and any other filters allowed by the `QueryBuilder`::

In [17]: from aiida.orm import Data
In [18]: data = Data()
In [19]: data.set_extra("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)>)

Finally, you can also specify the `Group` subclasses (discussed above),
in which case only groups of that subclass will be recognised::

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)