Skip to content

Commit 2a250e9

Browse files
jeromekelleherhossam26644
authored andcommitted
simulations above root implementation
1 parent 1ab3389 commit 2a250e9

11 files changed

Lines changed: 687 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
{pr}`2321`, {pr}`2326`, {pr}`2388`, {user}`abureau`, {user}`jeromekelleher`).
1010

1111
- Add support and wheels for Python3.13
12+
- Support for simulations after local mrca,
13+
({pr}`2396`, {user}`jeromekelleher`, {user}`hossam26644`).
1214

1315
- Drop Python 3.9 support, require Python >= 3.10 ({pr}`2418`, {user}`benjeffery`)
1416

algorithms.py

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020

2121
logger = daiquiri.getLogger()
22+
INFINITY = sys.float_info.max
2223

2324

2425
class FenwickTree:
@@ -247,7 +248,7 @@ def _get_common_ancestor_waiting_time(self, np, t):
247248
Returns the random waiting time until a common ancestor event
248249
occurs within this population.
249250
"""
250-
ret = sys.float_info.max
251+
ret = INFINITY
251252
u = random.expovariate(2 * np)
252253
if self.growth_rate == 0:
253254
ret = self.start_size * u
@@ -267,7 +268,7 @@ def _get_common_ancestor_waiting_time(self, np, t):
267268
def get_common_ancestor_waiting_time_hudson(self):
268269
def _get_common_ancestor_waiting_time_hudson(t):
269270
k = self.get_num_ancestors()
270-
ret = sys.float_info.max
271+
ret = INFINITY
271272
if k > 1:
272273
np = k * (k - 1) / 2
273274
ret = self._get_common_ancestor_waiting_time(np, t)
@@ -278,7 +279,7 @@ def _get_common_ancestor_waiting_time_hudson(t):
278279
def get_common_ancestor_waiting_time_smc_k(self):
279280
def _get_common_ancestor_waiting_time_smc_k(t):
280281
np = self.get_num_pairs()
281-
ret = sys.float_info.max
282+
ret = INFINITY
282283
if np > 0:
283284
ret = self._get_common_ancestor_waiting_time(np, t)
284285
return ret
@@ -885,6 +886,7 @@ def __init__(
885886
gene_conversion_length=1,
886887
discrete_genome=True,
887888
hull_offset=None,
889+
stop_at_local_mrca=True,
888890
):
889891
# Must be a square matrix.
890892
N = len(migration_matrix)
@@ -906,6 +908,7 @@ def __init__(
906908
self.migration_matrix = migration_matrix
907909
self.num_labels = num_labels
908910
self.num_populations = N
911+
self.stop_at_local_mrca = stop_at_local_mrca
909912
self.max_segments = max_segments
910913
self.coalescing_segments_only = coalescing_segments_only
911914
self.additional_nodes = msprime.NodeType(additional_nodes)
@@ -964,7 +967,7 @@ def __init__(
964967
self.sweep_trajectory = sweep_trajectory
965968
self.time_slice = time_slice
966969

967-
self.modifier_events = [(sys.float_info.max, None, None)]
970+
self.modifier_events = [(INFINITY, None, None)]
968971
for time, pop_id, new_size in population_size_changes:
969972
self.modifier_events.append(
970973
(time, self.change_population_size, (int(pop_id), new_size))
@@ -1253,7 +1256,7 @@ def simulate(self, end_time):
12531256
if self.model == "hudson":
12541257
self.hudson_simulate(end_time)
12551258
elif self.model == "dtwf":
1256-
self.dtwf_simulate()
1259+
self.dtwf_simulate(end_time)
12571260
elif self.model == "fixed_pedigree":
12581261
self.pedigree_simulate()
12591262
elif self.model == "single_sweep":
@@ -1315,7 +1318,6 @@ def hudson_simulate(self, end_time):
13151318
"""
13161319
Simulates the algorithm until all loci have coalesced.
13171320
"""
1318-
infinity = sys.float_info.max
13191321
non_empty_pops = {pop.id for pop in self.P if pop.get_num_ancestors() > 0}
13201322
potential_destinations = self.get_potential_destinations()
13211323

@@ -1326,31 +1328,31 @@ def hudson_simulate(self, end_time):
13261328
break
13271329
# self.print_state()
13281330
re_rate = self.get_total_recombination_rate(label=0)
1329-
t_re = infinity
1331+
t_re = INFINITY
13301332
if re_rate > 0:
13311333
t_re = random.expovariate(re_rate)
13321334

13331335
# Gene conversion can occur within segments ..
13341336
gc_rate = self.get_total_gc_rate(label=0)
1335-
t_gcin = infinity
1337+
t_gcin = INFINITY
13361338
if gc_rate > 0:
13371339
t_gcin = random.expovariate(gc_rate)
13381340
# ... or to the left of the first segment.
13391341
gc_left_rate = self.get_total_gc_left_rate(label=0)
1340-
t_gc_left = infinity
1342+
t_gc_left = INFINITY
13411343
if gc_left_rate > 0:
13421344
t_gc_left = random.expovariate(gc_left_rate)
13431345

13441346
# Common ancestor events occur within demes.
1345-
t_ca = infinity
1347+
t_ca = INFINITY
13461348
for index in non_empty_pops:
13471349
pop = self.P[index]
13481350
assert pop.get_num_ancestors() > 0
13491351
t = pop.get_common_ancestor_waiting_time(self.t)
13501352
if t < t_ca:
13511353
t_ca = t
13521354
ca_population = index
1353-
t_mig = infinity
1355+
t_mig = INFINITY
13541356
# Migration events happen at the rates in the matrix.
13551357
for j in non_empty_pops:
13561358
source_size = self.P[j].get_num_ancestors()
@@ -1365,7 +1367,9 @@ def hudson_simulate(self, end_time):
13651367
mig_source = j
13661368
mig_dest = k
13671369
min_time = min(t_re, t_ca, t_gcin, t_gc_left, t_mig)
1368-
assert min_time != infinity
1370+
assert (min_time != INFINITY) or (
1371+
(min_time == INFINITY) and not self.stop_at_local_mrca
1372+
)
13691373
if self.t + min_time > self.modifier_events[0][0]:
13701374
t, func, args = self.modifier_events.pop(0)
13711375
self.t = t
@@ -1379,7 +1383,14 @@ def hudson_simulate(self, end_time):
13791383
event = "MOD"
13801384
else:
13811385
self.t += min_time
1382-
if min_time == t_re:
1386+
if min_time >= INFINITY:
1387+
assert not self.stop_at_local_mrca
1388+
event = "END"
1389+
if end_time >= INFINITY:
1390+
end_time = self.t
1391+
else:
1392+
self.t = end_time
1393+
elif min_time == t_re:
13831394
event = "RE"
13841395
self.hudson_recombination_event(0)
13851396
elif min_time == t_gcin:
@@ -1400,6 +1411,7 @@ def hudson_simulate(self, end_time):
14001411
non_empty_pops.remove(mig_source)
14011412
assert self.P[mig_dest].get_num_ancestors() > 0
14021413
non_empty_pops.add(mig_dest)
1414+
14031415
logger.info(
14041416
"%s time=%f n=%d",
14051417
event,
@@ -1524,20 +1536,39 @@ def pedigree_simulate(self):
15241536
self.pedigree = Pedigree(self.tables)
15251537
self.dtwf_climb_pedigree()
15261538

1527-
def dtwf_simulate(self):
1539+
def dtwf_above_root_simulations_stop(self, events_happened):
1540+
return (
1541+
not events_happened
1542+
and not self.stop_at_local_mrca
1543+
and sum(pop.get_num_ancestors() for pop in self.P) == 1
1544+
)
1545+
1546+
def dtwf_simulate(self, end_time):
15281547
"""
15291548
Simulates the algorithm until all loci have coalesced.
15301549
"""
1531-
while self.ancestors_remain():
1550+
while self.ancestors_remain() and self.t < end_time:
15321551
self.t += 1
15331552
self.verify()
1534-
self.dtwf_generation()
1553+
events_happened = self.dtwf_generation()
1554+
1555+
if self.dtwf_above_root_simulations_stop(events_happened):
1556+
if end_time >= INFINITY:
1557+
end_time = self.t
1558+
else:
1559+
self.t = end_time
1560+
break
15351561

15361562
def dtwf_generation(self):
15371563
"""
15381564
Evolves one generation of a Wright Fisher population
1565+
Returns True if any events occurred, False otherwise.
15391566
"""
15401567
# Migration events happen at the rates in the matrix.
1568+
nodes = self.tables.nodes.num_rows
1569+
edges = self.tables.edges.num_rows
1570+
no_migration = True
1571+
15411572
for j in range(len(self.P)):
15421573
source_size = self.P[j].get_num_ancestors()
15431574
for k in range(len(self.P)):
@@ -1549,6 +1580,7 @@ def dtwf_generation(self):
15491580
mig_source = j
15501581
mig_dest = k
15511582
self.migration_event(mig_source, mig_dest)
1583+
no_migration = False
15521584

15531585
for pop_idx, pop in enumerate(self.P):
15541586
# Cluster haploid inds by parent
@@ -1605,6 +1637,14 @@ def dtwf_generation(self):
16051637
h, pop_idx, 0, parent_nodes[ploid]
16061638
) # label 0 only
16071639
self.verify()
1640+
if (
1641+
self.tables.nodes.num_rows == nodes
1642+
and self.tables.edges.num_rows == edges
1643+
and no_migration
1644+
):
1645+
# dtwf generation had no events
1646+
return False
1647+
return True
16081648

16091649
def process_pedigree_common_ancestors(self, ind, ploid):
16101650
"""
@@ -2256,14 +2296,16 @@ def merge_ancestors(self, H, pop_id, label, new_node_id=-1):
22562296
if r_max not in self.S:
22572297
j = self.S.floor_key(r_max)
22582298
self.S[r_max] = self.S[j]
2299+
2300+
min_overlap = len(X) if self.stop_at_local_mrca else 0
22592301
# Update the number of extant segments.
2260-
if self.S[left] == len(X):
2302+
if self.S[left] == min_overlap:
22612303
self.S[left] = 0
22622304
right = self.S.succ_key(left)
22632305
else:
22642306
right = left
2265-
while right < r_max and self.S[right] != len(X):
2266-
self.S[right] -= len(X) - 1
2307+
while right < r_max and self.S[right] != min_overlap:
2308+
self.S[right] -= min_overlap - 1
22672309
right = self.S.succ_key(right)
22682310
alpha = self.alloc_segment(left, right, new_node_id, pop_id)
22692311
# Update the heaps and make the record.
@@ -2394,6 +2436,7 @@ def merge_two_ancestors(self, population_index, label, x, y, u=-1):
23942436
new_lineage = self.alloc_lineage(None, population_index, label=label)
23952437
coalescence = False
23962438
defrag_required = False
2439+
min_overlap = 2 if self.stop_at_local_mrca else 0
23972440

23982441
while x is not None or y is not None:
23992442
alpha = None
@@ -2435,12 +2478,12 @@ def merge_two_ancestors(self, population_index, label, x, y, u=-1):
24352478
j = self.S.floor_key(r_max)
24362479
self.S[r_max] = self.S[j]
24372480
# Update the number of extant segments.
2438-
if self.S[left] == 2:
2481+
if self.S[left] == min_overlap:
24392482
self.S[left] = 0
24402483
right = self.S.succ_key(left)
24412484
else:
24422485
right = left
2443-
while right < r_max and self.S[right] != 2:
2486+
while right < r_max and self.S[right] != min_overlap:
24442487
self.S[right] -= 1
24452488
right = self.S.succ_key(right)
24462489
alpha = self.alloc_segment(
@@ -2860,6 +2903,7 @@ def run_simulate(args):
28602903
gene_conversion_length=mean_tract_length,
28612904
discrete_genome=args.discrete,
28622905
hull_offset=args.offset,
2906+
stop_at_local_mrca=not args.continue_after_local_mrca,
28632907
)
28642908
ts = s.simulate(args.end_time)
28652909
ts.dump(args.output_file)
@@ -2946,6 +2990,15 @@ def add_simulator_arguments(parser):
29462990
help="The delta_t value for selective sweeps",
29472991
)
29482992
parser.add_argument("--model", default="hudson")
2993+
parser.add_argument(
2994+
"--continue-after-local-mrca",
2995+
action="store_true",
2996+
default=False,
2997+
help=(
2998+
"If set, continue after local MRCA (i.e., do not stop). "
2999+
"Default: False (stop at local MRCA)."
3000+
),
3001+
)
29493002
parser.add_argument("--offset", type=float, default=0.0)
29503003
parser.add_argument(
29513004
"--from-ts",

docs/ancestry.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,46 @@ However, if `coalescing_segments_only=False`, then the edges recorded
11291129
would be from `m` to `p` over the entire segment `[a, b)`, and from `n` to `p` over the entire segment `[c,d)`.
11301130
The nodes `m` and `n` coalesce (in `p`) on only the overlapping segment `[c,b)`, and so the node `p` will be a unary node in the flanking regions: above `m` on the segment `[a,c)` and above `n` on the segment `[b,d)`.
11311131

1132+
(sec_simulation_after_mrca)=
1133+
1134+
### Simulations after local MRCA
1135+
1136+
By default, msprime stops simulating local trees when a local most recent common ancestor (MRCA) is found. This is because events that occur
1137+
above the common ancestor are shared across all samples, and we are usually interested in differences between samples.
1138+
```{code-cell}
1139+
import msprime
1140+
1141+
ts = msprime.sim_ancestry(2, recombination_rate=0.1, sequence_length=10, random_seed=21)
1142+
ts.draw_svg(time_scale='rank')
1143+
```
1144+
Here, for example, we see that simulations in the middle tree stopped at node 6, which is much younger than nodes 7 and 8.
1145+
And if we look closely, we can also see that simulations stopped at node 7 in the rightmost tree at a younger time than
1146+
node 8 in the leftmost tree.
1147+
1148+
1149+
However, for some specialised applications, simulations after the local MRCA might be needed. In this case, we set the
1150+
parameter `stop_at_local_mrca` of {func}`.sim_ancestry` to `False`, and we set the `end_time` parameter to stop simulations
1151+
when they reach this end time.
1152+
1153+
1154+
```{code-cell}
1155+
import msprime
1156+
1157+
ts = msprime.sim_ancestry(
1158+
2, recombination_rate=0.1,
1159+
sequence_length=10,
1160+
random_seed=21,
1161+
stop_at_local_mrca=False,
1162+
end_time=10)
1163+
1164+
ts.draw_svg(time_scale='rank')
1165+
```
1166+
1167+
Note that you might want to set an `end_time` that is larger than possible end times for your simulations to avoid stopping
1168+
your simulations before full coalescence. However, a larger `end_time` means that simulations will take longer and consume
1169+
more memory.
1170+
1171+
11321172
(sec_additional_nodes_ca)=
11331173

11341174
### Common ancestor events

0 commit comments

Comments
 (0)