phylogenie 2.0.14__py3-none-any.whl → 2.1.1__py3-none-any.whl

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.
@@ -1,86 +1,123 @@
1
+ import os
1
2
  from collections.abc import Sequence
2
3
 
4
+ import joblib
3
5
  import numpy as np
4
6
  from numpy.random import default_rng
7
+ from tqdm import tqdm
5
8
 
6
- from phylogenie.skyline import SkylineParameterLike
9
+ from phylogenie.io import dump_newick
7
10
  from phylogenie.tree import Tree
8
- from phylogenie.treesimulator.events import Event, get_contact_tracing_events
9
- from phylogenie.treesimulator.model import Model
11
+ from phylogenie.treesimulator.model import Event, Model
12
+
13
+ MAX_TIPS = 2**32
10
14
 
11
15
 
12
16
  def simulate_tree(
13
17
  events: Sequence[Event],
14
18
  min_tips: int = 1,
15
- max_tips: int | None = None,
19
+ max_tips: int = MAX_TIPS,
16
20
  max_time: float = np.inf,
17
21
  init_state: str | None = None,
18
22
  sampling_probability_at_present: float = 0.0,
19
- notification_probability: float = 0,
20
- max_notified_contacts: int = 1,
21
- samplable_states_after_notification: Sequence[str] | None = None,
22
- sampling_rate_after_notification: SkylineParameterLike = np.inf,
23
- contacts_removal_probability: SkylineParameterLike = 1,
24
- max_tries: int | None = None,
25
23
  seed: int | None = None,
26
24
  ) -> Tree | None:
27
- rng = default_rng(seed)
28
-
29
- if max_tips is None and max_time == np.inf:
30
- raise ValueError("Either max_tips or max_time must be specified.")
25
+ if max_time == np.inf and max_tips == MAX_TIPS:
26
+ raise ValueError("Either max_time or max_tips must be specified.")
31
27
 
32
- root_states = list({e.state for e in events})
28
+ if max_time == np.inf and sampling_probability_at_present:
29
+ raise ValueError(
30
+ "sampling_probability_at_present cannot be set when max_time is infinite."
31
+ )
33
32
 
34
- if notification_probability:
35
- events = get_contact_tracing_events(
36
- events,
37
- samplable_states_after_notification,
38
- sampling_rate_after_notification,
39
- contacts_removal_probability,
33
+ states = {e.state for e in events if e.state}
34
+ if init_state is None and len(states) > 1:
35
+ raise ValueError(
36
+ "Init state must be provided for models with more than one state."
40
37
  )
38
+ elif init_state is None:
39
+ (init_state,) = states
40
+ elif init_state not in states:
41
+ raise ValueError(f"Init state {init_state} not found in event states: {states}")
41
42
 
42
- n_tries = 0
43
- while max_tries is None or n_tries < max_tries:
44
- root_state = init_state if init_state is not None else rng.choice(root_states)
45
- model = Model(root_state, max_notified_contacts, notification_probability, rng)
43
+ rng = default_rng(seed)
44
+ while True:
45
+ model = Model(init_state, events)
46
46
  current_time = 0.0
47
47
  change_times = sorted(set(t for e in events for t in e.rate.change_times))
48
48
  next_change_time = change_times.pop(0) if change_times else np.inf
49
- n_tips = None if max_tips is None else rng.integers(min_tips, max_tips + 1)
50
49
 
51
- while current_time < max_time and (n_tips is None or model.n_sampled < n_tips):
50
+ target_n_tips = rng.integers(min_tips, max_tips) if max_time == np.inf else None
51
+ while current_time < max_time:
52
+ events = model.events
52
53
  rates = [e.get_propensity(model, current_time) for e in events]
53
54
 
54
55
  instantaneous_events = [e for e, r in zip(events, rates) if r == np.inf]
55
56
  if instantaneous_events:
56
57
  event = instantaneous_events[rng.integers(len(instantaneous_events))]
57
- event.apply(model, current_time)
58
+ event.apply(model, current_time, rng)
58
59
  continue
59
60
 
60
- if not any(rates):
61
+ if (
62
+ not any(rates)
63
+ or model.n_sampled > max_tips
64
+ or target_n_tips is not None
65
+ and model.n_sampled >= target_n_tips
66
+ ):
61
67
  break
62
68
 
63
69
  time_step = rng.exponential(1 / sum(rates))
70
+ if current_time + time_step >= next_change_time:
71
+ current_time = next_change_time
72
+ next_change_time = change_times.pop(0) if change_times else np.inf
73
+ continue
64
74
  if current_time + time_step >= max_time:
65
75
  current_time = max_time
66
76
  break
67
77
  current_time += time_step
68
78
 
69
- if current_time >= next_change_time:
70
- current_time = next_change_time
71
- next_change_time = change_times.pop(0) if change_times else np.inf
72
- continue
73
-
74
79
  event_idx = np.searchsorted(np.cumsum(rates) / sum(rates), rng.random())
75
- events[int(event_idx)].apply(model, current_time)
80
+ events[int(event_idx)].apply(model, current_time, rng)
76
81
 
77
82
  for individual in model.get_population():
78
83
  if rng.random() < sampling_probability_at_present:
79
- model.sample(individual, current_time, 1)
84
+ model.sample(individual, current_time, True)
80
85
 
81
- if model.n_sampled >= min_tips and (
82
- max_tips is None or model.n_sampled <= max_tips
83
- ):
86
+ if min_tips <= model.n_sampled <= max_tips:
84
87
  return model.get_sampled_tree()
85
88
 
86
- n_tries += 1
89
+
90
+ def generate_trees(
91
+ output_dir: str,
92
+ n_trees: int,
93
+ events: list[Event],
94
+ min_tips: int = 1,
95
+ max_tips: int = 2**32,
96
+ max_time: float = np.inf,
97
+ init_state: str | None = None,
98
+ sampling_probability_at_present: float = 0.0,
99
+ seed: int | None = None,
100
+ n_jobs: int = -1,
101
+ ) -> None:
102
+ if os.path.exists(output_dir):
103
+ raise FileExistsError(f"Output directory {output_dir} already exists")
104
+ os.mkdir(output_dir)
105
+
106
+ rng = default_rng(seed)
107
+ jobs = joblib.Parallel(n_jobs=n_jobs, return_as="generator_unordered")(
108
+ joblib.delayed(simulate_tree)(
109
+ events=events,
110
+ min_tips=min_tips,
111
+ max_tips=max_tips,
112
+ max_time=max_time,
113
+ init_state=init_state,
114
+ sampling_probability_at_present=sampling_probability_at_present,
115
+ seed=int(rng.integers(2**32)),
116
+ )
117
+ for _ in range(n_trees)
118
+ )
119
+ for i, tree in tqdm(
120
+ enumerate(jobs), total=n_trees, desc=f"Generating trees in {output_dir}..."
121
+ ):
122
+ if tree is not None:
123
+ dump_newick(tree, os.path.join(output_dir, f"{i}.nwk"))
@@ -1,62 +1,74 @@
1
+ from abc import ABC, abstractmethod
1
2
  from collections import defaultdict
2
- from dataclasses import dataclass, field
3
- from typing import ClassVar
3
+ from collections.abc import Sequence
4
+ from copy import deepcopy
5
+ from dataclasses import dataclass
6
+ from typing import Any
4
7
 
5
- from numpy.random import Generator, default_rng
8
+ import numpy as np
9
+ from numpy.random import Generator
6
10
 
11
+ from phylogenie.skyline import SkylineParameterLike, skyline_parameter
7
12
  from phylogenie.tree import Tree
8
13
 
9
- CT_POSTFIX = "-CT"
10
14
 
15
+ class Event(ABC):
16
+ def __init__(self, state: str, rate: SkylineParameterLike):
17
+ self.state = state
18
+ self.rate = skyline_parameter(rate)
11
19
 
12
- def get_CT_state(state: str) -> str:
13
- return f"{state}{CT_POSTFIX}"
20
+ def draw_individual(self, model: "Model", rng: Generator) -> int:
21
+ return rng.choice(model.get_population(self.state))
14
22
 
23
+ def get_propensity(self, model: "Model", time: float) -> float:
24
+ n_individuals = model.count_individuals(self.state)
25
+ rate = self.rate.get_value_at_time(time)
26
+ if rate == np.inf and not n_individuals:
27
+ return 0
28
+ return rate * n_individuals
15
29
 
16
- def is_CT_state(state: str) -> bool:
17
- return state.endswith(CT_POSTFIX)
30
+ @abstractmethod
31
+ def apply(self, model: "Model", time: float, rng: Generator) -> None: ...
18
32
 
19
33
 
20
34
  @dataclass
21
35
  class Individual:
36
+ id: int
22
37
  node: Tree
23
38
  state: str
24
- id: int = field(init=False)
25
- _id_counter: ClassVar[int] = 0
26
-
27
- def __post_init__(self):
28
- Individual._id_counter += 1
29
- self.id = Individual._id_counter
30
39
 
31
40
 
32
41
  class Model:
33
- def __init__(
34
- self,
35
- init_state: str,
36
- max_notified_contacts: int = 1,
37
- notification_probability: float = 0,
38
- rng: int | Generator | None = None,
39
- ):
42
+ def __init__(self, init_state: str, events: Sequence[Event]):
40
43
  self._next_node_id = 0
44
+ self._next_individual_id = 0
41
45
  self._population: dict[int, Individual] = {}
42
46
  self._states: dict[str, set[int]] = defaultdict(set)
43
- self._contacts: dict[int, list[Individual]] = defaultdict(list)
44
47
  self._sampled: set[str] = set()
45
48
  self._tree = self._get_new_individual(init_state).node
46
- self._max_notified_contacts = max_notified_contacts
47
- self._notification_probability = notification_probability
48
- self._rng = rng if isinstance(rng, Generator) else default_rng(rng)
49
+ self._events = list(events)
50
+ self.context: dict[str, Any] = {}
49
51
 
50
52
  @property
51
53
  def n_sampled(self) -> int:
52
54
  return len(self._sampled)
53
55
 
56
+ @property
57
+ def events(self) -> tuple[Event, ...]:
58
+ return tuple(self._events)
59
+
60
+ def add_event(self, event: Event) -> None:
61
+ self._events.append(event)
62
+
54
63
  def _get_new_node(self, state: str) -> Tree:
55
64
  self._next_node_id += 1
56
65
  return Tree(f"{self._next_node_id}|{state}")
57
66
 
58
67
  def _get_new_individual(self, state: str) -> Individual:
59
- individual = Individual(self._get_new_node(state), state)
68
+ self._next_individual_id += 1
69
+ individual = Individual(
70
+ self._next_individual_id, self._get_new_node(state), state
71
+ )
60
72
  self._population[individual.id] = individual
61
73
  self._states[state].add(individual.id)
62
74
  return individual
@@ -77,9 +89,8 @@ class Model:
77
89
  def remove(self, id: int, time: float) -> None:
78
90
  individual = self._population[id]
79
91
  self._set_branch_length(individual.node, time)
80
- state = individual.state
81
92
  self._population.pop(id)
82
- self._states[state].remove(id)
93
+ self._states[individual.state].remove(id)
83
94
 
84
95
  def migrate(self, id: int, state: str, time: float) -> None:
85
96
  individual = self._population[id]
@@ -88,17 +99,16 @@ class Model:
88
99
  self._states[state].add(id)
89
100
  self._stem(individual, time)
90
101
 
91
- def birth_from(self, id: int, state: str, time: float) -> None:
102
+ def birth_from(self, id: int, state: str, time: float) -> int:
92
103
  individual = self._population[id]
93
104
  new_individual = self._get_new_individual(state)
94
105
  individual.node.add_child(new_individual.node)
95
106
  self._stem(individual, time)
96
- self._contacts[id].append(new_individual)
97
- self._contacts[new_individual.id].append(individual)
107
+ return new_individual.id
98
108
 
99
- def sample(self, id: int, time: float, removal_probability: float) -> None:
109
+ def sample(self, id: int, time: float, removal: bool) -> None:
100
110
  individual = self._population[id]
101
- if self._rng.random() < removal_probability:
111
+ if removal:
102
112
  self._sampled.add(individual.node.id)
103
113
  self.remove(id, time)
104
114
  else:
@@ -108,47 +118,38 @@ class Model:
108
118
  individual.node.add_child(sample_node)
109
119
  self._stem(individual, time)
110
120
 
111
- for contact in self._contacts[id][-self._max_notified_contacts :]:
112
- if (
113
- contact.id in self._population
114
- and not is_CT_state(contact.state)
115
- and self._rng.random() < self._notification_probability
116
- ):
117
- self.migrate(contact.id, get_CT_state(contact.state), time)
121
+ def get_state(self, id: int) -> str:
122
+ return self._population[id].state
118
123
 
119
124
  def get_sampled_tree(self) -> Tree:
120
- tree = self._tree.copy()
125
+ tree = deepcopy(self._tree)
121
126
  for node in list(tree.postorder_traversal()):
122
127
  if node.id not in self._sampled and not node.children:
123
128
  if node.parent is None:
124
129
  raise ValueError("No samples in the tree.")
125
130
  else:
126
- node.parent.children.remove(node)
131
+ node.parent.remove_child(node)
127
132
  elif len(node.children) == 1:
128
133
  (child,) = node.children
129
- child.parent = node.parent
134
+ child.set_parent(node.parent)
130
135
  assert child.branch_length is not None
131
136
  assert node.branch_length is not None
132
137
  child.branch_length += node.branch_length
133
138
  if node.parent is None:
134
139
  return child
135
140
  else:
136
- node.parent.children.append(child)
137
- node.parent.children.remove(node)
141
+ node.parent.remove_child(node)
138
142
  return tree
139
143
 
140
144
  def get_full_tree(self) -> Tree:
141
- return self._tree.copy()
142
-
143
- def get_random_individual(self, state: str | None = None) -> int:
144
- if state is None:
145
- return self._rng.choice(list(self._population))
146
- return self._rng.choice(list(self._states[state]))
145
+ return deepcopy(self._tree)
147
146
 
148
- def get_population(self) -> list[int]:
149
- return list(self._population)
147
+ def get_population(self, states: str | None = None) -> list[int]:
148
+ if states is None:
149
+ return list(self._population)
150
+ return list(self._states[states])
150
151
 
151
- def count_individuals(self, state: str | None = None) -> int:
152
- if state is None:
152
+ def count_individuals(self, states: str | None = None) -> int:
153
+ if states is None:
153
154
  return len(self._population)
154
- return len(self._states[state])
155
+ return len(self._states[states])
phylogenie/typings.py CHANGED
@@ -16,6 +16,5 @@ Many2DScalars = Many2D[Scalar]
16
16
  Many3DScalars = Many3D[Scalar]
17
17
 
18
18
  Vector1D = list[Scalar]
19
- IntVector1D = list[int]
20
19
  Vector2D = list[Vector1D]
21
20
  Vector3D = list[Vector2D]
phylogenie/utils.py ADDED
@@ -0,0 +1,17 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class StrictBaseModel(BaseModel):
7
+ model_config = ConfigDict(extra="forbid")
8
+
9
+
10
+ class Distribution(BaseModel):
11
+ type: str
12
+ model_config = ConfigDict(extra="allow")
13
+
14
+ @property
15
+ def args(self) -> dict[str, Any]:
16
+ assert self.model_extra is not None
17
+ return self.model_extra
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phylogenie
3
- Version: 2.0.14
3
+ Version: 2.1.1
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Author: Gabriele Marino
6
6
  Author-email: gabmarino.8601@gmail.com
@@ -24,7 +24,6 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  [![AliSim](https://img.shields.io/badge/Powered%20by-AliSim-orange?style=flat-square)](https://iqtree.github.io/doc/AliSim)
26
26
  [![PyPI version](https://img.shields.io/pypi/v/phylogenie)](https://pypi.org/project/phylogenie/)
27
- [![PyPI downloads](https://shields.io/pypi/dm/phylogenie)](https://pypi.org/project/phylogenie/)
28
27
 
29
28
  Phylogenie is a [Python](https://www.python.org/) package designed to easily simulate phylogenetic datasets—such as trees and multiple sequence alignments (MSAs)—with minimal setup effort. Simply specify the distributions from which your parameters should be sampled, and Phylogenie will handle the rest!
30
29
 
@@ -0,0 +1,32 @@
1
+ phylogenie/__init__.py,sha256=T2mRLsYtoLlWt8GlxrrUnfXJ9XVioq7hTvVq3uJpwQI,2215
2
+ phylogenie/generators/__init__.py,sha256=zsOxy28-9j9alOQLIgrOAFfmM58NNHO_NEtW-KXQXAY,888
3
+ phylogenie/generators/alisim.py,sha256=dDqlSwLDbRE2u5SZlsq1mArobTBtuk0aeXY3m1N-bWA,2374
4
+ phylogenie/generators/configs.py,sha256=AiiFS6rpH9BPwDKCkT4SVrRzfLFFrwRCJM4CRj0Srdk,1072
5
+ phylogenie/generators/dataset.py,sha256=wYtb5fxjrM0bD9KVDlgZPpiR4ACezdKKfsmsXyc4__0,2755
6
+ phylogenie/generators/factories.py,sha256=14p1wvJtjdXgM4mPqV9vX34HYcKpemx9TPfemvoB5Wo,7265
7
+ phylogenie/generators/trees.py,sha256=EEj5KPuu7FSOoti_gGTmgWgiX68rEKFh57Hp6BDw8po,10049
8
+ phylogenie/generators/typeguards.py,sha256=yj4VkhOaUXJ2OrY-6zhOeY9C4yKIQxjZtk2d-vIxttQ,828
9
+ phylogenie/io.py,sha256=y7nQIvLgCvqELsXFKfm1GgKJO_saoQ-7zQpE3Kvajzc,3509
10
+ phylogenie/main.py,sha256=vtvSpQxBNlYABoFQ25czl-l3fIr4QRo3svWVd-jcArw,1170
11
+ phylogenie/msa.py,sha256=JDGyZUsAq6-m-SQjoCDjAkAZIxfgyl_PDIhdYn5HOow,2064
12
+ phylogenie/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ phylogenie/skyline/__init__.py,sha256=7pF4CUb4ZCLzNYJNhOjpuTOLTRhlK7L6ugfccNqjIGo,620
14
+ phylogenie/skyline/matrix.py,sha256=Gl8OgKjtieG0NwPYiPimKI36gefV8fm_OeorjdXxPTs,9146
15
+ phylogenie/skyline/parameter.py,sha256=EM9qlPt0JhMBy3TbztM0dj24BaGNEy8KWKdTObDKhbI,4644
16
+ phylogenie/skyline/vector.py,sha256=bJP7_FNX_Klt6wXqsyfj0KX3VNj6-dIhzCKSJuQcOV0,7115
17
+ phylogenie/tree.py,sha256=fmCLDNIls3VjY413b-khzj-2jlaQ9rJo7AFN1cz4zXk,2411
18
+ phylogenie/treesimulator/__init__.py,sha256=XG_xwETKWgDmCihqNUFCcMHtFg4WvZu5qbqWn9Dndt8,879
19
+ phylogenie/treesimulator/events/__init__.py,sha256=UGfvXOVJ_ZAkk_8sBPihjmxciiaEnXZEPFIY53sttWI,940
20
+ phylogenie/treesimulator/events/contact_tracing.py,sha256=_nJ85yhgGkeruQgMHvGpDYoyhheBf8M4LgZWiWdi5dY,4801
21
+ phylogenie/treesimulator/events/core.py,sha256=JokGmieAv2xEX7KsjBWZr05jHN1jB-XZbpxe9gwdbDA,7953
22
+ phylogenie/treesimulator/events/mutations.py,sha256=xkXUIppbLIWZqKwVf-hi7d-_pS42TG2EPVfJA_grxBg,3443
23
+ phylogenie/treesimulator/gillespie.py,sha256=TMDNKBkFwVyAEhBlbwxCTA61GuGwP-42HxpsAVXiU0s,4275
24
+ phylogenie/treesimulator/model.py,sha256=bWoFiO-99tTjHtueKPvGV9RSHQKYJ625XP1dvYbP1_Q,5454
25
+ phylogenie/typeguards.py,sha256=JtqmbEWJZBRHbWgCvcl6nrWm3VcBfzRbklbTBYHItn0,1325
26
+ phylogenie/typings.py,sha256=GknvAFXyiaWeeYJ8Lk5d6E2VHT-xW6ONEojYbtJYiB8,476
27
+ phylogenie/utils.py,sha256=pCg9ob0RpLUHwM49x4knKxL4FNPr3-EU_6zMXsvxtAg,370
28
+ phylogenie-2.1.1.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
29
+ phylogenie-2.1.1.dist-info/METADATA,sha256=Ay--Y2G4F1KwK9GgJFhtiTwGyKJ815JUCLYbXMyek1o,5375
30
+ phylogenie-2.1.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
31
+ phylogenie-2.1.1.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
32
+ phylogenie-2.1.1.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- phylogenie/__init__.py,sha256=4BytT42_M1K6T3W9eqHQCrKc6g0Lh5LTQxP8dGIJTsk,1915
2
- phylogenie/generators/__init__.py,sha256=VCpuvmOoY_N6p1h_Q0peYgGIIUIFLsvZT3T7vbHG6w0,1090
3
- phylogenie/generators/alisim.py,sha256=dDqlSwLDbRE2u5SZlsq1mArobTBtuk0aeXY3m1N-bWA,2374
4
- phylogenie/generators/configs.py,sha256=4jSBUZiFo2GacXWed5dy7lUEkaOWZkZG-KY9vHfhqGU,993
5
- phylogenie/generators/dataset.py,sha256=hbkN5McM4BKY7D0hLNaxdoAGsLHac6O-D4sgnZ0wFX4,2618
6
- phylogenie/generators/factories.py,sha256=0ckeAsKnPy69Vbdoi1rIyf6zRcqamz9VfSi0mAiTzds,6938
7
- phylogenie/generators/trees.py,sha256=jukaVXGcPGzDBEYMGJ1MKqWt4XbAB5EEfuHXDpwKTqM,9173
8
- phylogenie/generators/typeguards.py,sha256=yj4VkhOaUXJ2OrY-6zhOeY9C4yKIQxjZtk2d-vIxttQ,828
9
- phylogenie/io.py,sha256=ZXlofnSh7FX5UJiP0svRHrTraMSNgKa1GiAv0bMz7jU,2854
10
- phylogenie/main.py,sha256=vtvSpQxBNlYABoFQ25czl-l3fIr4QRo3svWVd-jcArw,1170
11
- phylogenie/msa.py,sha256=JDGyZUsAq6-m-SQjoCDjAkAZIxfgyl_PDIhdYn5HOow,2064
12
- phylogenie/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- phylogenie/skyline/__init__.py,sha256=7pF4CUb4ZCLzNYJNhOjpuTOLTRhlK7L6ugfccNqjIGo,620
14
- phylogenie/skyline/matrix.py,sha256=Gl8OgKjtieG0NwPYiPimKI36gefV8fm_OeorjdXxPTs,9146
15
- phylogenie/skyline/parameter.py,sha256=EM9qlPt0JhMBy3TbztM0dj24BaGNEy8KWKdTObDKhbI,4644
16
- phylogenie/skyline/vector.py,sha256=bJP7_FNX_Klt6wXqsyfj0KX3VNj6-dIhzCKSJuQcOV0,7115
17
- phylogenie/tree.py,sha256=dk8Sj1tqyGOunVO2crtIqb0LH-ws-PXqA8SuNcYfVHI,1738
18
- phylogenie/treesimulator/__init__.py,sha256=INPU9LrPdUmt3dYGzWDRoRKrPR9xENcHu44pJVUbyNA,525
19
- phylogenie/treesimulator/events.py,sha256=X3_0U9qqMpYgh6-7TwQEnlUipANkHz6QTCXlm-qXFQk,9524
20
- phylogenie/treesimulator/gillespie.py,sha256=NHiBLVlpKdcTPohMr6ehywbPeB8gnn6erEv2bkgvf4c,3268
21
- phylogenie/treesimulator/model.py,sha256=XpzAicmg2O6K0Trk5YolH-B_HJZxoSauF2wZOMqp-Iw,5559
22
- phylogenie/typeguards.py,sha256=JtqmbEWJZBRHbWgCvcl6nrWm3VcBfzRbklbTBYHItn0,1325
23
- phylogenie/typings.py,sha256=O1X6lGKTjJ2YJz3ApQ-rYb_tEJNUIcHdUIeYlSM4s5o,500
24
- phylogenie-2.0.14.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
25
- phylogenie-2.0.14.dist-info/METADATA,sha256=NXOQmoUHSTA2AjrXjt4VKSaM2KO6H2rBF7nrRuGK2f4,5473
26
- phylogenie-2.0.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
27
- phylogenie-2.0.14.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
28
- phylogenie-2.0.14.dist-info/RECORD,,