15 traversal: str =
"bfs_finest_first",
16 enable_propagate_bottom_up: bool =
False,
17 enable_optimize_hierarchy: bool =
False,
18 require_all_children_for_hierarchy_optimization: bool =
True,
19 periodic_BC: list[bool] |
None =
None,
22 verbose: bool =
False,
25 Split a tree using a staged load-balancing workflow.
28 1. distribute_leaves_bottom_up() or distribute_leaves_top_down(): Redistribute leaf nodes across subtrees.
29 2. _propagate_bottom_up(): Propagate subtree ownership to internal nodes.
30 3. _optimize_hierarchy() (optional): Further reduce split complexity by
31 promoting complete child sets up the hierarchy.
32 4. create_constructor_initialisation_string() (optional): Derive full
33 hardcoded constructor configuration from the transformed tree.
36 peano_tree: Tree to split (modified in place).
37 number_of_subtrees: Total number of subtrees to create.
38 traversal: Stage I traversal strategy. Supported values:
39 - "bfs": level-order leaf collection
40 - "bfs_finest_first": level-order leaf collection within each level,
41 but levels processed from finest to coarsest
42 - "dfs": depth-first leaf collection
43 enable_propagate_bottom_up: If True, enable Stage II bottom-up subtree
44 propagation after leaf redecomposition.
45 enable_optimize_hierarchy: If True, enable Stage III hierarchical
46 optimisation to minimise split operations through hierarchy lifting.
47 periodic_BC: One flag per axis. Peano never hands over cells along a
48 periodic boundary, so these cells stay on tree 0. If you pass the
49 periodic boundary conditions of your experiment, Stage I keeps
50 the leaves along periodic boundaries on tree 0, too, and counts
51 them towards tree 0's share. None means no periodic boundaries.
52 require_all_children_for_hierarchy_optimization: Stage III guard for
53 parent promotion. If True (default), a parent is only considered if
54 all 3^d children exist. If False, Stage III may also promote mixed
55 parents with partial child sets.
56 enable_extract_split_instructions: If True, run Stage IV and return the
57 derived `(hardcoded_config, hardcoded_mode)`.
58 destination_ranks: Optional destination rank list for Stage IV. If not
59 provided, it defaults to target subtree IDs.
60 verbose: If True, Stage IV prints extracted hardcoded parameters.
63 - All stages after Stage I are optional.
64 - The tree is modified in place by the enabled transformation stages.
65 - If number_of_subtrees <= 1, no transformation stage runs.
66 - Stage III requires Stage II to have been applied first for correctness.
69 @todo Dmitry We have to update the docu once we have converged the codes.
70 So this can wait, but it remains a todo. Also, there has to
71 be a different way to split up the leaves:
73 @todo Dmitry At the moment, I do not see how you determine how many cells
74 you assign to each tree? I assume there's a helper somewhere.
75 I'm not sure if we also want to have splittings, where we can
76 configure the min number of cells per tree. Not sure how to
77 best inject that, i.e. will need your advise.
81 traversal_mode = traversal.lower()
82 if traversal_mode ==
"bfs":
84 peano_tree, decomposition=decomposition, finest_level_first=
False, periodic_BC=periodic_BC
86 elif traversal_mode ==
"bfs_finest_first":
88 peano_tree, decomposition=decomposition, finest_level_first=
True, periodic_BC=periodic_BC
90 elif traversal_mode ==
"dfs":
92 peano_tree, decomposition=decomposition, periodic_BC=periodic_BC
96 f
"Unsupported traversal '{traversal}'. Use 'bfs', 'bfs_finest_first' or 'dfs'."
99 if not has_leaves_to_split:
100 return no_split_result(
"No leaf nodes found in subtree 0 to split.")
105 if enable_optimize_hierarchy:
108 require_all_children=require_all_children_for_hierarchy_optimization,
113 peano_tree._subtree_count = len(decomposition)
114 peano_tree.update_split_pattern()
117 print(f
"Splitting complete. Subtree count: {peano_tree._subtree_count}")
118 print(f
"Leaf decomposition: {moved_nodes}")
119 print(f
"Split pattern: {peano_tree._split_pattern}")
208 finest_level_first: bool =
False,
209 periodic_BC: list[bool] |
None =
None,
210) -> tuple[bool, list[int]]:
212 Stage I: BFS-style leaf redecomposition over Peano SFC order.
214 This strategy runs through the mesh level by level. It first assigns all
215 leaves on the finest level to partitions, then the leaves on the next
216 coarser level, and so forth. Within each level, cells are visited in
217 Peano space-filling-curve order, so the redecomposition follows the same
218 ordering as the SFC traversal used elsewhere in the load-balancing API.
220 @image html PeanoSFC.png
222 The goal is to keep the finest-mesh cells contiguous while respecting the
223 hierarchical tree structure. In practice, that makes the routine well
224 suited for solvers that spend most of their work on the finest cells, such
225 as limiter-based or multigrid-style workflows.
227 The lexicographic order that used to be documented here now lives in
228 `strategies/Lexicographic.py`; this function intentionally uses Peano SFC
232 - Any existing cell on the finest level, or
233 - Any existing coarser-level cell with no children on the next level.
236 peano_tree: Tree to update (modified in place).
237 number_of_subtrees: Total number of subtrees to create.
238 finest_level_first: If `False` (default), process levels from coarsest
239 to finest (`0..max_level-1`). If `True`, process levels from finest
240 to coarsest while keeping BFS order inside each level.
243 A tuple `(has_leaves_to_split, moved_nodes_per_subtree)`:
244 - `has_leaves_to_split`: `False` if no eligible leaves were found.
245 - `moved_nodes_per_subtree`: Per-subtree counts of reassigned leaves.
248 - Only leaves in subtree 0 are redistributed.
249 - Node existence flags (`weight`) are preserved.
250 - This stage does not update non-leaf assignments.
257 level_range = range(peano_tree._max_level)
258 for level
in level_range:
259 size = 3**(level + 1)
261 if peano_tree._dimension == 2:
262 for peano_index
in range(size * size):
263 a, b = Peano_to_Cartesian_2d(peano_index, size)
264 current_node = peano_tree._tree_arrays[level][a][b]
265 if current_node[0] == 1
and current_node[1] == 0:
269 if level == peano_tree._max_level - 1:
274 child_a, child_b = 3*a, 3*b
275 if (child_a < 3**(level+2)
and child_b < 3**(level+2)
and
276 peano_tree._tree_arrays[level+1][child_a][child_b][0] == 0):
280 leaf_nodes.append((level, a, b))
282 elif peano_tree._dimension == 3:
283 for peano_index
in range(size * size * size):
284 a, b, c = Peano_to_Cartesian_3d(peano_index, size)
285 current_node = peano_tree._tree_arrays[level][a][b][c]
286 if current_node[0] == 1
and current_node[1] == 0:
290 if level == peano_tree._max_level - 1:
295 child_a, child_b, child_c = 3*a, 3*b, 3*c
296 if (child_a < 3**(level+2)
and child_b < 3**(level+2)
and child_c < 3**(level+2)
and
297 peano_tree._tree_arrays[level+1][child_a][child_b][child_c][0] == 0):
301 leaf_nodes.append((level, a, b, c))
303 moved_nodes =
_assign_leaves(peano_tree, leaf_nodes, decomposition, periodic_BC)
305 return (
True, moved_nodes)
310 periodic_BC: list[bool] |
None =
None,
311 ) -> tuple[bool, list[int]]:
313 Stage I: DFS-style leaf redecomposition over the Peano SFC.
315 Collect leaf cells currently assigned to subtree 0 and redistribute them
316 across `number_of_subtrees` subtrees as evenly as possible.
317 The redecomposition logic is identical to `distribute_leaves_bottom_up()`, but leaf
318 discovery order follows DFS instead of level-order scanning. Both component-root
319 discovery and per-parent child traversal use Peano SFC order.
322 peano_tree: Tree to update (modified in place).
323 number_of_subtrees: Total number of subtrees to create.
326 A tuple `(has_leaves_to_split, moved_nodes_per_subtree)`:
327 - `has_leaves_to_split`: `False` if no eligible leaves were found.
328 - `moved_nodes_per_subtree`: Per-subtree counts of reassigned leaves.
330 Both return values can be ignored if you want to.
334 max_level = peano_tree._max_level
336 def _node_exists(level: int, a: int, b: int, c: int |
None =
None) -> bool:
337 if peano_tree._dimension == 2:
338 return peano_tree._tree_arrays[level][a][b][0] == 1
339 return peano_tree._tree_arrays[level][a][b][c][0] == 1
341 def _node_subtree(level: int, a: int, b: int, c: int |
None =
None) -> int:
342 if peano_tree._dimension == 2:
343 return peano_tree._tree_arrays[level][a][b][1]
344 return peano_tree._tree_arrays[level][a][b][c][1]
346 def _children_2d(level: int, a: int, b: int) -> tuple[bool, list[tuple[int, int]]]:
347 if level == max_level - 1:
351 has_existing_children =
False
352 for peano_index
in range(9):
353 local_a, local_b = Peano_to_Cartesian_2d(peano_index, 3)
354 child_a = 3 * a + local_a
355 child_b = 3 * b + local_b
356 if _node_exists(level + 1, child_a, child_b):
357 has_existing_children =
True
358 if _node_subtree(level + 1, child_a, child_b) == 0:
359 children.append((child_a, child_b))
361 return (has_existing_children, children)
363 def _children_3d(level: int, a: int, b: int, c: int) -> tuple[bool, list[tuple[int, int, int]]]:
364 if level == max_level - 1:
368 has_existing_children =
False
369 for peano_index
in range(27):
370 local_a, local_b, local_c = Peano_to_Cartesian_3d(peano_index, 3)
371 child_a = 3 * a + local_a
372 child_b = 3 * b + local_b
373 child_c = 3 * c + local_c
374 if _node_exists(level + 1, child_a, child_b, child_c):
375 has_existing_children =
True
376 if _node_subtree(level + 1, child_a, child_b, child_c) == 0:
377 children.append((child_a, child_b, child_c))
379 return (has_existing_children, children)
381 def _dfs_2d(level: int, a: int, b: int) ->
None:
387 has_existing_children, children = _children_2d(level, a, b)
388 if not has_existing_children:
389 leaf_nodes.append((level, a, b))
392 for child_a, child_b
in children:
393 _dfs_2d(level + 1, child_a, child_b)
395 def _dfs_3d(level: int, a: int, b: int, c: int) ->
None:
396 key = (level, a, b, c)
401 has_existing_children, children = _children_3d(level, a, b, c)
402 if not has_existing_children:
403 leaf_nodes.append((level, a, b, c))
406 for child_a, child_b, child_c
in children:
407 _dfs_3d(level + 1, child_a, child_b, child_c)
412 for level
in range(max_level):
413 size = 3 ** (level + 1)
415 if peano_tree._dimension == 2:
416 for peano_index
in range(size * size):
417 a, b = Peano_to_Cartesian_2d(peano_index, size)
418 if not _node_exists(level, a, b):
420 if _node_subtree(level, a, b) != 0:
423 is_component_root = level == 0
424 if not is_component_root:
425 pa, pb = a // 3, b // 3
426 parent_exists = _node_exists(level - 1, pa, pb)
427 parent_subtree0 = parent_exists
and _node_subtree(level - 1, pa, pb) == 0
428 is_component_root =
not parent_subtree0
430 if is_component_root:
433 elif peano_tree._dimension == 3:
434 for peano_index
in range(size * size * size):
435 a, b, c = Peano_to_Cartesian_3d(peano_index, size)
436 if not _node_exists(level, a, b, c):
438 if _node_subtree(level, a, b, c) != 0:
441 is_component_root = level == 0
442 if not is_component_root:
443 pa, pb, pc = a // 3, b // 3, c // 3
444 parent_exists = _node_exists(level - 1, pa, pb, pc)
446 parent_exists
and _node_subtree(level - 1, pa, pb, pc) == 0
448 is_component_root =
not parent_subtree0
450 if is_component_root:
451 _dfs_3d(level, a, b, c)
453 moved_nodes =
_assign_leaves(peano_tree, leaf_nodes, decomposition, periodic_BC)
455 return (
True, moved_nodes)
461 Propagate information bottom-up
463 This correponds to the second stage of the staged load-balancing workflow.
465 Propagate subtree assignments from leaves to internal nodes in a bottom-up pass.
466 For each existing non-leaf node, if all existing children belong to the same
467 subtree, assign the parent to that subtree.
471 We work from finest to the coarsest level. On each level, we trigger an
472 iterative process (while loop) for each colour (tree number) `k` that we
475 ### Check if all kids of one cell belong to the same tree
477 For a cell on level `level`, we run over all 3^d children (counter `child_a`
478 and `child_b`). There is a set, and we add all the child tree numbers to this
479 set. Once we have visited all children, we can check the set cardinality:
480 If the set cardinality equals 1, all children are assigned to the same
481 tree. We therefore can assign the parent to this tree number, too.
483 ### Make decision if to assign the parent
485 If all kids belong to the same tree, we can propagate this ownership one
486 level up. However, we do this for a tree `k` if and only if the following
489 There could be multiple nodes `k` within the tree that span all together a
490 forest of small trees. Let `max_k` be the maximum level of these root nodes
491 within the forest, and let `min_k` be the minimum level.
493 We assign an arbitrary node from another tree colour
494 the colour `k` if and only if
496 - `max_k - min_k` becomes smaller through the assignment, or;
497 - `max_k - min_k = 0` and we could group all root nodes of `k` into
502 The reason is that we don't want to propagate too aggressively bottom-up.
503 Eventually, our goal is to introduce a hierarchy over the trees. For this,
504 it helps us if all the root nodes of tree `k` are on the same level. In this
505 case, we can determine (likely) one parent `k' and then tell `k' to fork x
506 times where x is the number of roots within the forest of `k`.
508 Therefore, we try to ensure that `max_k - min_k` becomes smaller and smaller.
509 This implies that, in an adaptive mesh, we try to group all the fine mesh
510 parts together first.
512 Eventually, all root nodes of the forest `k` will end up on the one and same
513 level. It could now be that we could move all of them one level up. This
514 is covered by the second case distinction.
516 @todo Dmitry the check above is not implemented yet an causes the algorithm
519 peano_tree: Tree to update (modified in place).
525 - Operates from finest internal level to root.
526 - Preserves node existence flags (`weight`).
527 - Parents with mixed-child subtree assignments remain unchanged.
528 - Intended to run after Stage I (`distribute_leaves_bottom_up()` or `distribute_leaves_top_down()`).
530 print(
"Updating non-leaf node assignments to maintain tree topology...")
532 for level
in range(peano_tree._max_level - 2, -1, -1):
533 size = 3**(level + 1)
535 if peano_tree._dimension == 2:
536 for a
in range(size):
537 for b
in range(size):
538 current_node = peano_tree._tree_arrays[level][a][b]
539 if current_node[0] == 1:
541 child_subtrees = set()
542 child_size = 3**(level + 2)
545 for child_a
in range(3*a, min(3*(a+1), child_size)):
546 for child_b
in range(3*b, min(3*(b+1), child_size)):
547 child_node = peano_tree._tree_arrays[level+1][child_a][child_b]
548 if child_node[0] == 1:
549 child_subtrees.add(child_node[1])
552 if len(child_subtrees) == 1:
553 target_subtree = list(child_subtrees)[0]
554 peano_tree._tree_arrays[level][a][b] = (1, target_subtree)
556 elif peano_tree._dimension == 3:
557 for a
in range(size):
558 for b
in range(size):
559 for c
in range(size):
560 current_node = peano_tree._tree_arrays[level][a][b][c]
561 if current_node[0] == 1:
563 child_subtrees = set()
564 child_size = 3**(level + 2)
567 for child_a
in range(3*a, min(3*(a+1), child_size)):
568 for child_b
in range(3*b, min(3*(b+1), child_size)):
569 for child_c
in range(3*c, min(3*(c+1), child_size)):
570 child_node = peano_tree._tree_arrays[level+1][child_a][child_b][child_c]
571 if child_node[0] == 1:
572 child_subtrees.add(child_node[1])
575 if len(child_subtrees) == 1:
576 target_subtree = list(child_subtrees)[0]
577 peano_tree._tree_arrays[level][a][b][c] = (1, target_subtree)
579 print(
"Non-leaf node assignment update completed.")
583 Stage III of the staged load-balancing workflow.
585 Minimise the total number of split operations whilst preserving load balance
586 established by Stage II. The algorithm lifts split decisions up the tree hierarchy
587 by promoting a parent on level L-1 to the dominant subtree among its children
590 Hierarchy lifting is applicable when:
591 - All children of a parent exist (count equals 3^d, where d is the spatial
592 dimensions count: d=2 -> 9 children, d=3 -> 27 children)
593 - Children are mixed across at least two subtrees
594 - A dominant child subtree M exists (largest child count)
595 - The dominant subtree differs from the current parent subtree and yields a
596 reduction of lower-level split pressure
598 When these conditions are met, the parent is reassigned to the dominant subtree.
599 Remaining child subtrees stay on level L, but their source parent subtree is now
600 the updated one (which effectively moves part of splitting upwards).
602 The algorithm operates bottom-up from finest to coarsest level(s), iteratively
603 lifting applicable promotions until no further reductions are possible.
606 peano_tree: Tree to update (modified in place).
607 Required precondition: Stage II (bottom-up propagation) must have been
608 applied first to ensure non-leaf subtree consistency.
609 require_all_children: If True (default), only promote parents whose
610 full 3^d child set exists. If False, also allow promotions for mixed
617 - This function is intended to run after `_propagate_bottom_up()`.
618 - Preserves node existence flags (`weight`).
619 - Modifies only subtree assignments to reduce split operation count.
620 - Multi-pass iteration allows cascading promotions up the tree.
621 - The "all children in one subtree" case is already resolved by Stage II;
622 Stage III primarily targets mixed-child parents.
624 print(
"Stage III: Optimising hierarchy to minimise split operations...")
626 dimensions = peano_tree._dimension
627 max_level = peano_tree._max_level
628 children_per_parent = 3 ** dimensions
630 def _dominant_subtree(child_subtrees: list[int], parent_subtree: int) -> int |
None:
632 Pick a promotion target from mixed child subtree assignments.
634 Returns the dominant subtree id when promotion is beneficial, otherwise None.
636 if require_all_children
and len(child_subtrees) != children_per_parent:
639 counts: dict[int, int] = {}
640 for subtree
in child_subtrees:
641 counts[subtree] = counts.get(subtree, 0) + 1
648 ranked = sorted(counts.items(), key=
lambda item: (item[1], item[0]), reverse=
True)
649 dominant_subtree, dominant_count = ranked[0]
651 if dominant_subtree == parent_subtree:
655 if dominant_subtree < parent_subtree:
658 parent_count = counts.get(parent_subtree, 0)
659 if dominant_count <= parent_count:
662 return dominant_subtree
673 for level
in range(max_level - 2, -1, -1):
674 size = 3 ** (level + 1)
677 for a
in range(size):
678 for b
in range(size):
679 parent_node = peano_tree._tree_arrays[level][a][b]
680 if parent_node[0] != 1:
685 child_size = 3 ** (level + 2)
687 for child_a
in range(3 * a, min(3 * (a + 1), child_size)):
688 for child_b
in range(3 * b, min(3 * (b + 1), child_size)):
689 child_node = peano_tree._tree_arrays[level + 1][child_a][child_b]
690 if child_node[0] == 1:
691 child_subtrees.append(child_node[1])
693 target_subtree = _dominant_subtree(
694 child_subtrees=child_subtrees,
695 parent_subtree=parent_node[1],
698 if target_subtree
is not None:
699 peano_tree._tree_arrays[level][a][b] = (1, target_subtree)
702 elif dimensions == 3:
703 for a
in range(size):
704 for b
in range(size):
705 for c
in range(size):
706 parent_node = peano_tree._tree_arrays[level][a][b][c]
707 if parent_node[0] != 1:
712 child_size = 3 ** (level + 2)
714 for child_a
in range(3 * a, min(3 * (a + 1), child_size)):
715 for child_b
in range(3 * b, min(3 * (b + 1), child_size)):
716 for child_c
in range(3 * c, min(3 * (c + 1), child_size)):
717 child_node = peano_tree._tree_arrays[level + 1][
718 child_a][child_b][child_c]
719 if child_node[0] == 1:
720 child_subtrees.append(child_node[1])
722 target_subtree = _dominant_subtree(
723 child_subtrees=child_subtrees,
724 parent_subtree=parent_node[1],
727 if target_subtree
is not None:
728 peano_tree._tree_arrays[level][a][b][c] = (
733 f
"Hierarchy optimisation completed in {pass_count} pass(es). "
734 f
"Total split operations reduced."
739 Generate a YAML file describing the complete tree structure with spatial layout.
744 - **Tree Metadata**: Name, dimensions, domain boundaries, leaf counts
745 - **Split Summary**: Distribution statistics across subtrees
746 - **Level Data**: Complete spatial arrays for each refinement level
748 ## Spatial Array Format
750 Each level contains a spatial representation:
751 - **2D**: Row-indexed arrays with comma-separated (weight,subtree_id) tuples
752 - **3D**: Plane-row indexed arrays with nested structure
753 - **Indexing**: 0-based indexing for both rows and planes
755 ## Data Format Example
762 0: "(1,0), (1,0), (1,1)"
763 1: "(1,1), (1,1), (1,2)"
764 2: "(1,2), (1,2), (1,2)"
767 ## Integration with Peano
769 The YAML format is designed for:
770 - Direct import into Peano's hardcoded load balancer
771 - Visualization tools and analysis scripts
772 - Checkpoint/restart mechanisms for offline decomposition
776 - Preserves exact spatial relationships from tree arrays
777 - Maintains type consistency (all values converted to int)
778 - Uses safe YAML dumping to prevent formatting issues
781 peano_tree: Tree object to generate YAML from
782 output_yaml_file: Path to the output YAML file
788 IOError: If file cannot be written due to permissions or disk space
791 Large trees may generate substantial YAML files. Consider file system
792 constraints when processing high-resolution adaptive meshes.
797 for level
in range(peano_tree._max_level):
798 size = 3**(level + 1)
803 if peano_tree._dimension == 2:
805 for a
in range(size):
807 for b
in range(size):
808 weight, subtree_id = peano_tree._tree_arrays[level][a][b]
809 row_data.append(f
"({int(weight)},{int(subtree_id)})")
810 level_data[a] =
", ".join(row_data)
812 elif peano_tree._dimension == 3:
814 for a
in range(size):
816 for b
in range(size):
818 for c
in range(size):
819 weight, subtree_id = peano_tree._tree_arrays[level][a][b][c]
820 row_data.append(f
"({int(weight)},{int(subtree_id)})")
821 plane_data[b] =
", ".join(row_data)
822 level_data[a] = plane_data
832 'title': f
"Complete Tree Structure from {peano_tree._name}",
833 'description':
"Spatial representation of tree with (weight, subtree_id) tuples",
836 'name': peano_tree._name,
837 'dimension': peano_tree._dimension,
838 'max_level': peano_tree._max_level,
839 'domain_offset': list(peano_tree._domain_offset),
840 'domain_size': list(peano_tree._domain_size),
841 'total_leaf_count': int(peano_tree._leaf_count),
842 'subtree_count': peano_tree._subtree_count
845 'subtree_decomposition': [int(count)
for count
in peano_tree._split_pattern]
847 'tree_structure': tree_levels
851 with open(output_yaml_file,
'w')
as file:
852 yaml.dump(split_data, file, default_flow_style=
False, sort_keys=
False)
853 print(f
"Complete tree structure written to {output_yaml_file}")
855 print(f
"Error writing to YAML file: {e}")
917) -> tuple[list[int], list[int], list[int], list[bool]]:
919 Stage IV of the staged load-balancing workflow.
921 Derive the enumerated splits for Peano's hardcoded load balancer from a split tree.
923 The function scans the tree arrays and reconstructs a sequence of splits
924 which replays the observed decomposition with
925 toolbox::loadbalancing::strategies::Hardcoded. Entry k of each returned
926 list describes the kth split. The C++ strategy decides itself when to
927 issue the kth split, i.e. there is no notion of mesh sweeps here.
929 A split is emitted whenever, on some level, a cell with `subtree_id > 0` exists
930 whose parent exists and has a different subtree id (i.e. the subtree gets introduced
931 across a parent–child edge). Level 0 has no parent; any cells with `subtree_id > 0`
932 on level 0 are treated as introduced from subtree 0.
934 **BottomUp vs AggressiveTopDown extraction**
936 The level at which a split is emitted depends on how many distinct introduction
937 levels the subtree has:
939 - *AggressiveTopDown* (single introduction level): a split is emitted at that one
941 - *BottomUp* (multiple introduction levels): the subtree's forest of root cells does
942 not all reside on the same level. In BottomUp mode, Peano propagates the split
943 upward from the finest level automatically, so there must be no split instruction
944 higher up the tree. A **single** split is therefore emitted at the **finest**
945 introduction level (the deepest level at which any root cell of the subtree
946 borders a parent of a different subtree). Coarser introduction levels are
949 The splits are ordered from coarse to fine emission level and, within a level,
953 - `splittingTrees` is the unique parent subtree id for all introduced cells on the
954 emitted level, or 0 if ambiguous.
955 - `numberOfCells` is the count of introduced cells on the emitted level.
956 - `subTrees` are subtree ids (not MPI ranks); map them to destination ranks
958 - `bottomUpFlags` is a per-split boolean that is `True` when the corresponding
959 subtree has multiple introduction levels (BottomUp) and `False` otherwise
963 peano_tree: Input tree with subtree assignments.
966 Tuple `(splittingTrees, numberOfCells, subTrees, bottomUpFlags)`
967 suitable for building a `Hardcoded` configuration.
970 This helper is intended for *offline replay* of an already-computed split pattern.
971 It does not modify the tree.
973 if peano_tree._subtree_count <= 1:
974 print(
"No splits to extract - tree has only one subtree")
975 return ([], [], [], [])
977 dim = peano_tree._dimension
978 max_level = peano_tree._max_level
980 def count_cells_on_level(subtree_id: int, level: int) -> int:
981 """Count existing cells (weight==1) on a given level that belong to subtree_id."""
982 size = 3 ** (level + 1)
985 for a
in range(size):
986 for b
in range(size):
987 w, s = peano_tree._tree_arrays[level][a][b]
988 if w == 1
and s == subtree_id:
991 for a
in range(size):
992 for b
in range(size):
993 for c
in range(size):
994 w, s = peano_tree._tree_arrays[level][a][b][c]
995 if w == 1
and s == subtree_id:
999 def parent_subtrees_for_introduced_cells(subtree_id: int, level: int) -> tuple[int, set[int]]:
1001 Return (count, parent_ids) for cells of subtree_id at given level that are
1002 'introduced' wrt their parent: parent exists and parent_subtree != subtree_id.
1007 size = 3 ** (level + 1)
1009 parent_ids: set[int] = set()
1012 for a
in range(size):
1013 for b
in range(size):
1014 w, s = peano_tree._tree_arrays[level][a][b]
1015 if w != 1
or s != subtree_id:
1017 pa, pb = a // 3, b // 3
1018 pw, ps = peano_tree._tree_arrays[level - 1][pa][pb]
1019 if pw == 1
and ps != subtree_id:
1023 for a
in range(size):
1024 for b
in range(size):
1025 for c
in range(size):
1026 w, s = peano_tree._tree_arrays[level][a][b][c]
1027 if w != 1
or s != subtree_id:
1029 pa, pb, pc = a // 3, b // 3, c // 3
1030 pw, ps = peano_tree._tree_arrays[level - 1][pa][pb][pc]
1031 if pw == 1
and ps != subtree_id:
1035 return (count, parent_ids)
1037 split_operations = []
1039 for target_subtree
in range(1, peano_tree._subtree_count):
1045 introductions: dict[int, tuple[int, set[int]]] = {}
1048 moved_level0 = count_cells_on_level(target_subtree, level=0)
1049 if moved_level0 > 0:
1050 introductions[0] = (moved_level0, {0})
1053 for level
in range(1, max_level):
1054 moved_cells, parent_ids = parent_subtrees_for_introduced_cells(target_subtree, level)
1056 introductions[level] = (moved_cells, parent_ids)
1058 if not introductions:
1072 is_bottom_up = len(introductions) > 1
1073 emit_level = max(introductions.keys())
if is_bottom_up
else next(iter(introductions.keys()))
1075 num_cells, parent_ids = introductions[emit_level]
1076 source_subtree = next(iter(parent_ids))
if len(parent_ids) == 1
else 0
1078 split_operations.append(
1080 "source_subtree": source_subtree,
1081 "target_subtree": target_subtree,
1082 "num_cells": num_cells,
1083 "level": emit_level,
1084 "is_bottom_up": is_bottom_up,
1088 split_operations.sort(key=
lambda op: (op[
"level"], op[
"target_subtree"]))
1090 splittingTrees = [op[
"source_subtree"]
for op
in split_operations]
1091 numberOfCells = [op[
"num_cells"]
for op
in split_operations]
1092 subTrees = [op[
"target_subtree"]
for op
in split_operations]
1093 bottomUpFlags = [op[
"is_bottom_up"]
for op
in split_operations]
1095 return (splittingTrees, numberOfCells, subTrees, bottomUpFlags)