From 36d89fea2dca6a302a06305f2cdeffe79f91aa2e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Fri, 14 Aug 2026 09:54:24 +0000 Subject: [PATCH 1/6] draft fix for metadata inefficiency --- ... a neutral burn-in after simulation with recapitation II.py | 3 ++- ... a neutral burn-in after simulation with recapitation II.py | 3 ++- VERSIONS | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 59edd5da1..a56d3c0a4 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -9,10 +9,11 @@ # Calculate tree heights, giving uncoalesced sites the maximum time def tree_heights(ts): + uncoalesced_height = ts.metadata['SLiM']['tick'] heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): if tree.num_roots > 1: # not fully coalesced - heights[tree.index] = ts.metadata['SLiM']['tick'] + heights[tree.index] = uncoalesced_height else: children = tree.children(tree.root) real_root = tree.root if len(children) > 1 else children[0] diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 59edd5da1..a56d3c0a4 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -9,10 +9,11 @@ # Calculate tree heights, giving uncoalesced sites the maximum time def tree_heights(ts): + uncoalesced_height = ts.metadata['SLiM']['tick'] heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): if tree.num_roots > 1: # not fully coalesced - heights[tree.index] = ts.metadata['SLiM']['tick'] + heights[tree.index] = uncoalesced_height else: children = tree.children(tree.root) real_root = tree.root if len(children) > 1 else children[0] diff --git a/VERSIONS b/VERSIONS index 9fca5a933..8d6b270af 100644 --- a/VERSIONS +++ b/VERSIONS @@ -204,6 +204,7 @@ multitrait branch: add a built-in "Multispecies Multitrait Phenotype ~ Time" plot for phenotype ~ time, with one line per trait across all species add a built-in "Phenotype ~ Time" plot for phenotype ~ time, focusing on a single user-selected trait across subpopulations clean up the way pedigree IDs get recorded, for better efficiency and parallelization + fix recipe 18.10 to reduce metadata accesses (see discussion at https://github.com/tskit-dev/tskit/issues/3472#issuecomment-5210734749) version 5.2 (Eidos version 4.2): From 1a1214afa866a090fdc9114730ab682981e6810d Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 14 Aug 2026 13:04:48 -0700 Subject: [PATCH 2/6] fixup of python recipe --- ...rn-in after simulation with recapitation II.py | 15 ++++++++------- ...rn-in after simulation with recapitation II.py | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index a56d3c0a4..ea43ff2cf 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -9,15 +9,16 @@ # Calculate tree heights, giving uncoalesced sites the maximum time def tree_heights(ts): - uncoalesced_height = ts.metadata['SLiM']['tick'] heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): - if tree.num_roots > 1: # not fully coalesced - heights[tree.index] = uncoalesced_height - else: - children = tree.children(tree.root) - real_root = tree.root if len(children) > 1 else children[0] - heights[tree.index] = tree.time(real_root) + roots = tree.roots + if len(roots) == 1: + children = tree.children(roots[0]) + if len(children) == 1: + roots = children + root_heights = [tree.time(r) for r in tree.roots] + assert len(set(root_heights)) == 1 + heights[tree.index] = root_heights[0] heights[-1] = heights[-2] # repeat the last entry for plotting with step return heights diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index a56d3c0a4..ea43ff2cf 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -9,15 +9,16 @@ # Calculate tree heights, giving uncoalesced sites the maximum time def tree_heights(ts): - uncoalesced_height = ts.metadata['SLiM']['tick'] heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): - if tree.num_roots > 1: # not fully coalesced - heights[tree.index] = uncoalesced_height - else: - children = tree.children(tree.root) - real_root = tree.root if len(children) > 1 else children[0] - heights[tree.index] = tree.time(real_root) + roots = tree.roots + if len(roots) == 1: + children = tree.children(roots[0]) + if len(children) == 1: + roots = children + root_heights = [tree.time(r) for r in tree.roots] + assert len(set(root_heights)) == 1 + heights[tree.index] = root_heights[0] heights[-1] = heights[-2] # repeat the last entry for plotting with step return heights From d6def0541bc3957313c25cc4f662c20bb69046a1 Mon Sep 17 00:00:00 2001 From: peter Date: Sat, 15 Aug 2026 09:02:18 -0700 Subject: [PATCH 3/6] bug fixup --- ...l burn-in after simulation with recapitation II.py | 11 ++++++----- ...l burn-in after simulation with recapitation II.py | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index ea43ff2cf..51fd497ff 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -16,7 +16,7 @@ def tree_heights(ts): children = tree.children(roots[0]) if len(children) == 1: roots = children - root_heights = [tree.time(r) for r in tree.roots] + root_heights = [tree.time(r) for r in roots] assert len(set(root_heights)) == 1 heights[tree.index] = root_heights[0] heights[-1] = heights[-2] # repeat the last entry for plotting with step @@ -25,8 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post') -plt.show() +plt.step(breakpoints, heights, where='post', label="SLiM") # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -35,7 +34,9 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post') -plt.show() +plt.step(breakpoints, heights, where='post', label="recapitated") +plt.xlabel("genome"); plt.ylabel("time ago (generations)") +plt.savefig("tree_heights.png") + diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index ea43ff2cf..51fd497ff 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -16,7 +16,7 @@ def tree_heights(ts): children = tree.children(roots[0]) if len(children) == 1: roots = children - root_heights = [tree.time(r) for r in tree.roots] + root_heights = [tree.time(r) for r in roots] assert len(set(root_heights)) == 1 heights[tree.index] = root_heights[0] heights[-1] = heights[-2] # repeat the last entry for plotting with step @@ -25,8 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post') -plt.show() +plt.step(breakpoints, heights, where='post', label="SLiM") # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -35,7 +34,9 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post') -plt.show() +plt.step(breakpoints, heights, where='post', label="recapitated") +plt.xlabel("genome"); plt.ylabel("time ago (generations)") +plt.savefig("tree_heights.png") + From 316e7a07e3b7d40d99f978d0ee2e3e7febfb3d4e Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 15 Aug 2026 20:31:21 +0000 Subject: [PATCH 4/6] final (?) revisions --- ...l burn-in after simulation with recapitation II.py | 11 +++-------- ...l burn-in after simulation with recapitation II.py | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 51fd497ff..41990e2ed 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -7,7 +7,7 @@ # Load the .trees file ts = tskit.load("decap.trees") # no simplify! -# Calculate tree heights, giving uncoalesced sites the maximum time +# Calculate tree heights def tree_heights(ts): heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): @@ -25,7 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post', label="SLiM") +plt.step(breakpoints, heights, where='post'); plt.show() # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -34,9 +34,4 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post', label="recapitated") -plt.xlabel("genome"); plt.ylabel("time ago (generations)") -plt.savefig("tree_heights.png") - - - +plt.step(breakpoints, heights, where='post'); plt.show() diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 51fd497ff..41990e2ed 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -7,7 +7,7 @@ # Load the .trees file ts = tskit.load("decap.trees") # no simplify! -# Calculate tree heights, giving uncoalesced sites the maximum time +# Calculate tree heights def tree_heights(ts): heights = np.zeros(ts.num_trees + 1) for tree in ts.trees(): @@ -25,7 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post', label="SLiM") +plt.step(breakpoints, heights, where='post'); plt.show() # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -34,9 +34,4 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post', label="recapitated") -plt.xlabel("genome"); plt.ylabel("time ago (generations)") -plt.savefig("tree_heights.png") - - - +plt.step(breakpoints, heights, where='post'); plt.show() From 37054fe7a9c49bf86ce4081f06accc03af34597a Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 15 Aug 2026 20:39:01 +0000 Subject: [PATCH 5/6] add newlines since they fit --- ...neutral burn-in after simulation with recapitation II.py | 6 ++++-- ...neutral burn-in after simulation with recapitation II.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 41990e2ed..ab01eb3ab 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -25,7 +25,8 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post'); plt.show() +plt.step(breakpoints, heights, where='post'); +plt.show() # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -34,4 +35,5 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post'); plt.show() +plt.step(breakpoints, heights, where='post'); +plt.show() diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index 41990e2ed..ab01eb3ab 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -25,7 +25,8 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post'); plt.show() +plt.step(breakpoints, heights, where='post'); +plt.show() # Recapitate! recap = pyslim.recapitate(ts, ancestral_Ne=1e5, recombination_rate=3e-10, random_seed=1) @@ -34,4 +35,5 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post'); plt.show() +plt.step(breakpoints, heights, where='post'); +plt.show() From 0b1dd8b603ae7f7b46b75886b1d9ba417e7b8f32 Mon Sep 17 00:00:00 2001 From: Ben Haller Date: Sat, 15 Aug 2026 20:48:37 +0000 Subject: [PATCH 6/6] remove semicolons, sigh --- ...a neutral burn-in after simulation with recapitation II.py | 4 ++-- ...a neutral burn-in after simulation with recapitation II.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index ab01eb3ab..e4e449c14 100644 --- a/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/QtSLiM/recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -25,7 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post'); +plt.step(breakpoints, heights, where='post') plt.show() # Recapitate! @@ -35,5 +35,5 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post'); +plt.step(breakpoints, heights, where='post') plt.show() diff --git a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py index ab01eb3ab..e4e449c14 100644 --- a/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py +++ b/SLiMgui/Recipes/Recipe 18.10 - Adding a neutral burn-in after simulation with recapitation II.py @@ -25,7 +25,7 @@ def tree_heights(ts): # Plot tree heights before recapitation breakpoints = list(ts.breakpoints()) heights = tree_heights(ts) -plt.step(breakpoints, heights, where='post'); +plt.step(breakpoints, heights, where='post') plt.show() # Recapitate! @@ -35,5 +35,5 @@ def tree_heights(ts): # Plot the tree heights after recapitation breakpoints = list(recap.breakpoints()) heights = tree_heights(recap) -plt.step(breakpoints, heights, where='post'); +plt.step(breakpoints, heights, where='post') plt.show()