phylogenie 1.0.8__py3-none-any.whl → 2.0.0__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.
- phylogenie/generators/__init__.py +14 -0
- phylogenie/generators/alisim.py +71 -0
- phylogenie/generators/configs.py +41 -0
- phylogenie/{core → generators}/dataset.py +25 -23
- phylogenie/{core → generators}/factories.py +42 -52
- phylogenie/generators/trees.py +220 -0
- phylogenie/generators/typeguards.py +32 -0
- phylogenie/io.py +92 -0
- phylogenie/main.py +2 -2
- phylogenie/msa.py +72 -0
- phylogenie/skyline/matrix.py +62 -45
- phylogenie/skyline/vector.py +8 -6
- phylogenie/tree.py +53 -0
- phylogenie/treesimulator/__init__.py +21 -0
- phylogenie/treesimulator/events.py +256 -0
- phylogenie/treesimulator/gillespie.py +66 -0
- phylogenie/treesimulator/model.py +100 -0
- phylogenie/typings.py +0 -2
- {phylogenie-1.0.8.dist-info → phylogenie-2.0.0.dist-info}/METADATA +6 -18
- phylogenie-2.0.0.dist-info/RECORD +28 -0
- phylogenie/backend/__init__.py +0 -0
- phylogenie/backend/remaster/__init__.py +0 -21
- phylogenie/backend/remaster/generate.py +0 -187
- phylogenie/backend/remaster/reactions.py +0 -165
- phylogenie/backend/treesimulator.py +0 -163
- phylogenie/configs.py +0 -5
- phylogenie/core/__init__.py +0 -14
- phylogenie/core/configs.py +0 -37
- phylogenie/core/context/__init__.py +0 -4
- phylogenie/core/context/configs.py +0 -28
- phylogenie/core/context/distributions.py +0 -125
- phylogenie/core/context/factories.py +0 -54
- phylogenie/core/msas/__init__.py +0 -10
- phylogenie/core/msas/alisim.py +0 -35
- phylogenie/core/msas/base.py +0 -51
- phylogenie/core/trees/__init__.py +0 -11
- phylogenie/core/trees/base.py +0 -13
- phylogenie/core/trees/remaster/__init__.py +0 -3
- phylogenie/core/trees/remaster/configs.py +0 -14
- phylogenie/core/trees/remaster/factories.py +0 -26
- phylogenie/core/trees/remaster/generator.py +0 -177
- phylogenie/core/trees/treesimulator.py +0 -199
- phylogenie/core/typeguards.py +0 -32
- phylogenie-1.0.8.dist-info/RECORD +0 -39
- {phylogenie-1.0.8.dist-info → phylogenie-2.0.0.dist-info}/LICENSE.txt +0 -0
- {phylogenie-1.0.8.dist-info → phylogenie-2.0.0.dist-info}/WHEEL +0 -0
- {phylogenie-1.0.8.dist-info → phylogenie-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from numpy.random import Generator
|
|
4
|
+
|
|
5
|
+
from phylogenie.skyline import (
|
|
6
|
+
SkylineMatrixCoercible,
|
|
7
|
+
SkylineParameterLike,
|
|
8
|
+
SkylineVectorCoercible,
|
|
9
|
+
skyline_matrix,
|
|
10
|
+
skyline_parameter,
|
|
11
|
+
skyline_vector,
|
|
12
|
+
)
|
|
13
|
+
from phylogenie.treesimulator.model import Model
|
|
14
|
+
|
|
15
|
+
INFECTIOUS_STATE = "I"
|
|
16
|
+
EXPOSED_STATE = "E"
|
|
17
|
+
SUPERSPREADER_STATE = "S"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Event(ABC):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
rate: SkylineParameterLike,
|
|
24
|
+
state: str | None = None,
|
|
25
|
+
):
|
|
26
|
+
self.rate = skyline_parameter(rate)
|
|
27
|
+
self.state = state
|
|
28
|
+
|
|
29
|
+
def get_propensity(self, model: Model, time: float) -> float:
|
|
30
|
+
return self.rate.get_value_at_time(time) * model.count_leaves(self.state)
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def apply(self, rng: Generator, model: Model, time: float) -> None: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BirthEvent(Event):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
rate: SkylineParameterLike,
|
|
40
|
+
state: str | None = None,
|
|
41
|
+
child_state: str | None = None,
|
|
42
|
+
):
|
|
43
|
+
super().__init__(rate, state)
|
|
44
|
+
self.child_state = child_state
|
|
45
|
+
|
|
46
|
+
def apply(self, rng: Generator, model: Model, time: float) -> None:
|
|
47
|
+
node = model.get_random_leaf(self.state, rng)
|
|
48
|
+
model.add_child(node, time, True, self.child_state)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DeathEvent(Event):
|
|
52
|
+
def apply(self, rng: Generator, model: Model, time: float) -> None:
|
|
53
|
+
node = model.get_random_leaf(self.state, rng)
|
|
54
|
+
model.remove(node)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class MigrationEvent(Event):
|
|
58
|
+
def __init__(self, state: str, target_state: str, rate: SkylineParameterLike):
|
|
59
|
+
super().__init__(rate, state)
|
|
60
|
+
self.target_state = target_state
|
|
61
|
+
|
|
62
|
+
def apply(self, rng: Generator, model: Model, time: float) -> None:
|
|
63
|
+
node = model.get_random_leaf(self.state, rng)
|
|
64
|
+
model.add_child(node, time, False, self.target_state)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SamplingEvent(Event):
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
rate: SkylineParameterLike,
|
|
71
|
+
removal_probability: SkylineParameterLike,
|
|
72
|
+
state: str | None = None,
|
|
73
|
+
):
|
|
74
|
+
super().__init__(rate, state)
|
|
75
|
+
self.removal_probability = skyline_parameter(removal_probability)
|
|
76
|
+
|
|
77
|
+
def apply(self, rng: Generator, model: Model, time: float) -> None:
|
|
78
|
+
node = model.get_random_leaf(self.state, rng)
|
|
79
|
+
remove = rng.random() < self.removal_probability.get_value_at_time(time)
|
|
80
|
+
model.sample(node, time, remove)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_canonical_events(
|
|
84
|
+
sampling_rates: SkylineVectorCoercible,
|
|
85
|
+
birth_rates: SkylineVectorCoercible = 0,
|
|
86
|
+
death_rates: SkylineVectorCoercible = 0,
|
|
87
|
+
removal_probabilities: SkylineVectorCoercible = 0,
|
|
88
|
+
migration_rates: SkylineMatrixCoercible = 0,
|
|
89
|
+
birth_rates_among_states: SkylineMatrixCoercible = 0,
|
|
90
|
+
states: list[str] | None = None,
|
|
91
|
+
) -> list[Event]:
|
|
92
|
+
N = 1 if states is None else len(states)
|
|
93
|
+
|
|
94
|
+
birth_rates = skyline_vector(birth_rates, N)
|
|
95
|
+
death_rates = skyline_vector(death_rates, N)
|
|
96
|
+
sampling_rates = skyline_vector(sampling_rates, N)
|
|
97
|
+
removal_probabilities = skyline_vector(removal_probabilities, N)
|
|
98
|
+
if migration_rates and N == 1:
|
|
99
|
+
raise ValueError(f"Migration rates require multiple states (got {states}).")
|
|
100
|
+
if birth_rates_among_states and N == 1:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Birth rates among states require multiple states (got {states})."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
events: list[Event] = []
|
|
106
|
+
for i in range(N):
|
|
107
|
+
state = None if states is None else states[i]
|
|
108
|
+
events.append(BirthEvent(birth_rates[i], state, state))
|
|
109
|
+
events.append(DeathEvent(death_rates[i], state))
|
|
110
|
+
events.append(SamplingEvent(sampling_rates[i], removal_probabilities[i], state))
|
|
111
|
+
if N > 1:
|
|
112
|
+
migration_rates = skyline_matrix(migration_rates, N, N - 1)
|
|
113
|
+
birth_rates_among_states = skyline_matrix(
|
|
114
|
+
birth_rates_among_states, N, N - 1
|
|
115
|
+
)
|
|
116
|
+
assert states is not None
|
|
117
|
+
assert state is not None
|
|
118
|
+
for j, other_state in enumerate([s for s in states if s != state]):
|
|
119
|
+
events.append(MigrationEvent(state, other_state, migration_rates[i][j]))
|
|
120
|
+
events.append(
|
|
121
|
+
BirthEvent(birth_rates_among_states[i][j], state, other_state)
|
|
122
|
+
)
|
|
123
|
+
return [event for event in events if event.rate]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_epidemiological_events(
|
|
127
|
+
sampling_proportions: SkylineVectorCoercible = 1,
|
|
128
|
+
reproduction_numbers: SkylineVectorCoercible = 0,
|
|
129
|
+
become_uninfectious_rates: SkylineVectorCoercible = 0,
|
|
130
|
+
removal_probabilities: SkylineVectorCoercible = 1,
|
|
131
|
+
migration_rates: SkylineMatrixCoercible = 0,
|
|
132
|
+
reproduction_numbers_among_states: SkylineMatrixCoercible = 0,
|
|
133
|
+
states: list[str] | None = None,
|
|
134
|
+
) -> list[Event]:
|
|
135
|
+
N = 1 if states is None else len(states)
|
|
136
|
+
|
|
137
|
+
reproduction_numbers = skyline_vector(reproduction_numbers, N)
|
|
138
|
+
become_uninfectious_rates = skyline_vector(become_uninfectious_rates, N)
|
|
139
|
+
sampling_proportions = skyline_vector(sampling_proportions, N)
|
|
140
|
+
removal_probabilities = skyline_vector(removal_probabilities, N)
|
|
141
|
+
if N == 1 and reproduction_numbers_among_states:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Reproduction numbers among states require multiple states (got {states})."
|
|
144
|
+
)
|
|
145
|
+
reproduction_numbers_among_states = (
|
|
146
|
+
skyline_matrix(reproduction_numbers_among_states, N, N - 1) if N > 1 else 0
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
birth_rates = reproduction_numbers * become_uninfectious_rates
|
|
150
|
+
sampling_rates = become_uninfectious_rates * sampling_proportions
|
|
151
|
+
birth_rates_among_states = (
|
|
152
|
+
reproduction_numbers_among_states * become_uninfectious_rates
|
|
153
|
+
)
|
|
154
|
+
death_rates = become_uninfectious_rates - removal_probabilities * sampling_rates
|
|
155
|
+
|
|
156
|
+
return get_canonical_events(
|
|
157
|
+
states=states,
|
|
158
|
+
birth_rates=birth_rates,
|
|
159
|
+
death_rates=death_rates,
|
|
160
|
+
sampling_rates=sampling_rates,
|
|
161
|
+
removal_probabilities=removal_probabilities,
|
|
162
|
+
migration_rates=migration_rates,
|
|
163
|
+
birth_rates_among_states=birth_rates_among_states,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_FBD_events(
|
|
168
|
+
diversification: SkylineVectorCoercible = 0,
|
|
169
|
+
turnover: SkylineVectorCoercible = 0,
|
|
170
|
+
sampling_proportions: SkylineVectorCoercible = 1,
|
|
171
|
+
removal_probabilities: SkylineVectorCoercible = 0,
|
|
172
|
+
migration_rates: SkylineMatrixCoercible = 0,
|
|
173
|
+
diversification_between_types: SkylineMatrixCoercible = 0,
|
|
174
|
+
states: list[str] | None = None,
|
|
175
|
+
):
|
|
176
|
+
N = 1 if states is None else len(states)
|
|
177
|
+
|
|
178
|
+
diversification = skyline_vector(diversification, N)
|
|
179
|
+
turnover = skyline_vector(turnover, N)
|
|
180
|
+
sampling_proportions = skyline_vector(sampling_proportions, N)
|
|
181
|
+
removal_probabilities = skyline_vector(removal_probabilities, N)
|
|
182
|
+
if N == 1 and diversification_between_types:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"Diversification rates among states require multiple states (got {states})."
|
|
185
|
+
)
|
|
186
|
+
diversification_between_types = (
|
|
187
|
+
skyline_matrix(diversification_between_types, N, N - 1) if N > 1 else 0
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
birth_rates = diversification / (1 - turnover)
|
|
191
|
+
death_rates = turnover * birth_rates
|
|
192
|
+
sampling_rates = (
|
|
193
|
+
sampling_proportions
|
|
194
|
+
* death_rates
|
|
195
|
+
/ (1 - removal_probabilities * sampling_proportions)
|
|
196
|
+
)
|
|
197
|
+
birth_rates_among_states = diversification_between_types + death_rates
|
|
198
|
+
|
|
199
|
+
return get_canonical_events(
|
|
200
|
+
states=states,
|
|
201
|
+
birth_rates=birth_rates,
|
|
202
|
+
death_rates=death_rates,
|
|
203
|
+
sampling_rates=sampling_rates,
|
|
204
|
+
removal_probabilities=removal_probabilities,
|
|
205
|
+
migration_rates=migration_rates,
|
|
206
|
+
birth_rates_among_states=birth_rates_among_states,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get_BD_events(
|
|
211
|
+
reproduction_number: SkylineParameterLike,
|
|
212
|
+
infectious_period: SkylineParameterLike,
|
|
213
|
+
sampling_proportion: SkylineParameterLike = 1,
|
|
214
|
+
) -> list[Event]:
|
|
215
|
+
return get_epidemiological_events(
|
|
216
|
+
reproduction_numbers=reproduction_number,
|
|
217
|
+
become_uninfectious_rates=1 / infectious_period,
|
|
218
|
+
sampling_proportions=sampling_proportion,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_BDEI_events(
|
|
223
|
+
reproduction_number: SkylineParameterLike,
|
|
224
|
+
infectious_period: SkylineParameterLike,
|
|
225
|
+
incubation_period: SkylineParameterLike,
|
|
226
|
+
sampling_proportion: SkylineParameterLike = 1,
|
|
227
|
+
) -> list[Event]:
|
|
228
|
+
return get_epidemiological_events(
|
|
229
|
+
states=[EXPOSED_STATE, INFECTIOUS_STATE],
|
|
230
|
+
sampling_proportions=[0, sampling_proportion],
|
|
231
|
+
become_uninfectious_rates=[0, 1 / infectious_period],
|
|
232
|
+
reproduction_numbers_among_states=[[0], [reproduction_number]],
|
|
233
|
+
migration_rates=[[1 / incubation_period], [0]],
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_BDSS_events(
|
|
238
|
+
reproduction_number: SkylineParameterLike,
|
|
239
|
+
infectious_period: SkylineParameterLike,
|
|
240
|
+
superspreading_ratio: SkylineParameterLike,
|
|
241
|
+
superspreaders_proportion: SkylineParameterLike,
|
|
242
|
+
sampling_proportion: SkylineParameterLike = 1,
|
|
243
|
+
) -> list[Event]:
|
|
244
|
+
f_SS = superspreaders_proportion
|
|
245
|
+
r_SS = superspreading_ratio
|
|
246
|
+
R_0_IS = reproduction_number * f_SS / (1 + r_SS * f_SS - f_SS)
|
|
247
|
+
R_0_SI = (reproduction_number - r_SS * R_0_IS) * r_SS
|
|
248
|
+
R_0_S = r_SS * R_0_IS
|
|
249
|
+
R_0_I = R_0_SI / r_SS
|
|
250
|
+
return get_epidemiological_events(
|
|
251
|
+
states=[INFECTIOUS_STATE, SUPERSPREADER_STATE],
|
|
252
|
+
reproduction_numbers=[R_0_I, R_0_S],
|
|
253
|
+
reproduction_numbers_among_states=[[R_0_IS], [R_0_SI]],
|
|
254
|
+
become_uninfectious_rates=1 / infectious_period,
|
|
255
|
+
sampling_proportions=sampling_proportion,
|
|
256
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.random import default_rng
|
|
5
|
+
|
|
6
|
+
from phylogenie.tree import Tree
|
|
7
|
+
from phylogenie.treesimulator.events import Event
|
|
8
|
+
from phylogenie.treesimulator.model import Model
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def simulate_tree(
|
|
12
|
+
events: Sequence[Event],
|
|
13
|
+
min_tips: int = 1,
|
|
14
|
+
max_tips: int | None = None,
|
|
15
|
+
max_time: float = np.inf,
|
|
16
|
+
init_state: str | None = None,
|
|
17
|
+
sampling_probability_at_present: float = 0.0,
|
|
18
|
+
max_tries: int | None = None,
|
|
19
|
+
seed: int | None = None,
|
|
20
|
+
) -> Tree | None:
|
|
21
|
+
rng = default_rng(seed)
|
|
22
|
+
|
|
23
|
+
if max_tips is None and max_time == np.inf:
|
|
24
|
+
raise ValueError("Either max_tips or max_time must be specified.")
|
|
25
|
+
|
|
26
|
+
n_tries = 0
|
|
27
|
+
states = [e.state for e in events if e.state is not None]
|
|
28
|
+
init_state = (
|
|
29
|
+
init_state
|
|
30
|
+
if init_state is not None
|
|
31
|
+
else str(rng.choice(states)) if states else None
|
|
32
|
+
)
|
|
33
|
+
while max_tries is None or n_tries < max_tries:
|
|
34
|
+
model = Model(init_state)
|
|
35
|
+
current_time = 0.0
|
|
36
|
+
change_times = sorted(set(t for e in events for t in e.rate.change_times))
|
|
37
|
+
next_change_time = change_times.pop(0) if change_times else np.inf
|
|
38
|
+
n_tips = None if max_tips is None else rng.integers(min_tips, max_tips + 1)
|
|
39
|
+
|
|
40
|
+
while current_time < max_time and (n_tips is None or model.n_sampled < n_tips):
|
|
41
|
+
rates = [e.get_propensity(model, current_time) for e in events]
|
|
42
|
+
if not any(rates):
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
current_time += rng.exponential(1 / sum(rates))
|
|
46
|
+
if current_time >= max_time:
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
if current_time >= next_change_time:
|
|
50
|
+
current_time = next_change_time
|
|
51
|
+
next_change_time = change_times.pop(0) if change_times else np.inf
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
event_idx = np.searchsorted(np.cumsum(rates) / sum(rates), rng.random())
|
|
55
|
+
events[int(event_idx)].apply(rng, model, current_time)
|
|
56
|
+
|
|
57
|
+
for leaf in model.get_leaves():
|
|
58
|
+
if rng.random() < sampling_probability_at_present:
|
|
59
|
+
model.sample(leaf, current_time, True)
|
|
60
|
+
|
|
61
|
+
if model.n_sampled >= min_tips and (
|
|
62
|
+
max_tips is None or model.n_sampled <= max_tips
|
|
63
|
+
):
|
|
64
|
+
return model.get_sampled_tree()
|
|
65
|
+
|
|
66
|
+
n_tries += 1
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from numpy.random import Generator, default_rng
|
|
4
|
+
|
|
5
|
+
from phylogenie.tree import Tree
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Model:
|
|
9
|
+
def __init__(self, init_state: str | None = None):
|
|
10
|
+
self._next_id = 0
|
|
11
|
+
self._n_sampled = 0
|
|
12
|
+
self._leaves: dict[str, Tree] = {}
|
|
13
|
+
self._leaf2state: dict[str, str | None] = {}
|
|
14
|
+
self._state2leaves: dict[str | None, set[str]] = defaultdict(set)
|
|
15
|
+
self._tree = self._get_new_node(init_state, None)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def next_id(self) -> int:
|
|
19
|
+
self._next_id += 1
|
|
20
|
+
return self._next_id
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def n_sampled(self) -> int:
|
|
24
|
+
return self._n_sampled
|
|
25
|
+
|
|
26
|
+
def _get_new_node(self, state: str | None, branch_length: float | None) -> Tree:
|
|
27
|
+
id = str(self.next_id) if state is None else f"{self.next_id}|{state}"
|
|
28
|
+
node = Tree(id, branch_length)
|
|
29
|
+
if branch_length is None:
|
|
30
|
+
self._leaves[id] = node
|
|
31
|
+
self._leaf2state[id] = state
|
|
32
|
+
self._state2leaves[state].add(id)
|
|
33
|
+
return node
|
|
34
|
+
|
|
35
|
+
def remove(self, node_id: str) -> None:
|
|
36
|
+
self._state2leaves[self._leaf2state[node_id]].remove(node_id)
|
|
37
|
+
self._leaf2state.pop(node_id, None)
|
|
38
|
+
self._leaves.pop(node_id)
|
|
39
|
+
|
|
40
|
+
def add_child(
|
|
41
|
+
self,
|
|
42
|
+
node_id: str,
|
|
43
|
+
time: float,
|
|
44
|
+
stem: bool,
|
|
45
|
+
state: str | None,
|
|
46
|
+
branch_length: float | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
node = self._leaves[node_id]
|
|
49
|
+
if node.branch_length is not None:
|
|
50
|
+
raise ValueError("Cannot add a child to a node with a set branch length.")
|
|
51
|
+
node.add_child(self._get_new_node(state, branch_length))
|
|
52
|
+
if stem:
|
|
53
|
+
node.add_child(self._get_new_node(self._leaf2state[node.id], None))
|
|
54
|
+
node.branch_length = (
|
|
55
|
+
time if node.parent is None else time - node.parent.get_time()
|
|
56
|
+
)
|
|
57
|
+
self.remove(node_id)
|
|
58
|
+
|
|
59
|
+
def sample(self, node_id: str, time: float, remove: bool) -> None:
|
|
60
|
+
self.add_child(node_id, time, not remove, self._leaf2state[node_id], 0.0)
|
|
61
|
+
self._n_sampled += 1
|
|
62
|
+
|
|
63
|
+
def get_sampled_tree(self) -> Tree:
|
|
64
|
+
tree = self._tree.copy()
|
|
65
|
+
for node in list(tree.postorder_traversal()):
|
|
66
|
+
if node.branch_length is None or (
|
|
67
|
+
node.branch_length > 0 and not node.children
|
|
68
|
+
):
|
|
69
|
+
if node.parent is None:
|
|
70
|
+
raise ValueError("No samples in the tree.")
|
|
71
|
+
else:
|
|
72
|
+
node.parent.children.remove(node)
|
|
73
|
+
elif len(node.children) == 1:
|
|
74
|
+
(child,) = node.children
|
|
75
|
+
child.parent = node.parent
|
|
76
|
+
assert child.branch_length is not None
|
|
77
|
+
assert node.branch_length is not None
|
|
78
|
+
child.branch_length += node.branch_length
|
|
79
|
+
if node.parent is None:
|
|
80
|
+
return child
|
|
81
|
+
else:
|
|
82
|
+
node.parent.children.append(child)
|
|
83
|
+
node.parent.children.remove(node)
|
|
84
|
+
return tree
|
|
85
|
+
|
|
86
|
+
def get_random_leaf(
|
|
87
|
+
self, state: str | None = None, rng: int | Generator | None = None
|
|
88
|
+
) -> str:
|
|
89
|
+
rng = rng if isinstance(rng, Generator) else default_rng(rng)
|
|
90
|
+
if state is None:
|
|
91
|
+
return rng.choice(list(self._leaves))
|
|
92
|
+
return rng.choice(list(self._state2leaves[state]))
|
|
93
|
+
|
|
94
|
+
def get_leaves(self) -> list[str]:
|
|
95
|
+
return list(self._leaves)
|
|
96
|
+
|
|
97
|
+
def count_leaves(self, state: str | None = None) -> int:
|
|
98
|
+
if state is None:
|
|
99
|
+
return len(self._leaves)
|
|
100
|
+
return len(self._state2leaves[state])
|
phylogenie/typings.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: phylogenie
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Generate phylogenetic datasets with minimal setup effort
|
|
5
5
|
Author: Gabriele Marino
|
|
6
6
|
Author-email: gabmarino.8601@gmail.com
|
|
@@ -23,9 +23,9 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
[](https://github.com/evolbioinfo/treesimulator)
|
|
27
|
-
[](https://tgvaughan.github.io/remaster/)
|
|
28
26
|
[](https://iqtree.github.io/doc/AliSim)
|
|
27
|
+
[](https://pypi.org/project/phylogenie/)
|
|
28
|
+
[](https://pypi.org/project/phylogenie/)
|
|
29
29
|
|
|
30
30
|
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!
|
|
31
31
|
|
|
@@ -46,7 +46,7 @@ Phylogenie comes packed with useful features, including:
|
|
|
46
46
|
Simply specify the number of cores to use, and Phylogenie handles multiprocessing automatically.
|
|
47
47
|
|
|
48
48
|
- **Pre-implemented parameterizations** 🎯
|
|
49
|
-
Include canonical, fossilized birth-death, epidemiological, birth-death with exposed-infectious (BDEI), birth-death with superspreading (BDSS),
|
|
49
|
+
Include canonical, fossilized birth-death, epidemiological, birth-death with exposed-infectious (BDEI), birth-death with superspreading (BDSS), and more.
|
|
50
50
|
|
|
51
51
|
- **Skyline parameter support** 🪜
|
|
52
52
|
Support for piece-wise constant parameters.
|
|
@@ -54,9 +54,6 @@ Phylogenie comes packed with useful features, including:
|
|
|
54
54
|
- **Arithmetic operations on parameters** 🧮
|
|
55
55
|
Perform flexible arithmetic operations between parameters directly within the config file.
|
|
56
56
|
|
|
57
|
-
- **Support for common phylogenetic simulation tools** 🛠️
|
|
58
|
-
Compatible backends include ReMASTER, TreeSimulator, and AliSim.
|
|
59
|
-
|
|
60
57
|
- **Modular and extendible architecture** 🧩
|
|
61
58
|
Easily add new simulation backends as needed.
|
|
62
59
|
|
|
@@ -76,18 +73,9 @@ cd phylogenie
|
|
|
76
73
|
pip install .
|
|
77
74
|
```
|
|
78
75
|
|
|
79
|
-
## 🛠 Backend
|
|
80
|
-
|
|
81
|
-
Phylogenie works with the following simulation backends:
|
|
82
|
-
|
|
83
|
-
- **[TreeSimulator](https://github.com/evolbioinfo/treesimulator)**
|
|
84
|
-
A [Python](https://www.python.org/) package for simulating phylogenetic trees. It is automatically installed with Phylogenie, so you can use it right away.
|
|
85
|
-
|
|
86
|
-
- **[ReMASTER](https://tgvaughan.github.io/remaster/)**
|
|
87
|
-
A [BEAST2](https://www.beast2.org/) package designed for tree simulation. To use ReMASTER as a backend, you need to install it separately.
|
|
76
|
+
## 🛠 Backend dependency
|
|
88
77
|
|
|
89
|
-
|
|
90
|
-
A tool for simulating multiple sequence alignments (MSAs). It is distributed with [IQ-TREE](https://iqtree.github.io/) and also requires separate installation if you wish to use it as a backend.
|
|
78
|
+
Phylogenie relies on [AliSim](https://iqtree.github.io/doc/AliSim) for simulating multiple sequence alignments (MSAs). AliSim is a powerful MSAs simulation tool distributed with [IQ-TREE](https://iqtree.github.io/), and requires separate installation to use it as a simulation backend.
|
|
91
79
|
|
|
92
80
|
## 🚀 Quick Start
|
|
93
81
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
phylogenie/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
phylogenie/generators/__init__.py,sha256=kHDCQUSCl2O-wtPxcYEc1d4NTh5wjt-Oci9bYVNbhIY,440
|
|
3
|
+
phylogenie/generators/alisim.py,sha256=Psh_fAHHrJBCd1kqVtz_BdoVVmNvieHJRMmapQncJhM,2288
|
|
4
|
+
phylogenie/generators/configs.py,sha256=2NctdpKhOK7uuTrau18QCG1OqAAmmTl_c6QYwFnRSU0,1108
|
|
5
|
+
phylogenie/generators/dataset.py,sha256=Q8GsO6b4792VACvwle26QAsbeiyOGUqID2Q3-_urVS4,2543
|
|
6
|
+
phylogenie/generators/factories.py,sha256=AxLNuvZKnrSSUltr--kJ1zr-JjObD-KIip31ETSN0wk,6952
|
|
7
|
+
phylogenie/generators/trees.py,sha256=CXFXeYLYjgJe7tDwCaYrtHBVLl8Wn83in_pDM_2oH6M,8717
|
|
8
|
+
phylogenie/generators/typeguards.py,sha256=6WIQIy6huHok4vYqA9ym6g2kvyyXPggBONWwbiHtDiY,925
|
|
9
|
+
phylogenie/io.py,sha256=lDLWz-7fsw85HCpVjtpGpw2oWxf5Aurd3_jTx2Gt_pU,2865
|
|
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=S9CTsiK8hh4hXEnbsnPFnIXVNUFL9Oe3MMBKWRNEsSE,8951
|
|
15
|
+
phylogenie/skyline/parameter.py,sha256=CJ5OEyRQG2Tg1WJWQ1IpfX-6hjJv80Zj8lMoRke5nnQ,4648
|
|
16
|
+
phylogenie/skyline/vector.py,sha256=becZlWLeT12mvpDC5MLGXZPKlgmbkY-DEcuhdQpnsSc,7135
|
|
17
|
+
phylogenie/tree.py,sha256=nNUQrTtUqgc-Z6CHflqROZ8W2ZL1YWGG7xeWmcKwZUU,1684
|
|
18
|
+
phylogenie/treesimulator/__init__.py,sha256=koyVZxKUeMtY1S2WUgqbeIthoGbdUmx9vP4svFi4a6U,459
|
|
19
|
+
phylogenie/treesimulator/events.py,sha256=4TJpf9v9Cx-8g886Ilw2fbjSCuSkIoQ_xfOkm_a4ex8,9356
|
|
20
|
+
phylogenie/treesimulator/gillespie.py,sha256=p2bjwc6iyaf4mMI-xregJwOnXkjT2tIhxXbcGncQ5b0,2293
|
|
21
|
+
phylogenie/treesimulator/model.py,sha256=pPl4JYnBIHjWeSgpIypaDOes11tA4bFAGuMRArXbsVo,3577
|
|
22
|
+
phylogenie/typeguards.py,sha256=WBOSJSaOC8VDtrYoA2w_AYEXTpyKdCfmsM29KaKXl3A,1350
|
|
23
|
+
phylogenie/typings.py,sha256=O1X6lGKTjJ2YJz3ApQ-rYb_tEJNUIcHdUIeYlSM4s5o,500
|
|
24
|
+
phylogenie-2.0.0.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
|
|
25
|
+
phylogenie-2.0.0.dist-info/METADATA,sha256=n2Rf9bfLtz_ctQrAZaXc-nQCe1Z3xqoUbiNrpwLOD94,5602
|
|
26
|
+
phylogenie-2.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
27
|
+
phylogenie-2.0.0.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
|
|
28
|
+
phylogenie-2.0.0.dist-info/RECORD,,
|
phylogenie/backend/__init__.py
DELETED
|
File without changes
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from phylogenie.backend.remaster.generate import generate_trees
|
|
2
|
-
from phylogenie.backend.remaster.reactions import (
|
|
3
|
-
DEFAULT_POPULATION,
|
|
4
|
-
SAMPLE_POPULATION,
|
|
5
|
-
PunctualReaction,
|
|
6
|
-
Reaction,
|
|
7
|
-
get_canonical_reactions,
|
|
8
|
-
get_epidemiological_reactions,
|
|
9
|
-
get_FBD_reactions,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
__all__ = [
|
|
13
|
-
"DEFAULT_POPULATION",
|
|
14
|
-
"SAMPLE_POPULATION",
|
|
15
|
-
"PunctualReaction",
|
|
16
|
-
"Reaction",
|
|
17
|
-
"get_canonical_reactions",
|
|
18
|
-
"get_epidemiological_reactions",
|
|
19
|
-
"get_FBD_reactions",
|
|
20
|
-
"generate_trees",
|
|
21
|
-
]
|