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 |
|---|---|
|
str |
|
str |
|
value |
|
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 |
|---|---|
|
First node found or |
|
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 afind_node_from_...()variant, which raises aCGNSNodeNotFoundErrorif nothing is found (mainly useful for typing);All the
get_nodes_from_...()functions have aniter_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_labelif it ends with_t, and byget_nameotherwise.
>>> 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 functionsRestrict 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_...()meansget_node_from_...()withdepth==1get_children_from_...()meansget_nodes_from_...()withdepth==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_… functionsIf 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)
|
Node is a Zone_t described by polyedric 2D elements |
|
Node is a Zone_t described by polyedric 3D elements |
|
Label of node is either GridConnectivity_t or GridConnectivity1to1_t |
|
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 of the node is exactly equal to the provided |
|
Name of the node belongs to the provided |
|
Name of the node matches the provided |
|
Label of the node is exactly equal to the provided |
|
Label of the node belongs to the provided |
|
Label of the node matches the provided |
|
Value of the node is equal to the provided |
|
Value of the node belongs to the provided |
|
Node has a child whose name is exactly |
|
Node has a child whose label is exactly |
|
Node allows a GridLocation child, and its value matches |
|
Node has an (Additional)FamilyName_t 2 child whose value is |
|
Node is a Zone_t and its connectivity is described by |
|
Label of node is BC_t and its GridLocation value is |
|
Label of node is Elements_t and its str type (eg |
|
Label of node is GridConnectivity(1to1)_t and join is or not Abutting1to1 (resp periodic) depending of the boolean value of |
- 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_additionalisTrue, which is the default value- 3
kindandcell_dimcan be set toNone(default value) to ignore this additional criteria- 4
is_1to1andis_periocan be set toNone(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:
Return first match |
Return all matches |
Iterate all matches |
|
|---|---|---|---|
Single predicate |
|
|
|
Multiple predicates |
|
|
|
Return first match |
Return all matches |
Iterate all matches |
|
|---|---|---|---|
Single predicate |
|
|
|
Multiple predicates |
|
|
|
The following functions do not directly derive from the previous one, but allow additional usefull searches:
|
Return the node in input tree matching given path, or None |
|
Return the list of all the CGNSBase_t nodes found in input tree |
|
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): seeget_node_from_predicate()search(str): seeget_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:
depth(int or pair of int): seeget_node_from_predicate()search(str): seeget_node_from_predicate()
- 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) -> boolancestors (bool) – If
False(default), keep only the terminal node. IfTrue, 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:
depth(int or pair of int): seeget_node_from_predicate()search(str): seeget_node_from_predicate()explore(str): seeget_nodes_from_predicate()
- 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) -> boolancestors (bool) – If
False(default), keep only the terminal nodes. IfTrue, 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']