Node searching

It is often necessary to select one or several nodes matching a specific condition in a CGNSTree. maia.pytree provides various functions to do that, depending of what is needed.

Tutorial

Almost all the functions have the following pattern:

get_node(s)_from_{predicate}(s)(root, condition, **kwargs)

root has the same meaning for all the search functions, and is simply the tree in which the search is performed. Then, by choosing appropriate keyword for {predicate} and using or not the s suffixes, lot of cases can be covered.

To illustrate the different possibilities, let’s consider the following example tree:

ZoneBC ZoneBC_t
├───BC1 BC_t
│   ├───GridLocation GridLocation_t "Vertex"
│   ├───PointRange IndexRange_t [[ 1 11] [ 1  1]]
│   └───BCDataSet BCDataSet_t "Null"
│       ├───PointRange IndexRange_t [[1 5] [1 1]]
│       └───GridLocation GridLocation_t "FaceCenter"
└───BC2 BC_t
    ├───GridLocation GridLocation_t "Vertex"
    └───PointRange IndexRange_t [[ 1 11] [ 6  6]]

Search condition

The first customizable thing is {predicate}, which indicates which criteria is applied to every node to select it or not. The condition is the concrete value of the criteria:

Function

Condition kind

get_..._from_name()

str

get_..._from_label()

str

get_..._from_value()

value

get_..._from_predicate()

function

The first functions are easy to understand: note just here that from_name and from_label accept wildcards * in their condition.

The last one is the more general and take as predicate a function, which will be applied to the nodes of the tree, and must return True (node is selected) or False (node is not selected) : f(n:CGNSTree) -> bool

Here is an example of these functions:

>>> PT.get_node_from_name(root, 'BC2')
# Return node BC2
>>> PT.get_node_from_label(root, 'BC_t')
# Return node BC1
>>> PT.get_node_from_value(root, [[1,5],[1,1]])
# Return the PointRange node located under BCDataSet
>>> PT.get_node_from_predicate(root, lambda n: 'BC' in PT.get_label(n)
...    and PT.Subset.GridLocation(n) == 'FaceCenter')
# Return node BCDataSet

More details about the general predicate case are provided in section building predicates.

See also

There is also a get_..._from_name_and_label() form, which takes two str as condition: first one is for the name, second one for the label.

>>> PT.get_node_from_name_and_label(root, '*', '*Location_t')
# Return node GridLocation of BC1

Number of results

The second customizable thing is the s after node, which can be used to decide if the search will return the first node matching the predicate, or all the nodes matching the predicate:

Function

Return

get_node_from_...()

First node found or None

get_nodes_from_...()

List of all the nodes found or []

>>> PT.get_node_from_label(root, 'BC_t')
# Return node BC1
>>> PT.get_nodes_from_label(root, 'BC_t')
# Return a list containing BC1, BC2
>>> PT.get_node_from_label(root, 'DataArray_t')
# Return None
>>> PT.get_nodes_from_label(root, 'DataArray_t')
# Return an empty list

See also

  • All the get_node_from_...() functions have a find_node_from_...() variant, which raises a CGNSNodeNotFoundError if nothing is found (mainly useful for typing);

  • All the get_nodes_from_...() functions have an iter_nodes_from_...() variant, which returns a generator instead of a list and can be used for iteration.

Chaining searches (advanced)

When looking for a particular node, it is often necessary to chain several searches. For example, if we want to select the BCData nodes under a BCDataSet node, we can do

>>> for bcds in PT.get_nodes_from_label(root, 'BCDataSet_t'):
>>>   for bcdata in PT.get_nodes_from_label(bcds, 'BCData_t'):
>>>     # Do something with bcdata nodes

This code can be reduced to a single function call by just adding a s to the search function, and changing the condition to a list :

>>> for bcdata in PT.get_nodes_from_labels(node, ['BCDataSet_t', 'BCData_t']):
>>>   # Do something with bcdata nodes

Just as before, a search will be performed starting from root, using the first condition; from the results, a second search will be performed using the second condition; and so on.

Tip

Since it can be cumbersome to write manually several predicate functions, the get_..._from_predicates() come with “autopredicate” feature: the list of functions can be replaced by a string, which is converted from the following steps:

  • String is split from separator '/'

  • Each substring is replaced by get_label if it ends with _t, and by get_name otherwise.

>>> for bcdata in PT.get_nodes_from_predicates(root, 'BCDataSet_t/BCData_t'):
>>>   # Do something wih bcdata nodes

Note that the generic versions get_..._from_predicates() expect a list of predicate functions as condition, and can thus be used to mix the kind of criteria used to compare at each level :

>>> is_bc = lambda n : PT.get_label(n) == 'BC_t' and
...                    PT.get_node_from_label(n, 'BCDataSet_t') is None
>>> is_pr = lambda n : PT.get_name(n) == 'PointRange'
>>> for pr in PT.get_nodes_from_predicates(root, [is_bc, is_pr]):
>>>   # Do something with pr nodes

All the functions allowing chained search take an additional boolean parameter ancestors. If this parameter is True, the function return tuple(s) of nodes instead of just the terminal node(s). This tuple is of size len(conditions) and contains all the intermediate results. The default value of ancestors is False.

>>> for bc, loc in PT.get_nodes_from_predicates(root,
...                                             'BC_t/GridLocation_t',
...                                             ancestors=True):
...   print(PT.get_name(bc), PT.get_value(loc))
BC1 Vertex
BC1 FaceCenter
BC2 Vertex

Fine tuning searches

Here is a selection of the most usefull kwargs accepted by the functions. See API reference for the full list.

  • depth (integer): Apply to all the functions

    Restrict the search to the nth level of children, where level 0 is the input node itself. If unset, search is not restricted.

    Tip

    Limiting the search to a single level is so common that all the functions have a specific variant to do that :

    • get_child_from_...() means get_node_from_...() with depth==1

    • get_children_from_...() means get_nodes_from_...() with depth==1

    >>> PT.get_nodes_from_label(root, 'GridLocation_t')
    # Return the 3 GridLocation nodes
    >>> PT.get_nodes_from_label(root, 'GridLocation_t', depth=2)
    # Return the 2 GridLocation nodes under the BC nodes
    
  • explore (‘shallow’ or ‘deep’): Apply to get_nodes_from_… functions

    If explore == ‘shallow’, the children of a node matching the predicate are not tested. If explore=’deep’, all the nodes are tested. Default is ‘shallow’.

    >>> PT.get_nodes_from_label(root, 'BC*_t')
    # Return nodes BC1 and BC2
    >>> PT.get_nodes_from_label(root, 'BC*_t', explore='deep')
    # Return nodes BC1, BCDataSet and BC2
    

Building predicates

As explained before, the generic form get_..._from_predicate() takes for search condition a callable having the following signature: f(n:CGNSTree) -> bool.

Although users can define their own predicate functions (eg. using lambda expressions), the pred submodule of maia.pytree facilitates the usage and the creation of predicate functions.

>>> from maia.pytree import pred as PTp

Predefined predicates

Some frequently used predicate functions are directly available in the pred module. By convention, we use uppercase letters for these object, which are callable having the relevant signature f(n:CGNSTree) -> bool, and can thus directly be used as predicate:

>>> zones = PT.get_nodes_from_predicate(tree, PTp.IS_POLY3D_ZONE)

IS_POLY2D_ZONE

Node is a Zone_t described by polyedric 2D elements

IS_POLY3D_ZONE

Node is a Zone_t described by polyedric 3D elements

IS_GC

Label of node is either GridConnectivity_t or GridConnectivity1to1_t

IS_SUBSET

Node has a PointList or PointRange child

Predicate generator

Since we can not statically define all the useful predicates function, the pred module also provides generating functions; we use lowercase letters to name these functions. They are not directly predicate function, but they return a closure having the relevant signature f(n:CGNSTree) -> bool (so a predicate function) when called:

>>> sols = PT.get_nodes_from_predicate(tree, PTp.label_is('FlowSolution_t'))

name_is(name)

Name of the node is exactly equal to the provided name

name_in(name_l)

Name of the node belongs to the provided name_l list

name_matches(name)

Name of the node matches the provided name, for which wildcard * is accepted

label_is(label)

Label of the node is exactly equal to the provided label

label_in(label_l)

Label of the node belongs to the provided label_l list

label_matches(label)

Label of the node matches the provided label, for which wildcard * is accepted

value_is(value)

Value of the node is equal to the provided value

value_in(value_l)

Value of the node belongs to the provided value_l list

has_child_of_name(child_name)

Node has a child whose name is exactly child_name

has_child_of_label(child_label)

Node has a child whose label is exactly child_label

has_location(loc)

Node allows a GridLocation child, and its value matches loc 1

belongs_to_family(family[, allow_additional])

Node has an (Additional)FamilyName_t 2 child whose value is family (wildcard accepted)

is_zone_of_kind([kind, cell_dim])

Node is a Zone_t and its connectivity is described by kind (one of S, U, Poly, Std) and cell_dim (one of 1, 2, 3) 3

is_bc_of_location(loc)

Label of node is BC_t and its GridLocation value is loc

is_element_of_type(type)

Label of node is Elements_t and its str type (eg QUAD_4) is type

is_gc_of_kind([is_1to1, is_perio])

Label of node is GridConnectivity(1to1)_t and join is or not Abutting1to1 (resp periodic) depending of the boolean value of is_1to1 (resp is_perio) 4

1

If a GridLocation is allowed, but absent, its default value is Vertex (even for BCDataSet_t nodes, despite SIDS specification)

2

AdditionalFamilyName_t nodes are considered only if allow_additional is True, which is the default value

3

kind and cell_dim can be set to None (default value) to ignore this additional criteria

4

is_1to1 and is_perio can be set to None (default value) to ignore this additional criteria

Logical operations

All predicate functions (predefined uppercase functions and return object of preficate generators) are wrapped in a NodePredicate object allowing to combine them with the logical operators AND &, OR | and NOT ~:

>>> pred = PTp.label_is('BCDataSet_t') & IS_SUBSET & ~PTp.has_location('Vertex')

We also provide the all and any functions that take a list of NodePredicate objects as input and return a single NodePredicate as output, following standard all and any logic.

>>> pred = PTp.any([PTp.has_location(f'{dir}FaceCenter') for dir in 'IJK'])

Users can wrap their callable predicate function in a NodePredicate object (to make them combinable) using the default constructor:

>>> AT_LEAST_3_CHILDREN = PTp.NodePredicate(lambda X: len(PT.get_children(X)) >= 3)
>>> pred = PTp.label_is('BC_t') & AT_LEAST_3_CHILDREN

Defining new predicate generator (advanced)

The previous exemple can be generalized to a predicate generator using again the NodePredicate constructor:

>>> def at_least_n_children(n:int) -> NodePredicate:
...   return NodePredicate(lambda X : len(PT.get_children(X)) >= n)

Alternatively, the pred module provides a decorator turning parametrized boolean functions into a predicate generator:

>>> @PTp.predicate_generator
... def check_at_least_n_children(node:CGNSTree, n:int) -> bool:
...   return len(PT.get_children(node)) >= n

Once decorated, check_at_least_n_children does no longer return a bool, but can be call with a parameter n to generate a NodePredicate. Note that as for any decorator, you can keep the original function available with at_least_n_children = PTp.predicate_generator(check_at_least_n_children).

Users are advised to gather their application specific predicates function and predicate generators in a dedicated module.

Summary

Here is an overview of the available searching functions:

Generic version of search functions

Return first match

Return all matches

Iterate all matches

Single predicate

get_node_from_predicate()

get_nodes_from_predicate()

iter_nodes_from_predicate()

Multiple predicates

get_node_from_predicates()

get_nodes_from_predicates()

iter_nodes_from_predicates()

Specialized versions of search functions

Return first match

Return all matches

Iterate all matches

Single predicate

get_node_from_name() get_node_from_label() get_node_from_value() get_node_from_name_and_label() get_child_from_name() get_child_from_label() get_child_from_value() get_child_from_name_and_label()

get_nodes_from_name() get_nodes_from_label() get_nodes_from_value() get_nodes_from_name_and_label() get_children_from_name() get_children_from_label() get_children_from_value() get_children_from_name_and_label()

iter_nodes_from_name() iter_nodes_from_label() iter_nodes_from_value() iter_nodes_from_name_and_label() iter_children_from_name() iter_children_from_label() iter_children_from_value() iter_children_from_name_and_label()

Multiple predicates

get_node_from_names() get_node_from_labels() get_node_from_values() get_node_from_name_and_labels() get_child_from_names() get_child_from_labels() get_child_from_values() get_child_from_name_and_labels()

get_nodes_from_names() get_nodes_from_labels() get_nodes_from_values() get_nodes_from_name_and_labels() get_children_from_names() get_children_from_labels() get_children_from_values() get_children_from_name_and_labels()

iter_nodes_from_names() iter_nodes_from_labels() iter_nodes_from_values() iter_nodes_from_name_and_labels() iter_children_from_names() iter_children_from_labels() iter_children_from_values() iter_children_from_name_and_labels()

The following functions do not directly derive from the previous one, but allow additional usefull searches:

get_node_from_path(root, path)

Return the node in input tree matching given path, or None

get_all_CGNSBase_t(root)

Return the list of all the CGNSBase_t nodes found in input tree

get_all_Zone_t(root)

Return the list of all the Zone_t nodes found in input tree

API reference

get_node_from_predicate(root, predicate, **kwargs)

Return the first node in input tree matching the given predicate, or None

The search can be fine-tuned with the following kwargs:

  • depth (int or pair of int): limit the search between the depths minD and maxD, 0 beeing the input node itself and None meaning unlimited. If a single int is provided, it is assigned to maxD. Defaults to (0,None).

  • search (str): use a Depth-First-Search ('dfs') or Breath-First-Search ('bfs') algorithm. Defaults to 'dfs'.

Parameters
  • root (CGNSTree) – Tree is which the search is performed

  • predicate (callable) – condition to select node, which must have the following signature: f(n:CGNSTree) -> bool

  • **kwargs – Additional options (see above)

Returns

CGNSTree or None – Node found

Note

This function admits the following shorcuts:

  • get_node_from_name|label|value|name_and_label() (embedded predicate)

  • get_child_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

get_nodes_from_predicate(root, predicate, **kwargs)

Return the list of all nodes in input tree matching the given predicate

The search can be fine-tuned with the following kwargs:

  • depth (int or pair of int): see get_node_from_predicate()

  • search (str): see get_node_from_predicate()

  • explore (str): Explore the whole tree ('deep') or stop exploring the current branch once predicate is satisfied ('shallow'). Defaults to 'shallow'.

Parameters
  • root (CGNSTree) – Tree is which the search is performed

  • predicate (callable) – condition to select node, which must have the following signature: f(n:CGNSTree) -> bool

  • **kwargs – Additional options (see above)

Returns

list of CGNSTree – Nodes found

Note

This function admits the following shorcuts:

  • get_nodes_from_name|label|value|name_and_label() (embedded predicate)

  • get_children_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

iter_nodes_from_predicate(root, predicate, **kwargs)

Iterator version of get_nodes_from_predicate()

Note

This function admits the following shorcuts:

  • iter_nodes_from_name|label|value|name_and_label() (embedded predicate)

  • iter_children_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

get_node_from_predicates(root, predicates, ancestors=False, **kwargs)

Return the first node in input tree matching the chain of predicates, or None

The search can be fine-tuned with the following kwargs:

Parameters
  • root (CGNSTree) – Tree is which the search is performed

  • predicates (list of callable) – conditions to select next node, each one having the following signature: f(n:CGNSTree) -> bool

  • ancestors (bool) – If False (default), keep only the terminal node. If True, keep the intermediate nodes and return a tuple of nodes instead of a single node.

  • **kwargs – Additional options (see above)

Returns

CGNSTree or None – Node found

Note

This function admits the following shorcuts:

  • get_node_from_names|labels|values|name_and_labels() (embedded predicate)

  • get_child_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

get_nodes_from_predicates(root, predicates, ancestors=False, **kwargs)

Return the list of all nodes in input tree matching the chain of predicates

The search can be fine-tuned with the following kwargs:

Parameters
  • root (CGNSTree) – Tree is which the search is performed

  • predicates (list of callable) – conditions to select next node, each one having the following signature: f(n:CGNSTree) -> bool

  • ancestors (bool) – If False (default), keep only the terminal nodes. If True, keep the intermediate nodes and return a list of tuples of nodes instead of a list of nodes.

  • **kwargs – Additional options (see above)

Returns

list of CGNSTree – Nodes found

Note

This function admits the following shorcuts:

  • get_nodes_from_names|labels|values|name_and_labels() (embedded predicate)

  • get_children_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

iter_nodes_from_predicates(root, predicates, ancestors=False, **kwargs)

Iterator version of get_nodes_from_predicates()

Note

This function admits the following shorcuts:

  • iter_nodes_from_names|labels|values|name_and_labels() (embedded predicate)

  • iter_children_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

get_node_from_path(root, path)

Return the node in input tree matching given path, or None

A path is a str containing a full list of names, separated by '/', leading to the node to select. Root name should not be included in path. Wildcards are not accepted in path.

Parameters
  • root (CGNSTree) – Tree is which the search is performed

  • path (str) – path of the node to select

Returns

CGNSTree or None – Node found

Example

>>> zone = PT.yaml.to_node('''
... Zone Zone_t:
...   ZoneBC ZoneBC_t:
...     BC BC_t "Null":
...       GridLocation GridLocation_t "Vertex":
... ''')
>>> node = PT.get_node_from_path(zone, 'ZoneBC/BC/GridLocation')
>>> PT.get_name(node) # Node is returned
'GridLocation'
>>> PT.get_node_from_path(zone, 'ZoneBC/BC/PointRange') # Returns None
get_all_CGNSBase_t(root)

Return the list of all the CGNSBase_t nodes found in input tree

This function is SIDS aware, and will only search nodes in relevant places.

Parameters

root (CGNSTree) – Tree is which the search is performed

Returns

list of CGNSTree – Nodes found

See also

This function has the iterator counterpart iter_all_CGNSBase_t()

Example

>>> tree = PT.yaml.to_cgns_tree('''
... BaseA CGNSBase_t:
...   Zone1 Zone_t:
...   Zone2 Zone_t:
... BaseB CGNSBase_t:
...   Zone3 Zone_t:
... ''')
>>> [PT.get_name(n) for n in PT.get_all_CGNSBase_t(tree)]
['BaseA', 'BaseB']
get_all_Zone_t(root)

Return the list of all the Zone_t nodes found in input tree

This function is SIDS aware, and will only search nodes in relevant places.

Parameters

root (CGNSTree) – Tree is which the search is performed

Returns

list of CGNSTree – Nodes found

See also

This function has the iterator counterpart iter_all_Zone_t()

Example

>>> tree = PT.yaml.to_cgns_tree('''
... BaseA CGNSBase_t:
...   Zone1 Zone_t:
...   Zone2 Zone_t:
... BaseB CGNSBase_t:
...   Zone3 Zone_t:
... ''')
>>> [PT.get_name(n) for n in PT.iter_all_Zone_t(tree)]
['Zone1', 'Zone2', 'Zone3']