Tree operations
This page describes some maia.pytree features that operate on whole CGNSTrees, such as
copying, displaying of comparing trees.
These features are less CGNS SIDS-aware than the one listed
in Node inspection or Node creation presets pages.
Tree construction
Copy
The following functions return a copy of the input tree:
- shallow_copy(t)
Create a shallow copy of the input tree.
Values of the nodes are not copied, but only known as a shared referenced by the output tree. Other data, such as names, labels and tree structure are truly copied.
- Parameters
t (CGNSTree) – Input tree
- Returns
CGNSTree – Copied tree
Example
>>> zone = PT.new_Zone(type='Unstructured', size=[[9,4,0]]) >>> zone_dupl = PT.shallow_copy(zone) >>> zone_dupl[1] *= 2 >>> PT.get_value(zone) array([[18, 8, 0]], dtype=int32)
- deep_copy(t)
Create a deep copy of the input tree.
Values of the nodes are copied, and both trees consequently do not share any reference.
- Parameters
t (CGNSTree) – Input tree
- Returns
CGNSTree – Copied tree
Example
>>> zone = PT.new_Zone(type='Unstructured', size=[[9,4,0]]) >>> zone_dupl = PT.deep_copy(zone) >>> zone_dupl[1] *= 2 >>> PT.get_value(zone) array([[9, 4, 0]], dtype=int32)
Logical operations
The following functions construct a new tree from logical operations:
- union(t1, t2)
Create a new tree from the union of the input trees.
Nodes existing on both input trees keeps the value and label of
t1. Note also that output values are shared references to input trees. Usesdeep_copy()afterwards if you want an independant copy.Important
Input root nodes must have the same name. An exception will be raised otherwise.
- Parameters
t1 (CGNSTree) – First CGNS node
t2 (CGNSTree) – Second CGNS node
- Returns
CGNSTree – Tree created from union
Example
>>> tree1 = PT.yaml.to_node(''' ... Base CGNSBase_t: ... Zone1 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone3": ... Zone2 Zone_t: ... ''') >>> tree2 = PT.yaml.to_node(''' ... Base CGNSBase_t: ... Zone2 Zone_t: ... Zone3 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone1": ... ''') >>> tree = PT.union(tree1, tree2) >>> PT.print_tree(tree) Base CGNSBase_t ├───Zone1 Zone_t │ └───ZoneGridConnectivity ZoneGridConnectivity_t │ └───match GridConnectivity1to1_t "Zone3" ├───Zone2 Zone_t └───Zone3 Zone_t └───ZoneGridConnectivity ZoneGridConnectivity_t └───match GridConnectivity1to1_t "Zone1"
- intersection(t1, t2, comp_func=<function is_same_node>)
Create a new tree from the intersection of the input trees.
At each tree level, input nodes are considered equal if the binary predicate
comp_func(n1, n2)returnsTrue. If not provided, the functionis_same_node()is used.If the intersection is empty, result contains only the root node. Note also that output values are shared references to input tree
t1. Usesdeep_copy()afterwards if you want an independant copy.Important
Input root nodes must have the same name. An exception will be raised otherwise.
- Parameters
t1 (CGNSTree) – First CGNS node
t2 (CGNSTree) – Second CGNS node
comp_func (Callable) – Binary predicate used for comparison (see above)
- Returns
CGNSTree – Tree created from intersection
Example
>>> tree1 = PT.yaml.to_cgns_tree(''' ... CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2]: ... Base CGNSBase_t: ... Zone1 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone3": ... Zone2 Zone_t: ... ''') >>> tree2 = PT.yaml.to_cgns_tree(''' ... CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2]: ... Base CGNSBase_t: ... Zone2 Zone_t: ... Zone3 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone1": ... ''') >>> tree = PT.intersection(tree1, tree2) >>> PT.print_tree(tree) CGNSTree CGNSTree_t ├───CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2] └───Base CGNSBase_t └───Zone2 Zone_t
- difference(t1, t2, comp_func=<function is_same_node>)
Create a new tree from the difference of the input trees.
At each tree level, input nodes are considered equal if the binary predicate
comp_func(n1, n2)returnsTrue. If not provided, the functionis_same_node()is used.This operation is not symmetric. Ouput contains nodes belonging only to first input. Note also that output values are shared references to input tree
t1. Usesdeep_copy()afterwards if you want an independant copy.Important
Input root nodes must have the same name. An exception will be raised otherwise.
- Parameters
t1 (CGNSTree) – First CGNS node
t2 (CGNSTree) – Second CGNS node
comp_func (Callable) – Binary predicate used for comparison (see above)
- Returns
CGNSTree – Tree created from difference
Example
>>> tree1 = PT.yaml.to_cgns_tree(''' ... CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2]: ... Base CGNSBase_t: ... Zone1 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone3": ... Zone2 Zone_t: ... ''') >>> tree2 = PT.yaml.to_cgns_tree(''' ... CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2]: ... Base CGNSBase_t: ... Zone2 Zone_t: ... Zone3 Zone_t: ... ZoneGridConnectivity ZoneGridConnectivity_t: ... match GridConnectivity1to1_t "Zone1": ... ''') >>> tree = PT.difference(tree1, tree2) >>> PT.print_tree(tree) CGNSTree CGNSTree_t └───Base CGNSBase_t └───Zone1 Zone_t └───ZoneGridConnectivity ZoneGridConnectivity_t └───match GridConnectivity1to1_t "Zone3"
Tree editing
Removing nodes
Functions removing nodes reuses the concept of predicate described in the Node searching page, which we advise users to read in first place.
However, there is much less variability and options when using remove functions:
there is no equivalent of chaining searches (no predicates version), and
the only additional parameter is the depth until which nodes are inspected for suppression.
In addition, only the variant removing all the matches is provided,
leading to the following generic function:
- rm_nodes_from_predicate(root, predicate, **kwargs)
Remove all the nodes in the input tree matching the given predicate.
The search can be fine-tuned with the following kwargs:
depth(int): Stop exploring nodes oncedepthis reached (0 beeing the node itself, andNonemeaning unlimited). Defaults toNone.
- Parameters
root (CGNSTree) – Tree in which nodes are removed
predicate (callable) – condition to remove nodes, which must have the following signature:
f(n:CGNSTree) -> bool**kwargs – Additional options (see above)
Example
>>> zone = PT.yaml.to_node(''' ... Zone Zone_t: ... FamilyName FamilyName_t 'ROW1': ... ZoneBC ZoneBC_t: ... bc1 BC_t: ... FamilyName FamilyName_t 'BC1': ... Index_i IndexArray_t: ... bc2 BC_t: ... FamilyName FamilyName_t 'BC2': ... Index_ii IndexArray_t: ... ''') >>> PT.rm_children_from_label(zone, 'FamilyName_t') >>> len(PT.get_nodes_from_label(zone, 'FamilyName_t')) 2 >>> PT.rm_nodes_from_label(zone, 'FamilyName_t') >>> len(PT.get_nodes_from_label(zone, 'FamilyName_t')) 0
Note
This function admits the following shorcuts:
rm_nodes_from_name(),rm_nodes_from_label(),rm_nodes_from_value(),rm_nodes_from_name_and_label()(embedded predicate)rm_children_from_name(),rm_children_from_label(),rm_children_from_value(),rm_children_from_name_and_label()(embedded predicate + depth=1)
For convenience, we also provide the keep_children_from_predicate()
from which remove the children nodes that do not match the predicate.
Note that unlike rm_nodes_from_predicate(), this function is limited
to the first level of children.
- keep_children_from_predicate(root, predicate)
Remove all the children of root node expect the ones matching the given predicate.
- Parameters
root (CGNSTree) – Tree in which nodes are removed
predicate (callable) – condition to keep nodes, which must have the following signature:
f(n:CGNSTree) -> bool
Example
>>> zone = PT.yaml.to_node(''' ... Zone Zone_t: ... FamilyName FamilyName_t 'ROW1': ... ZoneBC ZoneBC_t: ... bc1 BC_t: ... FamilyName FamilyName_t 'BC1': ... Index_i IndexArray_t: ... bc2 BC_t: ... FamilyName FamilyName_t 'BC2': ... Index_ii IndexArray_t: ... ''') >>> PT.keep_children_from_label(zone, 'FamilyName_t') >>> PT.print_tree(zone) Zone Zone_t └───FamilyName FamilyName_t "ROW1"
Note
This function admits the following shorcuts:
keep_children_from_name(),keep_children_from_label(),keep_children_from_value(),keep_children_from_name_and_label()(embedded predicate)
When the node to remove is known, it is also possible to directly remove it from its path:
- rm_node_from_path(root, path)
Remove the node in input tree matching the given path.
A path is a str containing a full list of names, separated by
'/', leading to the node to remove. Root name should not be included in path. Wildcards are not accepted in path.- Parameters
root (CGNSTree) – Tree in which the search is performed
path (str) – path of the node to remove
Example
>>> zone = PT.new_Zone('Zone') >>> PT.new_FlowSolution('FS', fields={'Density' : [1.], 'Temperature' : [273.]}, parent=zone) >>> PT.rm_node_from_path(zone, 'FS/Density') >>> PT.print_tree(zone) Zone Zone_t ├───ZoneType ZoneType_t "Null" └───FS FlowSolution_t └───Temperature DataArray_t R4 [273.]
See also
Also exists as
pop_node_from_path(), which removes the node and returns it.
Tree comparisons
maia.pytree offers two ways to compare trees. We can either simply check
if two trees are identical or not with the functions
is_same_node() and is_same_tree(),
or get a full report of differences using diff_tree().
The later also allow users to define their own comparison function
for data arrays.
Checking trees equality
- is_same_node(node1, node2, abs_tol=0, type_tol=False)
Compare two nodes.
Nodes are considered equal if they have the same name, label and value. Note that no check is performed on their children.
- Parameters
t1 (CGNSTree) – first tree
t2 (CGNSTree) – second tree
abs_tol (float) – absolute tolerance used for value comparison, performed by
np.allclosefunctiontype_tol (bool) – if True, allow comparaison of compatible but different types (I4/I8 or R4/R8). Otherwise, nodes are considered to differ.
- Returns
bool – True if nodes are identical
Example
>>> zone1 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='ROTOR') >>> zone2 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='STATOR') >>> PT.is_same_node(zone1, zone2) True
- is_same_tree(t1, t2, abs_tol=0, type_tol=False)
Compare recursively two trees.
Trees are considered equal if they recursively have the same children (order does not matters), in the sense of
is_same_node().See
is_same_node()for arguments description.- Returns
bool – True if trees are identical
Example
>>> zone1 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='ROTOR') >>> zone2 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='STATOR') >>> PT.is_same_tree(zone1, zone2) False
Reporting trees differences
- diff_tree(t1, t2, strict_value_type=True, comp=None)
Report the differences between two trees.
This function is similar to
is_same_tree(), but returns a full report of differences between the two input trees. In addition, it is possible to provide a custom comparison function for numerical arrays or to choose one in the following list:maia.pytree.compare.EqualArray: two arrays are equal if all their elements are one-to-one exactly equal. This is the default comparison method.maia.pytree.compare.CloseArray: two arrays are equal if all their elements are one-to-one close up to a given tolerance.
- Parameters
t1 (CGNSTree) – first tree
t2 (CGNSTree) – second tree
strict_value_type (bool) – if True, allow comparaison of compatible but different types (I4/I8 or R4/R8). Otherwise, nodes are considered to differ.
comp – comparison function to check the value of nodes (see above)
- Returns
(
DiffReport) – Difference report. First value indicates if trees are identical, second and third store the differences between trees, encoded as strings (respectivly errors and warnings).
Example
>>> zone1 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='ROW1') >>> zone2 = PT.new_Zone(type='Unstructured', size=[[9,4,0]], family='ROW2') >>> PT.diff_tree(zone1, zone2) DiffReport(status=False, errors='/Zone/FamilyName -- Values differ: ROW1 <> ROW2\n', warnings='')
Comparison methods
The following comparison methods are exposed in the maia.pytree.compare module:
- EqualArray()
A callable object generating a report for
diff_tree(), using an exact point-to-point comparison.Example
>>> sol1 = PT.new_FlowSolution(fields={'Density' : [1., 1.002, 1.]}) >>> sol2 = PT.new_FlowSolution(fields={'Density' : [1., 1.001, 1.]}) >>> comp = PT.compare.EqualArray() >>> PT.diff_tree(sol1, sol2, comp=comp) DiffReport( status=False, errors='/FlowSolution/Density -- Values differ: [1. 1.002 1. ] <> [1. 1.001 1. ]\n', warnings='' )
- CloseArray(rtol=1e-05, atol=1e-08)
A callable object generating a report for
diff_tree(), using a point-to-point with tolerance comparison (see np.isclose documentation).- Parameters
rtol (float) – relative tolerance
atol (float) – absolute tolerance
Example
>>> sol1 = PT.new_FlowSolution(fields={'Density' : [1., 1.002, 1.]}) >>> sol2 = PT.new_FlowSolution(fields={'Density' : [1., 1.001, 1.]}) >>> comp = PT.compare.CloseArray(rtol=0, atol=1E-2) >>> PT.diff_tree(sol1, sol2, comp=comp) DiffReport(status=True, errors='', warnings='')
To define a custom comparison method, users must provide a callable object comparing two
nodes. Note that this method will only be called on nodes having numerics (excluding str)
values of same shape, since diff_tree() will report a difference otherwise.
This callback must have the following signature:
def __call__(nodes_1: List[CGNSTree],
nodes_2: List[CGNSTree]) -> Tuple[bool,str,str]:
where nodes_1 and nodes_2 are the two nodes on which comparison is performed,
stacked with their ancestors.
As an example, here is a dummy comparison method definition:
class CustomComparator:
def __call__(self, nodes_1, nodes_2):
if 'BC_t' in [PT.get_label(n) for n in nodes_1]:
return (True, '', '') # If node is below a BC, considerer it's ok
else:
return (False, 'Uncomparable value\n', '')
Comparison reports
Comparison reports are named tuple made of 3 values:
- class DiffReport(status, errors, warnings)
Stores the output of
diff_tree()- Parameters
status (bool) –
Trueif trees are identicalerrors (str) – differences between the two trees
warnings (str) – minor differences between the two trees
The choice to write in errors or warning report is let to the comparison object.
To illustrate the content of a DiffReport, consider the following example:
>>> zone1 = PT.yaml.to_node('''
Zone Zone_t I4 [[9,4,0]]:
ZoneType ZoneType_t "Unstructured":
FlowSolution FlowSolution_t:
GridLocation GridLocation_t "CellCenter":
Density DataArray_t [1., 1, 1, 1]:
Pressure DataArray_t [1E5, 1E5, 1E5, 1E5]:
SomeData ZoneSubRegion_t:
PointList IndexArray_t [2,4,6,8]:
Descriptor Descriptor_t "Even vtx":
''')
>>> zone2 = PT.yaml.to_node('''
Zone Zone_t I8 [[9,4,0]]:
ZoneType ZoneType_t "Unstructured":
FamilyName FamilyName_t "ROTOR":
FlowSolution FlowSolution_t:
GridLocation GridLocation_t "CellCenter":
Density DataArray_t [1., 1.1, 1, 1]:
SomeData DiscreteData_t:
PointList IndexArray_t [1,3,5,7,9]:
Descriptor Descriptor_t "Odd vtx":
''')
>>> report = PT.diff_tree(zone1, zone2)
Printing the errors report with >>> print(report.errors) gives
/Zone -- Value types differ: I4 <> I8
/Zone/FlowSolution/Density -- Values differ: [1. 1. 1. 1.] <> [1. 1.1 1. 1. ]
< /Zone/FlowSolution/Pressure
/Zone/SomeData -- Labels differ: ZoneSubRegion_t <> DiscreteData_t
/Zone/SomeData/Descriptor -- Values differ: Even vtx <> Odd vtx
/Zone/SomeData/PointList -- Value shape differ: (4,) <> (5,)
> /Zone/FamilyName
Following diff convention, symbols < (resp. >) are used to mark nodes
existing only in first (resp. second) tree.
Nodes existing in both trees, but with different labels or values are shown using
the following pattern : {path} -- {diff_kind}: {details}.
If the <> symbol is used to display details,
the value to its left (resp. right) corresponds to the first (resp. second) input node.
Note
Users implementing their own comp object must only write the equivalent
of {details} in the report. Other data will be insered automatically.