diff --git a/docs/source/working_with_aiida/groups.rst b/docs/source/working_with_aiida/groups.rst index 55bd82ebd5..dc5379b6cf 100644 --- a/docs/source/working_with_aiida/groups.rst +++ b/docs/source/working_with_aiida/groups.rst @@ -177,3 +177,116 @@ 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 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=''), + GroupPath('base1/sub_group1', cls='')] + +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='') + +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"].is_virtual + Out[10]: False + In [11]: path["base1/sub_group1"].get_group() + Out[11]: + +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]: (, 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='') + GroupPath('base1/sub_group2', cls='') + GroupPath('base2/other/sub_group3', cls='') + +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]: + 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=''), + node=) + +Finally, you can also specify the `Group` subclasses (as discussed above):: + + In [22]: from aiida.orm import UpfFamily + In [23]: path2 = GroupPath(cls=UpfFamily) + In [24]: path2["base1"].get_or_create_group() + Out[24]: (, True) + +.. important:: + + 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]: + In [26]: GroupPath("a").is_virtual + Out[26]: True + In [27]: GroupPath("a", cls=orm.UpfFamily).is_virtual + Out[27]: False