phylogenie 2.1.13__tar.gz → 2.1.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of phylogenie might be problematic. Click here for more details.
- {phylogenie-2.1.13 → phylogenie-2.1.14}/PKG-INFO +1 -1
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/alisim.py +3 -1
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/plot.py +42 -17
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/tree.py +48 -19
- phylogenie-2.1.14/phylogenie/treesimulator/features.py +49 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/model.py +1 -3
- phylogenie-2.1.14/phylogenie/utils.py +59 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/pyproject.toml +1 -1
- phylogenie-2.1.13/phylogenie/treesimulator/features.py +0 -39
- phylogenie-2.1.13/phylogenie/utils.py +0 -28
- {phylogenie-2.1.13 → phylogenie-2.1.14}/LICENSE.txt +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/README.md +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/__init__.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/__init__.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/configs.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/dataset.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/factories.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/trees.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/generators/typeguards.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/io.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/main.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/models.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/msa.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/py.typed +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/skyline/__init__.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/skyline/matrix.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/skyline/parameter.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/skyline/vector.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/__init__.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/events/__init__.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/events/contact_tracing.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/events/core.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/events/mutations.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/treesimulator/gillespie.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/typeguards.py +0 -0
- {phylogenie-2.1.13 → phylogenie-2.1.14}/phylogenie/typings.py +0 -0
|
@@ -9,6 +9,7 @@ from phylogenie.generators.dataset import DatasetGenerator, DataType
|
|
|
9
9
|
from phylogenie.generators.factories import data, string
|
|
10
10
|
from phylogenie.generators.trees import TreeDatasetGeneratorConfig
|
|
11
11
|
from phylogenie.io import dump_newick
|
|
12
|
+
from phylogenie.utils import get_node_depths
|
|
12
13
|
|
|
13
14
|
MSAS_DIRNAME = "MSAs"
|
|
14
15
|
TREES_DIRNAME = "trees"
|
|
@@ -71,8 +72,9 @@ class AliSimDatasetGenerator(DatasetGenerator):
|
|
|
71
72
|
"Tree simulation timed out, retrying with different parameters..."
|
|
72
73
|
)
|
|
73
74
|
|
|
75
|
+
times = get_node_depths(tree)
|
|
74
76
|
for leaf in tree.get_leaves():
|
|
75
|
-
leaf.name += f"|{leaf
|
|
77
|
+
leaf.name += f"|{times[leaf]}"
|
|
76
78
|
dump_newick(tree, f"{tree_filename}.nwk")
|
|
77
79
|
|
|
78
80
|
self._generate_one_from_tree(msa_filename, f"{tree_filename}.nwk", rng, d)
|
|
@@ -6,7 +6,7 @@ import matplotlib.pyplot as plt
|
|
|
6
6
|
from matplotlib.axes import Axes
|
|
7
7
|
|
|
8
8
|
from phylogenie.tree import Tree
|
|
9
|
-
from phylogenie.utils import
|
|
9
|
+
from phylogenie.utils import get_node_depths
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Coloring(str, Enum):
|
|
@@ -22,44 +22,69 @@ def plot_tree(
|
|
|
22
22
|
coloring: str | Coloring | None = None,
|
|
23
23
|
cmap: str | None = None,
|
|
24
24
|
show_legend: bool = True,
|
|
25
|
+
x_feature: str | None = None,
|
|
25
26
|
) -> Axes:
|
|
26
27
|
if ax is None:
|
|
27
28
|
ax = plt.gca()
|
|
28
29
|
|
|
29
|
-
xs =
|
|
30
|
+
xs = (
|
|
31
|
+
get_node_depths(tree)
|
|
32
|
+
if x_feature is None
|
|
33
|
+
else {node: node.get(x_feature) for node in tree}
|
|
34
|
+
)
|
|
30
35
|
ys = {node: i for i, node in enumerate(tree.inorder_traversal())}
|
|
31
36
|
|
|
32
37
|
if color_by is not None:
|
|
33
|
-
features = set(node.get(color_by) for node in tree)
|
|
38
|
+
features = set(node.get(color_by) for node in tree if color_by in node.features)
|
|
34
39
|
if coloring is None and any(isinstance(f, float) for f in features):
|
|
35
40
|
coloring = Coloring.CONTINUOUS
|
|
36
41
|
elif coloring is None:
|
|
37
42
|
coloring = Coloring.DISCRETE
|
|
43
|
+
|
|
38
44
|
if coloring == Coloring.DISCRETE:
|
|
39
45
|
if any(isinstance(f, float) for f in features):
|
|
40
46
|
raise ValueError(
|
|
41
47
|
"Discrete coloring selected but feature values are not all categorical."
|
|
42
48
|
)
|
|
43
|
-
|
|
44
|
-
colormap = plt.get_cmap(cmap, len(features))
|
|
49
|
+
colormap = plt.get_cmap("tab20" if cmap is None else cmap)
|
|
45
50
|
feature_colors = {
|
|
46
51
|
f: mcolors.to_hex(colormap(i)) for i, f in enumerate(features)
|
|
47
52
|
}
|
|
48
|
-
colors = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
colors = {
|
|
54
|
+
node: (
|
|
55
|
+
feature_colors[node.get(color_by)]
|
|
56
|
+
if color_by in node.features
|
|
57
|
+
else default_color
|
|
58
|
+
)
|
|
59
|
+
for node in tree
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
if show_legend:
|
|
63
|
+
legend_handles = [
|
|
64
|
+
mpatches.Patch(color=feature_colors[f], label=str(f))
|
|
65
|
+
for f in features
|
|
66
|
+
]
|
|
67
|
+
if any(color_by not in node.features for node in tree):
|
|
68
|
+
legend_handles.append(
|
|
69
|
+
mpatches.Patch(color=default_color, label="None")
|
|
70
|
+
)
|
|
53
71
|
ax.legend(handles=legend_handles, title=color_by) # pyright: ignore
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
|
|
73
|
+
elif coloring == Coloring.CONTINUOUS:
|
|
74
|
+
norm = mcolors.Normalize(vmin=min(features), vmax=max(features))
|
|
75
|
+
colormap = plt.get_cmap("viridis" if cmap is None else cmap)
|
|
76
|
+
colors = {
|
|
77
|
+
node: (
|
|
78
|
+
colormap(norm(float(node.get(color_by))))
|
|
79
|
+
if color_by in node.features
|
|
80
|
+
else default_color
|
|
81
|
+
)
|
|
82
|
+
for node in tree
|
|
83
|
+
}
|
|
60
84
|
|
|
61
85
|
sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
|
|
62
86
|
ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
|
|
87
|
+
|
|
63
88
|
else:
|
|
64
89
|
raise ValueError(
|
|
65
90
|
f"Unknown coloring method: {coloring}. Choices are {list(Coloring)}."
|
|
@@ -68,11 +93,11 @@ def plot_tree(
|
|
|
68
93
|
colors = {node: default_color for node in tree}
|
|
69
94
|
|
|
70
95
|
for node in tree:
|
|
71
|
-
x1, y1 = xs[node
|
|
96
|
+
x1, y1 = xs[node], ys[node]
|
|
72
97
|
if node.parent is None:
|
|
73
98
|
ax.hlines(y=y1, xmin=0, xmax=x1, color=colors[node]) # pyright: ignore
|
|
74
99
|
continue
|
|
75
|
-
x0, y0 = xs[node.parent
|
|
100
|
+
x0, y0 = xs[node.parent], ys[node.parent]
|
|
76
101
|
ax.vlines(x=x0, ymin=y0, ymax=y1, color=colors[node]) # pyright: ignore
|
|
77
102
|
ax.hlines(y=y1, xmin=x0, xmax=x1, color=colors[node]) # pyright: ignore
|
|
78
103
|
|
|
@@ -23,12 +23,49 @@ class Tree:
|
|
|
23
23
|
return self._features.copy()
|
|
24
24
|
|
|
25
25
|
@property
|
|
26
|
-
def
|
|
27
|
-
if self.parent is None
|
|
26
|
+
def depth(self) -> float:
|
|
27
|
+
if self.parent is None:
|
|
28
28
|
return 0.0
|
|
29
29
|
if self.branch_length is None:
|
|
30
30
|
raise ValueError(f"Branch length of node {self.name} is not set.")
|
|
31
|
-
return self.branch_length
|
|
31
|
+
return self.parent.depth + self.branch_length
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def depth_level(self) -> int:
|
|
35
|
+
if self.parent is None:
|
|
36
|
+
return 0
|
|
37
|
+
return self.parent.depth_level + 1
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def height(self) -> float:
|
|
41
|
+
if self.is_leaf():
|
|
42
|
+
return 0.0
|
|
43
|
+
if any(child.branch_length is None for child in self.children):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Branch length of one or more children of node {self.name} is not set."
|
|
46
|
+
)
|
|
47
|
+
return max(
|
|
48
|
+
child.branch_length + child.height # pyright: ignore
|
|
49
|
+
for child in self.children
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def height_level(self) -> int:
|
|
54
|
+
if self.is_leaf():
|
|
55
|
+
return 0
|
|
56
|
+
return 1 + max(child.height_level for child in self.children)
|
|
57
|
+
|
|
58
|
+
def set(self, key: str, value: Any) -> None:
|
|
59
|
+
self._features[key] = value
|
|
60
|
+
|
|
61
|
+
def update_features(self, features: dict[str, Any]) -> None:
|
|
62
|
+
self._features.update(features)
|
|
63
|
+
|
|
64
|
+
def get(self, key: str) -> Any:
|
|
65
|
+
return self._features[key]
|
|
66
|
+
|
|
67
|
+
def delete(self, key: str) -> None:
|
|
68
|
+
del self._features[key]
|
|
32
69
|
|
|
33
70
|
def add_child(self, child: "Tree") -> "Tree":
|
|
34
71
|
child._parent = self
|
|
@@ -75,23 +112,12 @@ class Tree:
|
|
|
75
112
|
return not self.children
|
|
76
113
|
|
|
77
114
|
def get_leaves(self) -> tuple["Tree", ...]:
|
|
78
|
-
return tuple(node for node in self if
|
|
115
|
+
return tuple(node for node in self if node.is_leaf())
|
|
79
116
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def set(self, key: str, value: Any) -> None:
|
|
85
|
-
self._features[key] = value
|
|
86
|
-
|
|
87
|
-
def update_features(self, features: dict[str, Any]) -> None:
|
|
88
|
-
self._features.update(features)
|
|
89
|
-
|
|
90
|
-
def get(self, key: str) -> Any:
|
|
91
|
-
return self._features[key]
|
|
92
|
-
|
|
93
|
-
def delete(self, key: str) -> None:
|
|
94
|
-
del self._features[key]
|
|
117
|
+
def ladderize(self, feature: str) -> None:
|
|
118
|
+
self._children.sort(key=lambda x: x.get(feature))
|
|
119
|
+
for child in self.children:
|
|
120
|
+
child.ladderize(feature)
|
|
95
121
|
|
|
96
122
|
def copy(self):
|
|
97
123
|
new_tree = Tree(self.name, self.branch_length)
|
|
@@ -103,5 +129,8 @@ class Tree:
|
|
|
103
129
|
def __iter__(self) -> Iterator["Tree"]:
|
|
104
130
|
return self.preorder_traversal()
|
|
105
131
|
|
|
132
|
+
def __len__(self) -> int:
|
|
133
|
+
return sum(1 for _ in self)
|
|
134
|
+
|
|
106
135
|
def __repr__(self) -> str:
|
|
107
136
|
return f"TreeNode(name='{self.name}', branch_length={self.branch_length}, features={self.features})"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from phylogenie.tree import Tree
|
|
5
|
+
from phylogenie.treesimulator.events import get_mutation_id
|
|
6
|
+
from phylogenie.treesimulator.model import get_node_state
|
|
7
|
+
from phylogenie.utils import (
|
|
8
|
+
get_node_depth_levels,
|
|
9
|
+
get_node_depths,
|
|
10
|
+
get_node_height_levels,
|
|
11
|
+
get_node_heights,
|
|
12
|
+
get_node_leaf_counts,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_states(tree: Tree) -> dict[Tree, str]:
|
|
17
|
+
return {node: get_node_state(node.name) for node in tree}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_mutations(tree: Tree) -> dict[Tree, int]:
|
|
21
|
+
return {node: get_mutation_id(node.name) for node in tree}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Feature(str, Enum):
|
|
25
|
+
DEPTH = "depth"
|
|
26
|
+
DEPTH_LEVEL = "depth_level"
|
|
27
|
+
HEIGHT = "height"
|
|
28
|
+
HEIGHT_LEVEL = "height_level"
|
|
29
|
+
MUTATION = "mutation"
|
|
30
|
+
N_LEAVES = "n_leaves"
|
|
31
|
+
STATE = "state"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
FEATURES_EXTRACTORS = {
|
|
35
|
+
Feature.DEPTH: get_node_depths,
|
|
36
|
+
Feature.DEPTH_LEVEL: get_node_depth_levels,
|
|
37
|
+
Feature.HEIGHT: get_node_heights,
|
|
38
|
+
Feature.HEIGHT_LEVEL: get_node_height_levels,
|
|
39
|
+
Feature.MUTATION: _get_mutations,
|
|
40
|
+
Feature.N_LEAVES: get_node_leaf_counts,
|
|
41
|
+
Feature.STATE: _get_states,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def set_features(tree: Tree, features: Iterable[Feature]) -> None:
|
|
46
|
+
for feature in features:
|
|
47
|
+
feature_maps = FEATURES_EXTRACTORS[feature](tree)
|
|
48
|
+
for node in tree:
|
|
49
|
+
node.set(feature.value, feature_maps[node])
|
|
@@ -89,9 +89,7 @@ class Model:
|
|
|
89
89
|
def _set_branch_length(self, node: Tree, time: float) -> None:
|
|
90
90
|
if node.branch_length is not None:
|
|
91
91
|
raise ValueError(f"Branch length of node {node.name} is already set.")
|
|
92
|
-
node.branch_length =
|
|
93
|
-
time if node.parent is None else time - node.parent.get_time()
|
|
94
|
-
)
|
|
92
|
+
node.branch_length = time if node.parent is None else time - node.parent.depth
|
|
95
93
|
|
|
96
94
|
def _stem(self, individual: Individual, time: float) -> None:
|
|
97
95
|
self._set_branch_length(individual.node, time)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from phylogenie.tree import Tree
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_node_leaf_counts(tree: Tree) -> dict[Tree, int]:
|
|
5
|
+
n_leaves: dict[Tree, int] = {}
|
|
6
|
+
for node in tree.postorder_traversal():
|
|
7
|
+
n_leaves[node] = sum(n_leaves[child] for child in node.children) or 1
|
|
8
|
+
return n_leaves
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_node_depth_levels(tree: Tree) -> dict[Tree, int]:
|
|
12
|
+
depth_levels: dict[Tree, int] = {}
|
|
13
|
+
for node in tree:
|
|
14
|
+
if node.parent is None:
|
|
15
|
+
depth_levels[node] = 0
|
|
16
|
+
else:
|
|
17
|
+
depth_levels[node] = depth_levels[node.parent] + 1
|
|
18
|
+
return depth_levels
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_node_depths(tree: Tree) -> dict[Tree, float]:
|
|
22
|
+
depths: dict[Tree, float] = {}
|
|
23
|
+
for node in tree:
|
|
24
|
+
if node.parent is None:
|
|
25
|
+
depths[node] = 0
|
|
26
|
+
else:
|
|
27
|
+
if node.branch_length is None:
|
|
28
|
+
raise ValueError(f"Branch length of node {node.name} is not set.")
|
|
29
|
+
depths[node] = depths[node.parent] + node.branch_length
|
|
30
|
+
return depths
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_node_height_levels(tree: Tree) -> dict[Tree, int]:
|
|
34
|
+
height_levels: dict[Tree, int] = {}
|
|
35
|
+
for node in tree.postorder_traversal():
|
|
36
|
+
if node.is_leaf():
|
|
37
|
+
height_levels[node] = 0
|
|
38
|
+
else:
|
|
39
|
+
height_levels[node] = max(
|
|
40
|
+
1 + height_levels[child] for child in node.children
|
|
41
|
+
)
|
|
42
|
+
return height_levels
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_node_heights(tree: Tree) -> dict[Tree, float]:
|
|
46
|
+
heights: dict[Tree, float] = {}
|
|
47
|
+
for node in tree.postorder_traversal():
|
|
48
|
+
if node.is_leaf():
|
|
49
|
+
heights[node] = 0
|
|
50
|
+
else:
|
|
51
|
+
if any(child.branch_length is None for child in node.children):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Branch length of one or more children of node {node.name} is not set."
|
|
54
|
+
)
|
|
55
|
+
heights[node] = max(
|
|
56
|
+
child.branch_length + heights[child] # pyright: ignore
|
|
57
|
+
for child in node.children
|
|
58
|
+
)
|
|
59
|
+
return heights
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
from collections.abc import Iterable
|
|
2
|
-
from enum import Enum
|
|
3
|
-
|
|
4
|
-
from phylogenie.tree import Tree
|
|
5
|
-
from phylogenie.treesimulator.events import get_mutation_id
|
|
6
|
-
from phylogenie.treesimulator.model import get_node_state
|
|
7
|
-
from phylogenie.utils import get_heights, get_n_tips, get_times
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _get_states(tree: Tree) -> dict[str, str]:
|
|
11
|
-
return {node.name: get_node_state(node.name) for node in tree}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _get_mutations(tree: Tree) -> dict[str, int]:
|
|
15
|
-
return {node.name: get_mutation_id(node.name) for node in tree}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Feature(str, Enum):
|
|
19
|
-
STATE = "state"
|
|
20
|
-
MUTATION = "mutation"
|
|
21
|
-
N_TIPS = "n_tips"
|
|
22
|
-
TIME = "time"
|
|
23
|
-
HEIGHT = "height"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
FEATURES_EXTRACTORS = {
|
|
27
|
-
Feature.STATE: _get_states,
|
|
28
|
-
Feature.MUTATION: _get_mutations,
|
|
29
|
-
Feature.N_TIPS: get_n_tips,
|
|
30
|
-
Feature.TIME: get_times,
|
|
31
|
-
Feature.HEIGHT: get_heights,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def set_features(tree: Tree, features: Iterable[Feature]) -> None:
|
|
36
|
-
for feature in features:
|
|
37
|
-
feature_maps = FEATURES_EXTRACTORS[feature](tree)
|
|
38
|
-
for node in tree:
|
|
39
|
-
node.set(feature.value, feature_maps[node.name])
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from phylogenie.tree import Tree
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def get_n_tips(tree: Tree) -> dict[str, int]:
|
|
5
|
-
n_tips: dict[str, int] = {}
|
|
6
|
-
for node in tree.postorder_traversal():
|
|
7
|
-
n_tips[node.name] = (
|
|
8
|
-
1 if node.is_leaf() else sum(n_tips[child.name] for child in node.children)
|
|
9
|
-
)
|
|
10
|
-
return n_tips
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def get_times(tree: Tree) -> dict[str, float]:
|
|
14
|
-
times: dict[str, float] = {}
|
|
15
|
-
for node in tree:
|
|
16
|
-
parent_time = 0 if node.parent is None else times[node.parent.name]
|
|
17
|
-
times[node.name] = node.time_to_parent + parent_time
|
|
18
|
-
return times
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_heights(tree: Tree) -> dict[str, int]:
|
|
22
|
-
heights: dict[str, int] = {}
|
|
23
|
-
for node in tree.postorder_traversal():
|
|
24
|
-
if node.is_leaf():
|
|
25
|
-
heights[node.name] = 0
|
|
26
|
-
else:
|
|
27
|
-
heights[node.name] = 1 + max(heights[child.name] for child in node.children)
|
|
28
|
-
return heights
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|