phylogenie 3.1.11__tar.gz → 3.1.18__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.

Files changed (46) hide show
  1. {phylogenie-3.1.11/src/phylogenie.egg-info → phylogenie-3.1.18}/PKG-INFO +1 -1
  2. {phylogenie-3.1.11 → phylogenie-3.1.18}/pyproject.toml +2 -2
  3. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/__init__.py +8 -0
  4. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/draw.py +40 -27
  5. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/factories.py +17 -13
  6. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/trees.py +17 -3
  7. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/msa.py +11 -4
  8. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/__init__.py +2 -0
  9. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/gillespie.py +11 -2
  10. {phylogenie-3.1.11 → phylogenie-3.1.18/src/phylogenie.egg-info}/PKG-INFO +1 -1
  11. {phylogenie-3.1.11 → phylogenie-3.1.18}/LICENSE.txt +0 -0
  12. {phylogenie-3.1.11 → phylogenie-3.1.18}/README.md +0 -0
  13. {phylogenie-3.1.11 → phylogenie-3.1.18}/setup.cfg +0 -0
  14. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/__init__.py +0 -0
  15. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/alisim.py +0 -0
  16. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/configs.py +0 -0
  17. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/dataset.py +0 -0
  18. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/generators/typeguards.py +0 -0
  19. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/io/__init__.py +0 -0
  20. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/io/fasta.py +0 -0
  21. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/main.py +0 -0
  22. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/mixins.py +0 -0
  23. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/py.typed +0 -0
  24. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/skyline/__init__.py +0 -0
  25. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/skyline/matrix.py +0 -0
  26. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/skyline/parameter.py +0 -0
  27. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/skyline/vector.py +0 -0
  28. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/__init__.py +0 -0
  29. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/base.py +0 -0
  30. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/contact_tracing.py +0 -0
  31. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/core.py +0 -0
  32. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/mutations.py +0 -0
  33. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/features.py +0 -0
  34. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/__init__.py +0 -0
  35. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/newick.py +0 -0
  36. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/nexus.py +0 -0
  37. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/model.py +0 -0
  38. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/tree.py +0 -0
  39. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/treesimulator/utils.py +0 -0
  40. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/typeguards.py +0 -0
  41. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie/typings.py +0 -0
  42. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie.egg-info/SOURCES.txt +0 -0
  43. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie.egg-info/dependency_links.txt +0 -0
  44. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie.egg-info/entry_points.txt +0 -0
  45. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie.egg-info/requires.txt +0 -0
  46. {phylogenie-3.1.11 → phylogenie-3.1.18}/src/phylogenie.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phylogenie
3
- Version: 3.1.11
3
+ Version: 3.1.18
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "phylogenie"
3
- version = "3.1.11"
3
+ version = "3.1.18"
4
4
  description = "Generate phylogenetic datasets with minimal setup effort"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -29,4 +29,4 @@ phylogenie = "phylogenie.main:main"
29
29
 
30
30
  [build-system]
31
31
  requires = ["setuptools>=42"]
32
- build-backend = "setuptools.build_meta"
32
+ build-backend = "setuptools.build_meta"
@@ -38,6 +38,7 @@ from phylogenie.treesimulator import (
38
38
  Tree,
39
39
  compute_mean_leaf_pairwise_distance,
40
40
  compute_sackin_index,
41
+ count_hops,
41
42
  dump_newick,
42
43
  generate_trees,
43
44
  get_BD_events,
@@ -50,12 +51,15 @@ from phylogenie.treesimulator import (
50
51
  get_FBD_events,
51
52
  get_mrca,
52
53
  get_mutation_id,
54
+ get_node_ages,
53
55
  get_node_depth_levels,
54
56
  get_node_depths,
55
57
  get_node_height_levels,
56
58
  get_node_heights,
57
59
  get_node_leaf_counts,
58
60
  get_node_state,
61
+ get_node_times,
62
+ get_path,
59
63
  load_newick,
60
64
  load_nexus,
61
65
  set_features,
@@ -100,6 +104,7 @@ __all__ = [
100
104
  "Tree",
101
105
  "compute_mean_leaf_pairwise_distance",
102
106
  "compute_sackin_index",
107
+ "count_hops",
103
108
  "dump_newick",
104
109
  "generate_trees",
105
110
  "get_BD_events",
@@ -112,12 +117,15 @@ __all__ = [
112
117
  "get_FBD_events",
113
118
  "get_mrca",
114
119
  "get_mutation_id",
120
+ "get_node_ages",
115
121
  "get_node_depth_levels",
116
122
  "get_node_depths",
117
123
  "get_node_height_levels",
118
124
  "get_node_heights",
119
125
  "get_node_leaf_counts",
120
126
  "get_node_state",
127
+ "get_node_times",
128
+ "get_path",
121
129
  "load_newick",
122
130
  "load_nexus",
123
131
  "set_features",
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ from collections.abc import Mapping
2
3
  from dataclasses import dataclass
3
4
  from typing import Any, Literal, overload
4
5
 
@@ -30,7 +31,7 @@ Color = str | tuple[float, float, float] | tuple[float, float, float, float]
30
31
  def draw_tree(
31
32
  tree: Tree,
32
33
  ax: Axes | None = None,
33
- colors: Color | dict[Tree, Color] = "black",
34
+ colors: Color | Mapping[Tree, Color] = "black",
34
35
  backward_time: bool = False,
35
36
  branch_kwargs: dict[str, Any] | None = None,
36
37
  sampled_ancestor_kwargs: dict[str, Any] | None = None,
@@ -44,8 +45,8 @@ def draw_tree(
44
45
  The phylogenetic tree to draw.
45
46
  ax : Axes | None, optional
46
47
  The matplotlib Axes to draw on. If None, uses the current Axes.
47
- colors : Color | dict[Tree, Color], optional
48
- A single color for all branches or a dictionary mapping each node to a color.
48
+ colors : Color | Mapping[Tree, Color], optional
49
+ A single color for all branches or a mapping from each node to a color.
49
50
  backward_time : bool, optional
50
51
  If True, the x-axis is inverted to represent time going backward.
51
52
  branch_kwargs : dict[str, Any] | None, optional
@@ -67,7 +68,7 @@ def draw_tree(
67
68
  if "marker" not in sampled_ancestor_kwargs:
68
69
  sampled_ancestor_kwargs["marker"] = "o"
69
70
 
70
- if not isinstance(colors, dict):
71
+ if not isinstance(colors, Mapping):
71
72
  colors = {node: colors for node in tree}
72
73
 
73
74
  xs = (
@@ -141,7 +142,7 @@ def draw_dated_tree(
141
142
  tree: Tree,
142
143
  calibration_nodes: tuple[CalibrationNode, CalibrationNode],
143
144
  ax: Axes | None = None,
144
- colors: Color | dict[Tree, Color] = "black",
145
+ colors: Color | Mapping[Tree, Color] = "black",
145
146
  branch_kwargs: dict[str, Any] | None = None,
146
147
  ) -> Axes:
147
148
  """
@@ -155,8 +156,8 @@ def draw_dated_tree(
155
156
  Two calibration nodes defining the mapping from depth to date.
156
157
  ax : Axes | None, optional
157
158
  The matplotlib Axes to draw on. If None, uses the current Axes.
158
- colors : Color | dict[Tree, Color], optional
159
- A single color for all branches or a dictionary mapping each node to a color.
159
+ colors : Color | Mapping[Tree, Color], optional
160
+ A single color for all branches or a mapping from each node to a color.
160
161
  branch_kwargs : dict[str, Any] | None, optional
161
162
  Additional keyword arguments to pass to the branch drawing functions.
162
163
 
@@ -170,7 +171,7 @@ def draw_dated_tree(
170
171
  if branch_kwargs is None:
171
172
  branch_kwargs = {}
172
173
 
173
- if not isinstance(colors, dict):
174
+ if not isinstance(colors, Mapping):
174
175
  colors = {node: colors for node in tree}
175
176
 
176
177
  xs = {
@@ -224,9 +225,9 @@ def _init_colored_tree_categorical(
224
225
  color_by: str,
225
226
  ax: Axes | None = None,
226
227
  default_color: Color = "black",
227
- colormap: str | Colormap = "tab20",
228
+ colormap: str | Mapping[str, Color] | Colormap = "tab20",
228
229
  show_legend: bool = True,
229
- labels: dict[Any, str] | None = None,
230
+ labels: Mapping[Any, str] | None = None,
230
231
  legend_kwargs: dict[str, Any] | None = None,
231
232
  ) -> tuple[Axes, dict[Tree, Color]]:
232
233
  """
@@ -242,11 +243,14 @@ def _init_colored_tree_categorical(
242
243
  The matplotlib Axes to draw on. If None, uses the current Axes.
243
244
  default_color : Color, optional
244
245
  The color to use for nodes without the specified metadata.
245
- colormap : str | Colormap, optional
246
- The colormap to use for coloring categories. Defaults to 'tab20'.
246
+ colormap : str | Mapping[str, Color] | Colormap, optional
247
+ The colormap to use for coloring categories.
248
+ If a string, it is used to get a matplotlib colormap.
249
+ If a mapping, it maps category values to colors directly.
250
+ Defaults to 'tab20'.
247
251
  show_legend : bool, optional
248
252
  Whether to display a legend for the categories.
249
- labels : dict[Any, str] | None, optional
253
+ labels : Mapping[Any, str] | None, optional
250
254
  A mapping from category values to labels for the legend.
251
255
  legend_kwargs : dict[str, Any] | None, optional
252
256
  Additional keyword arguments to pass to the legend.
@@ -259,13 +263,16 @@ def _init_colored_tree_categorical(
259
263
  if ax is None:
260
264
  ax = plt.gca()
261
265
 
266
+ features = {node: node[color_by] for node in tree if color_by in node.metadata}
262
267
  if isinstance(colormap, str):
263
268
  colormap = plt.get_cmap(colormap)
269
+ if isinstance(colormap, Colormap):
270
+ feature_colors = {
271
+ f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features.values()))
272
+ }
273
+ else:
274
+ feature_colors = colormap
264
275
 
265
- features = {node: node[color_by] for node in tree if color_by in node.metadata}
266
- feature_colors = {
267
- f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features.values()))
268
- }
269
276
  colors = {
270
277
  node: feature_colors[features[node]] if node in features else default_color
271
278
  for node in tree
@@ -294,9 +301,9 @@ def draw_colored_tree_categorical(
294
301
  ax: Axes | None = None,
295
302
  backward_time: bool = False,
296
303
  default_color: Color = "black",
297
- colormap: str | Colormap = "tab20",
304
+ colormap: str | Mapping[str, Color] | Colormap = "tab20",
298
305
  show_legend: bool = True,
299
- labels: dict[Any, str] | None = None,
306
+ labels: Mapping[Any, str] | None = None,
300
307
  legend_kwargs: dict[str, Any] | None = None,
301
308
  branch_kwargs: dict[str, Any] | None = None,
302
309
  sampled_ancestor_kwargs: dict[str, Any] | None = None,
@@ -316,11 +323,14 @@ def draw_colored_tree_categorical(
316
323
  If True, the x-axis is inverted to represent time going backward.
317
324
  default_color : Color, optional
318
325
  The color to use for nodes without the specified metadata.
319
- colormap : str | Colormap, optional
320
- The colormap to use for coloring categories. Defaults to 'tab20'.
326
+ colormap : str | Mapping[str, Color] | Colormap, optional
327
+ The colormap to use for coloring categories.
328
+ If a string, it is used to get a matplotlib colormap.
329
+ If a mapping, it maps category values to colors directly.
330
+ Defaults to 'tab20'.
321
331
  show_legend : bool, optional
322
332
  Whether to display a legend for the categories.
323
- labels : dict[Any, str] | None, optional
333
+ labels : Mapping[Any, str] | None, optional
324
334
  A mapping from category values to labels for the legend.
325
335
  legend_kwargs : dict[str, Any] | None, optional
326
336
  Additional keyword arguments to pass to the legend.
@@ -360,9 +370,9 @@ def draw_colored_dated_tree_categorical(
360
370
  color_by: str,
361
371
  ax: Axes | None = None,
362
372
  default_color: Color = "black",
363
- colormap: str | Colormap = "tab20",
373
+ colormap: str | Mapping[str, Color] | Colormap = "tab20",
364
374
  show_legend: bool = True,
365
- labels: dict[Any, str] | None = None,
375
+ labels: Mapping[Any, str] | None = None,
366
376
  legend_kwargs: dict[str, Any] | None = None,
367
377
  branch_kwargs: dict[str, Any] | None = None,
368
378
  ) -> Axes:
@@ -381,11 +391,14 @@ def draw_colored_dated_tree_categorical(
381
391
  The matplotlib Axes to draw on. If None, uses the current Axes.
382
392
  default_color : Color, optional
383
393
  The color to use for nodes without the specified metadata.
384
- colormap : str | Colormap, optional
385
- The colormap to use for coloring categories. Defaults to 'tab20'.
394
+ colormap : str | Mapping[str, Color] | Colormap, optional
395
+ The colormap to use for coloring categories.
396
+ If a string, it is used to get a matplotlib colormap.
397
+ If a mapping, it maps category values to colors directly.
398
+ Defaults to 'tab20'.
386
399
  show_legend : bool, optional
387
400
  Whether to display a legend for the categories.
388
- labels : dict[Any, str] | None, optional
401
+ labels : Mapping[Any, str] | None, optional
389
402
  A mapping from category values to labels for the legend.
390
403
  legend_kwargs : dict[str, Any] | None, optional
391
404
  Additional keyword arguments to pass to the legend.
@@ -19,18 +19,22 @@ from phylogenie.skyline import (
19
19
  from phylogenie.treesimulator import EventType, Mutation
20
20
 
21
21
 
22
- def _eval_expression(expression: str, data: dict[str, Any]) -> Any:
22
+ def eval_expression(
23
+ expression: str, data: dict[str, Any], extra_globals: dict[str, Any] | None = None
24
+ ) -> Any:
25
+ if extra_globals is None:
26
+ extra_globals = {}
23
27
  return np.array(
24
28
  eval(
25
29
  expression,
26
- {"np": np, **{k: np.array(v) for k, v in data.items()}},
30
+ {"np": np, **{k: np.array(v) for k, v in data.items()}, **extra_globals},
27
31
  )
28
32
  ).tolist()
29
33
 
30
34
 
31
35
  def integer(x: cfg.Integer, data: dict[str, Any]) -> int:
32
36
  if isinstance(x, str):
33
- e = _eval_expression(x, data)
37
+ e = eval_expression(x, data)
34
38
  if isinstance(e, int):
35
39
  return e
36
40
  raise ValueError(
@@ -41,7 +45,7 @@ def integer(x: cfg.Integer, data: dict[str, Any]) -> int:
41
45
 
42
46
  def scalar(x: cfg.Scalar, data: dict[str, Any]) -> pgt.Scalar:
43
47
  if isinstance(x, str):
44
- e = _eval_expression(x, data)
48
+ e = eval_expression(x, data)
45
49
  if isinstance(e, pgt.Scalar):
46
50
  return e
47
51
  raise ValueError(
@@ -54,13 +58,13 @@ def string(s: Any, data: dict[str, Any]) -> str:
54
58
  if not isinstance(s, str):
55
59
  return str(s)
56
60
  return re.sub(
57
- r"\{([^{}]+)\}", lambda match: str(_eval_expression(match.group(1), data)), s
61
+ r"\{([^{}]+)\}", lambda match: str(eval_expression(match.group(1), data)), s
58
62
  ) # Match content inside curly braces
59
63
 
60
64
 
61
65
  def many_scalars(x: cfg.ManyScalars, data: dict[str, Any]) -> pgt.ManyScalars:
62
66
  if isinstance(x, str):
63
- e = _eval_expression(x, data)
67
+ e = eval_expression(x, data)
64
68
  if tg.is_many_scalars(e):
65
69
  return e
66
70
  raise ValueError(
@@ -73,7 +77,7 @@ def one_or_many_scalars(
73
77
  x: cfg.OneOrManyScalars, data: dict[str, Any]
74
78
  ) -> pgt.OneOrManyScalars:
75
79
  if isinstance(x, str):
76
- e = _eval_expression(x, data)
80
+ e = eval_expression(x, data)
77
81
  if tg.is_one_or_many_scalars(e):
78
82
  return e
79
83
  raise ValueError(
@@ -99,7 +103,7 @@ def skyline_vector(
99
103
  x: cfg.SkylineVector, data: dict[str, Any]
100
104
  ) -> SkylineVectorCoercible:
101
105
  if isinstance(x, str):
102
- e = _eval_expression(x, data)
106
+ e = eval_expression(x, data)
103
107
  if tg.is_one_or_many_scalars(e):
104
108
  return e
105
109
  raise ValueError(
@@ -114,7 +118,7 @@ def skyline_vector(
114
118
 
115
119
  change_times = many_scalars(x.change_times, data)
116
120
  if isinstance(x.value, str):
117
- e = _eval_expression(x.value, data)
121
+ e = eval_expression(x.value, data)
118
122
  if tg.is_many_one_or_many_scalars(e):
119
123
  value = e
120
124
  else:
@@ -142,7 +146,7 @@ def one_or_many_2D_scalars(
142
146
  x: cfg.OneOrMany2DScalars, data: dict[str, Any]
143
147
  ) -> pgt.OneOrMany2DScalars:
144
148
  if isinstance(x, str):
145
- e = _eval_expression(x, data)
149
+ e = eval_expression(x, data)
146
150
  if tg.is_one_or_many_2D_scalars(e):
147
151
  return e
148
152
  raise ValueError(
@@ -160,7 +164,7 @@ def skyline_matrix(
160
164
  return None
161
165
 
162
166
  if isinstance(x, str):
163
- e = _eval_expression(x, data)
167
+ e = eval_expression(x, data)
164
168
  if tg.is_one_or_many_2D_scalars(e):
165
169
  return e
166
170
  raise ValueError(
@@ -175,7 +179,7 @@ def skyline_matrix(
175
179
 
176
180
  change_times = many_scalars(x.change_times, data)
177
181
  if isinstance(x.value, str):
178
- e = _eval_expression(x.value, data)
182
+ e = eval_expression(x.value, data)
179
183
  if tg.is_many_one_or_many_2D_scalars(e):
180
184
  value = e
181
185
  else:
@@ -213,7 +217,7 @@ def distribution(x: cfg.Distribution, data: dict[str, Any]) -> cfg.Distribution:
213
217
  args = x.args
214
218
  for arg_name, arg_value in args.items():
215
219
  if isinstance(arg_value, str):
216
- args[arg_name] = _eval_expression(arg_value, data)
220
+ args[arg_name] = eval_expression(arg_value, data)
217
221
  return cfg.Distribution(type=x.type, **args)
218
222
 
219
223
 
@@ -12,6 +12,7 @@ from phylogenie.generators.configs import Distribution
12
12
  from phylogenie.generators.dataset import DatasetGenerator, DataType
13
13
  from phylogenie.generators.factories import (
14
14
  data,
15
+ eval_expression,
15
16
  integer,
16
17
  mutations,
17
18
  scalar,
@@ -57,6 +58,7 @@ class TreeDatasetGenerator(DatasetGenerator):
57
58
  timeout: float = np.inf
58
59
  node_features: list[Feature] | None = None
59
60
  acceptance_criterion: str | None = None
61
+ logs: dict[str, str] | None = None
60
62
 
61
63
  @abstractmethod
62
64
  def _get_events(self, data: dict[str, Any]) -> list[Event]: ...
@@ -77,10 +79,21 @@ class TreeDatasetGenerator(DatasetGenerator):
77
79
  acceptance_criterion: None | Callable[[Tree], bool] = (
78
80
  None
79
81
  if self.acceptance_criterion is None
80
- else lambda tree: eval(
81
- self.acceptance_criterion, {}, {"tree": tree} # pyright: ignore
82
+ else lambda tree: eval_expression(
83
+ self.acceptance_criterion, # pyright: ignore
84
+ data,
85
+ {"tree": tree},
82
86
  )
83
87
  )
88
+ logs: None | dict[str, Callable[[Tree], Any]] = (
89
+ None
90
+ if self.logs is None
91
+ else {
92
+ key: lambda tree, expr=expr: eval_expression(expr, data, {"tree": tree})
93
+ for key, expr in self.logs.items()
94
+ }
95
+ )
96
+
84
97
  return simulate_tree(
85
98
  events=events,
86
99
  n_tips=None if self.n_tips is None else integer(self.n_tips, data),
@@ -92,6 +105,7 @@ class TreeDatasetGenerator(DatasetGenerator):
92
105
  seed=seed,
93
106
  timeout=self.timeout,
94
107
  acceptance_criterion=acceptance_criterion,
108
+ logs=logs,
95
109
  )
96
110
 
97
111
  def generate_one(
@@ -111,7 +125,7 @@ class TreeDatasetGenerator(DatasetGenerator):
111
125
  dump_newick(tree, f"{filename}.nwk")
112
126
  break
113
127
  except TimeoutError:
114
- print("Simulation timed out, retrying with different parameters...")
128
+ print("Simulation timed out. Retrying with different parameters...")
115
129
  return d | metadata
116
130
 
117
131
 
@@ -1,4 +1,4 @@
1
- from collections.abc import Iterator
1
+ from collections.abc import Iterable, Iterator
2
2
  from dataclasses import dataclass
3
3
  from datetime import date
4
4
 
@@ -11,16 +11,23 @@ class Sequence:
11
11
  chars: str
12
12
  time: float | date | None = None
13
13
 
14
+ def __len__(self) -> int:
15
+ return len(self.chars)
16
+
14
17
 
15
18
  class MSA:
16
- def __init__(self, sequences: list[Sequence]):
17
- self.sequences = sequences
18
- lengths = {len(sequence.chars) for sequence in sequences}
19
+ def __init__(self, sequences: Iterable[Sequence]):
20
+ self._sequences = sequences
21
+ lengths = {len(sequence) for sequence in sequences}
19
22
  if len(lengths) > 1:
20
23
  raise ValueError(
21
24
  f"All sequences in the alignment must have the same length (got lengths: {lengths})"
22
25
  )
23
26
 
27
+ @property
28
+ def sequences(self) -> tuple[Sequence, ...]:
29
+ return tuple(self._sequences)
30
+
24
31
  @property
25
32
  def ids(self) -> list[str]:
26
33
  return [sequence.id for sequence in self.sequences]
@@ -25,6 +25,7 @@ from phylogenie.treesimulator.tree import Tree
25
25
  from phylogenie.treesimulator.utils import (
26
26
  compute_mean_leaf_pairwise_distance,
27
27
  compute_sackin_index,
28
+ count_hops,
28
29
  get_distance,
29
30
  get_mrca,
30
31
  get_node_ages,
@@ -66,6 +67,7 @@ __all__ = [
66
67
  "Tree",
67
68
  "compute_mean_leaf_pairwise_distance",
68
69
  "compute_sackin_index",
70
+ "count_hops",
69
71
  "get_distance",
70
72
  "get_mrca",
71
73
  "get_node_ages",
@@ -25,6 +25,7 @@ def simulate_tree(
25
25
  seed: int | None = None,
26
26
  timeout: float = np.inf,
27
27
  acceptance_criterion: Callable[[Tree], bool] | None = None,
28
+ logs: dict[str, Callable[[Tree], Any]] | None = None,
28
29
  ) -> tuple[Tree, dict[str, Any]]:
29
30
  if (max_time != np.inf) == (n_tips is not None):
30
31
  raise ValueError("Exactly one of max_time or n_tips must be specified.")
@@ -85,7 +86,13 @@ def simulate_tree(
85
86
  model.sample(individual, current_time, True)
86
87
 
87
88
  tree = model.get_sampled_tree()
88
- if acceptance_criterion is None or acceptance_criterion(tree):
89
+
90
+ if acceptance_criterion is not None and not acceptance_criterion(tree):
91
+ continue
92
+
93
+ if logs is not None:
94
+ for key, func in logs.items():
95
+ metadata[key] = func(tree)
89
96
  return (tree, metadata)
90
97
 
91
98
 
@@ -102,6 +109,7 @@ def generate_trees(
102
109
  n_jobs: int = -1,
103
110
  timeout: float = np.inf,
104
111
  acceptance_criterion: Callable[[Tree], bool] | None = None,
112
+ logs: dict[str, Callable[[Tree], Any]] | None = None,
105
113
  ) -> pd.DataFrame:
106
114
  if isinstance(output_dir, str):
107
115
  output_dir = Path(output_dir)
@@ -121,6 +129,7 @@ def generate_trees(
121
129
  seed=seed,
122
130
  timeout=timeout,
123
131
  acceptance_criterion=acceptance_criterion,
132
+ logs=logs,
124
133
  )
125
134
  metadata["file_id"] = i
126
135
  if node_features is not None:
@@ -128,7 +137,7 @@ def generate_trees(
128
137
  dump_newick(tree, output_dir / f"{i}.nwk")
129
138
  return metadata
130
139
  except TimeoutError:
131
- print("Simulation timed out, retrying with a different seed...")
140
+ print("Simulation timed out. Retrying with a different seed...")
132
141
  seed += 1
133
142
 
134
143
  rng = default_rng(seed)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phylogenie
3
- Version: 3.1.11
3
+ Version: 3.1.18
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
File without changes
File without changes
File without changes