From ada5f753dd00400da7c326a4b0ead166397bd03a Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 24 Aug 2026 15:38:18 -0700 Subject: [PATCH 1/2] split baselineOffset tags fixup problems with phenotype invalidation --- docs/metadata.md | 8 +++-- pyslim/slim_metadata.py | 8 +++-- tests/test_annotation.py | 30 +++++++++++++++++-- tests/test_recipes/recipe_WF.slim | 1 + tests/test_recipes/recipe_nucleotides_WF.slim | 1 + .../recipe_nucleotides_nonWF.slim | 1 + tests/test_recipes/recipe_resettable_WF.slim | 1 + tests/test_recipes/restart_WF.slim | 3 ++ tests/test_recipes/restart_and_run_WF.slim | 2 +- tests/test_recipes/restart_and_run_nonWF.slim | 1 + .../test_recipes/restart_multispecies_WF.slim | 3 ++ tests/test_recipes/restart_nonWF.slim | 3 ++ .../test_recipes/restart_nucleotides_WF.slim | 2 ++ .../restart_nucleotides_WF_chromosomes.slim | 2 ++ .../restart_nucleotides_nonWF.slim | 2 ++ 15 files changed, 59 insertions(+), 9 deletions(-) diff --git a/docs/metadata.md b/docs/metadata.md index 8332cdb2..3d8962ee 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -67,8 +67,10 @@ and `ts.metadata["SLiM"]` contains information about the simulation: * `index`: the index of the trait in SLiM * `name`: the name in SLiM for the trait * `type`: additive, multiplicative, or logistic - * `baselineOffset`, `baselineAccumulation`: a value added to all traits, and whether the effect of substitutions - accumulate in that value + * `baselineOffsetFromUser`: a value added to all traits + * `baselineOffsetFromSubstitutions`: the total effect of all substitutions on the traits + at the time the tree sequence was saved + * `baselineAccumulation`: whether the effect of substitutions accumulate in that value * `directFitnessEffect`: whether the trait has a direct effect on fitness * `individualOffsetMean`, `individualOffsetSD`: parameters governing the individual-level offsets (i.e., "environment" effects) @@ -148,7 +150,7 @@ we might do: md = ts.metadata traits = md["SLiM"]["traits"] values = [ - [x['phenotype'] - y["baselineOffset"] for x, y in zip(ind.metadata['per_trait'], traits)] + [x['phenotype'] - y["baselineOffsetFromUser"] for x, y in zip(ind.metadata['per_trait'], traits)] for ind in ts.individuals() ] ``` diff --git a/pyslim/slim_metadata.py b/pyslim/slim_metadata.py index e515f559..970198d6 100644 --- a/pyslim/slim_metadata.py +++ b/pyslim/slim_metadata.py @@ -186,9 +186,13 @@ def is_vacant_num_bytes(num_chromosomes): "description": "Whether the baseline offset includes accumulated effects from fixed (substituted) mutations.", "type": "boolean", }, - "baselineOffset": { - "description": "The baseline offset of the trait.", + "baselineOffsetFromUser": { "type": "number", + "description": "The from-user component of the baseline offset of the trait.", + }, + "baselineOffsetFromSubstitutions": { + "type": "number", + "description": "The from-substitutions component of the baseline offset of the trait.", }, "directFitnessEffect": { "description": "Whether the trait's effects are used directly as fitness effects.", diff --git a/tests/test_annotation.py b/tests/test_annotation.py index ead0a046..56ae5b5c 100644 --- a/tests/test_annotation.py +++ b/tests/test_annotation.py @@ -42,7 +42,19 @@ def canonicalise_tables(tables): tables.canonicalise() -def verify_slim_restart_equality(in_ts_dict, out_ts_dict, check_prov=True): +def invalidate_phenotypes(tables): + indivs = tables.individuals.copy() + tables.individuals.clear() + for ind in indivs: + md = ind.metadata + for x in md["per_trait"]: + x["phenotype"] = np.nan + tables.individuals.append(ind.replace(metadata=md)) + + +def verify_slim_restart_equality( + in_ts_dict, out_ts_dict, check_prov=True, phenotypes=True +): """ Check for equality, in everything but the last provenance. """ @@ -56,6 +68,9 @@ def verify_slim_restart_equality(in_ts_dict, out_ts_dict, check_prov=True): canonicalise_tables(in_tables) out_tables = out_ts.dump_tables() canonicalise_tables(out_tables) + if not phenotypes: + invalidate_phenotypes(in_tables) + invalidate_phenotypes(out_tables) in_tables.assert_equals(out_tables, ignore_provenance=True) @@ -585,6 +600,7 @@ def test_empty_populations(self, helper_functions, tmp_path): WF=False, subpop_map=subpop_map, multichrom=False, + PHENOTYPES=False, )["default"] self.verify_remapping(ts, sts, subpop_map) @@ -670,6 +686,7 @@ def test_many_species(self, helper_functions, tmp_path): tmp_path, WF=wf, subpop_map=subpop_map, + PHENOTYPES=False, ) self.verify_remapping(fox_ts, tsd["fox"], subpop_map["fox"]) self.verify_remapping(mouse_ts, tsd["mouse"], subpop_map["mouse"]) @@ -797,6 +814,7 @@ def test_remapping_empty_pops(self, helper_functions, tmp_path): multichrom=False, WF=False, subpop_map=subpop_map, + PHENOTYPES=False, )["default"] self.verify_remapping(ts, rts, subpop_map) @@ -863,6 +881,7 @@ def test_remapping(self, helper_functions, tmp_path): multichrom=False, WF=True, subpop_map=subpop_map, + PHENOTYPES=False, )["default"] self.verify_remapping(ts, rts, subpop_map) @@ -934,6 +953,7 @@ def test_reload_recapitate(self, restart_name, recipe, helper_functions, tmp_pat tmp_path, "multichrom" in recipe, WF=False, + PHENOTYPES=True, ) # check for equality, in everything but the last provenance verify_slim_restart_equality(in_ts, out_ts) @@ -947,6 +967,7 @@ def test_reload_recapitate(self, restart_name, recipe, helper_functions, tmp_pat tmp_path, "multichrom" in recipe, subpop_map=subpop_map, + PHENOTYPES=True, ) assert in_ts.keys() == out_ts.keys() for k in in_ts: @@ -982,10 +1003,13 @@ def test_reload_annotate(self, restart_name, recipe, helper_functions, tmp_path) in_ts[chrom] = tables.tree_sequence() # put it through SLiM (which just reads in and writes out) out_ts = helper_functions.run_slim_restart( - in_ts, restart_name, tmp_path, "multichrom" in recipe + in_ts, + restart_name, + tmp_path, + "multichrom" in recipe, ) # check for equality, in everything but the last provenance - verify_slim_restart_equality(in_ts, out_ts) + verify_slim_restart_equality(in_ts, out_ts, phenotypes=False) class TestReload(tests.PyslimTestCase): diff --git a/tests/test_recipes/recipe_WF.slim b/tests/test_recipes/recipe_WF.slim index 11702500..f8b8251c 100644 --- a/tests/test_recipes/recipe_WF.slim +++ b/tests/test_recipes/recipe_WF.slim @@ -52,6 +52,7 @@ mutation() { // OUTPUT/FINISH 10 late() { + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, metadata=MD); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_nucleotides_WF.slim b/tests/test_recipes/recipe_nucleotides_WF.slim index 32b30e9b..e6112177 100644 --- a/tests/test_recipes/recipe_nucleotides_WF.slim +++ b/tests/test_recipes/recipe_nucleotides_WF.slim @@ -62,6 +62,7 @@ mutation() { // OUTPUT/FINISH 10 late() { + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, metadata=MD); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_nucleotides_nonWF.slim b/tests/test_recipes/recipe_nucleotides_nonWF.slim index 5896984f..c08f2e67 100644 --- a/tests/test_recipes/recipe_nucleotides_nonWF.slim +++ b/tests/test_recipes/recipe_nucleotides_nonWF.slim @@ -71,6 +71,7 @@ mutation() { // OUTPUT/FINISH 10 late() { + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, metadata=MD); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_resettable_WF.slim b/tests/test_recipes/recipe_resettable_WF.slim index b835374a..e9d18e15 100644 --- a/tests/test_recipes/recipe_resettable_WF.slim +++ b/tests/test_recipes/recipe_resettable_WF.slim @@ -22,6 +22,7 @@ initialize() } 10 late() { + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, metadata=METADATA); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/restart_WF.slim b/tests/test_recipes/restart_WF.slim index 6fd44c49..411a5468 100644 --- a/tests/test_recipes/restart_WF.slim +++ b/tests/test_recipes/restart_WF.slim @@ -4,6 +4,7 @@ initialize() if (!exists("TREES_FILE")) defineGlobal("TREES_FILE", "out.trees"); // as well as any bespoke TREES_FILE for output, you need to define: // TREES_IN = path to trees to initialise + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); initializeTreeSeq(timeUnit="generations"); initializeMutationRate(1e-2); initializeMutationType("m1", 0.5, "f", -0.1); @@ -18,6 +19,7 @@ initialize() if (STAGE == "early") { md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } } @@ -25,6 +27,7 @@ initialize() if (STAGE == "late") { md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } } diff --git a/tests/test_recipes/restart_and_run_WF.slim b/tests/test_recipes/restart_and_run_WF.slim index bc65d26a..a038f4f5 100644 --- a/tests/test_recipes/restart_and_run_WF.slim +++ b/tests/test_recipes/restart_and_run_WF.slim @@ -14,7 +14,6 @@ initialize() if (!exists("SUBPOP_MAP")) defineConstant("SUBPOP_MAP", NULL); } -// output immediately! Nothing should have changed. 1 early() { if (STAGE == "early") { sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); @@ -28,6 +27,7 @@ initialize() 20 early() { catn("Done."); + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F); sim.simulationFinished(); } diff --git a/tests/test_recipes/restart_and_run_nonWF.slim b/tests/test_recipes/restart_and_run_nonWF.slim index 988eaa55..9cb8317e 100644 --- a/tests/test_recipes/restart_and_run_nonWF.slim +++ b/tests/test_recipes/restart_and_run_nonWF.slim @@ -29,6 +29,7 @@ initialize() 20 early() { catn("Done."); + sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F); sim.simulationFinished(); } diff --git a/tests/test_recipes/restart_multispecies_WF.slim b/tests/test_recipes/restart_multispecies_WF.slim index b026bc4e..c14df4a2 100644 --- a/tests/test_recipes/restart_multispecies_WF.slim +++ b/tests/test_recipes/restart_multispecies_WF.slim @@ -5,6 +5,7 @@ species all initialize() { setSeed(23); initializeSLiMModelType("WF"); + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); } species fox initialize() @@ -38,6 +39,7 @@ ticks all 1 early() { if (STAGE == "early") { for (sp in community.allSpecies) { sp.readFromPopulationFile(TREES_IN.getValue(sp.name), subpopMap=SUBPOP_MAP.getValue(sp.name)); + if (PHENOTYPES) sp.demandPhenotype(NULL); sp.treeSeqOutput(executeLambda("TREES_FILE_" + sp.name + ";"), simplify=F); } } @@ -46,6 +48,7 @@ ticks all 1 late() { if (STAGE == "late") { for (sp in community.allSpecies) { sp.readFromPopulationFile(TREES_IN.getValue(sp.name), subpopMap=SUBPOP_MAP.getValue(sp.name)); + if (PHENOTYPES) sp.demandPhenotype(NULL); sp.treeSeqOutput(executeLambda("TREES_FILE_" + sp.name + ";"), simplify=F); } } diff --git a/tests/test_recipes/restart_nonWF.slim b/tests/test_recipes/restart_nonWF.slim index 38a39cb7..c2106486 100644 --- a/tests/test_recipes/restart_nonWF.slim +++ b/tests/test_recipes/restart_nonWF.slim @@ -4,6 +4,7 @@ initialize() if (!exists("TREES_FILE")) defineGlobal("TREES_FILE", "out.trees"); // as well as any bespoke TREES_FILE for output, you need to define: // TREES_IN = path to trees to initialise + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); initializeSLiMModelType("nonWF"); initializeTreeSeq(); initializeMutationRate(1e-2); @@ -19,6 +20,7 @@ initialize() if (STAGE == "early") { md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } } @@ -27,6 +29,7 @@ initialize() if (STAGE == "late") { md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } } diff --git a/tests/test_recipes/restart_nucleotides_WF.slim b/tests/test_recipes/restart_nucleotides_WF.slim index fefa39a6..81de1839 100644 --- a/tests/test_recipes/restart_nucleotides_WF.slim +++ b/tests/test_recipes/restart_nucleotides_WF.slim @@ -4,6 +4,7 @@ initialize() if (!exists("TREES_FILE")) defineGlobal("TREES_FILE", "out.trees"); // as well as any bespoke TREES_FILE for output, you need to define: // TREES_IN = path to trees to initialise + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); defineConstant("L", 1e2); initializeSLiMOptions(keepPedigrees=T, nucleotideBased=T); initializeTreeSeq(timeUnit="generations"); @@ -29,6 +30,7 @@ initialize() // output immediately! Nothing should have changed. md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } diff --git a/tests/test_recipes/restart_nucleotides_WF_chromosomes.slim b/tests/test_recipes/restart_nucleotides_WF_chromosomes.slim index 2247e4d2..8bc00101 100644 --- a/tests/test_recipes/restart_nucleotides_WF_chromosomes.slim +++ b/tests/test_recipes/restart_nucleotides_WF_chromosomes.slim @@ -5,6 +5,7 @@ initialize() // as well as any bespoke TREES_FILE for output, you need to define: // TREES_IN = path to trees to initialise defineConstant("L", 1e2); + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); initializeSLiMOptions(keepPedigrees=T, nucleotideBased=T); initializeSex(); initializeTreeSeq(timeUnit="generations"); @@ -31,6 +32,7 @@ initialize() // output immediately! Nothing should have changed. md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } diff --git a/tests/test_recipes/restart_nucleotides_nonWF.slim b/tests/test_recipes/restart_nucleotides_nonWF.slim index c30a52a3..7cb4991f 100644 --- a/tests/test_recipes/restart_nucleotides_nonWF.slim +++ b/tests/test_recipes/restart_nucleotides_nonWF.slim @@ -5,6 +5,7 @@ initialize() // as well as any bespoke TREES_FILE for output, you need to define: // TREES_IN = path to trees to initialise defineConstant("L", 1e2); + if (!exists("PHENOTYPES")) defineGlobal("PHENOTYPES", T); initializeSLiMModelType("nonWF"); initializeSLiMOptions(keepPedigrees=T, nucleotideBased=T); initializeTreeSeq(); @@ -20,6 +21,7 @@ initialize() // output immediately! Nothing should have changed. md = treeSeqMetadata(TREES_IN, userData=F).getValue("SLiM").getValue("user_metadata"); sim.readFromPopulationFile(TREES_IN, subpopMap=SUBPOP_MAP); + if (PHENOTYPES) sim.demandPhenotype(NULL); sim.treeSeqOutput(TREES_FILE, simplify=F, metadata=md); } From 40374946a9d59b7c29ff24130809fa5c71d8fb4d Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 25 Aug 2026 06:04:46 -0700 Subject: [PATCH 2/2] speedups and skip on windows --- tests/recipe_specs.py | 22 +++- tests/test_metadata.py | 7 ++ .../recipe_chromosomes_adds_muts.slim | 2 +- .../recipe_record_everyone_WF_early.slim | 2 +- .../recipe_record_everyone_WF_first.slim | 2 +- .../recipe_record_everyone_WF_late.slim | 2 +- .../recipe_record_everyone_nonWF_early.slim | 2 +- .../recipe_record_everyone_nonWF_first.slim | 2 +- .../recipe_record_everyone_nonWF_late.slim | 2 +- tests/test_tree_sequence.py | 104 +++++++++--------- 10 files changed, 86 insertions(+), 61 deletions(-) diff --git a/tests/recipe_specs.py b/tests/recipe_specs.py index d31c2f69..fbe39a2d 100644 --- a/tests/recipe_specs.py +++ b/tests/recipe_specs.py @@ -3,6 +3,7 @@ # possible attributes to simulation scripts are # (the script name itself, to specify a particular one) # WF, nonWF +# minimal: a minimal set of recipes to test basic things with # adds_mutations # nucleotides, non-nucleotides: has the respective sorts of mutations # mutation_spectrum: writes out info file on mutation spectrum @@ -21,10 +22,15 @@ # (chromosome type) # All files are of the form `tests/test_recipes/{key}` recipe_specs = { - "recipe_nonWF.slim": {"nonWF": True, "pedigree": True, "record_mutations": True}, - "recipe_nonWF_X.slim": {"nonWF": True, "pedigree": True, "X": True}, + "recipe_nonWF.slim": { + "nonWF": True, + "pedigree": True, + "record_mutations": True, + "minimal": True, + }, + "recipe_nonWF_X.slim": {"nonWF": True, "pedigree": True, "X": True, "minimal": True}, "recipe_nonWF_Y.slim": {"nonWF": True, "pedigree": True, "Y": True}, - "recipe_nonWF_H.slim": {"nonWF": True, "pedigree": True, "H": True}, + "recipe_nonWF_H.slim": {"nonWF": True, "pedigree": True, "H": True, "minimal": True}, "recipe_WF_X.slim": { "WF": True, "pedigree": True, @@ -83,7 +89,12 @@ }, "recipe_long_nonWF.slim": {"nonWF": True, "long": True}, "recipe_old_nonWF.slim": {"nonWF": True, "remembered_first": True}, - "recipe_WF.slim": {"WF": True, "pedigree": True, "record_mutations": True}, + "recipe_WF.slim": { + "WF": True, + "pedigree": True, + "record_mutations": True, + "minimal": True, + }, "recipe_no_simplify.slim": {"WF": True, "no_simplify": True}, "recipe_long_WF.slim": {"WF": True, "long": True}, "recipe_WF_migration.slim": {"WF": True, "pedigree": True, "multipop": True}, @@ -93,6 +104,7 @@ "nucleotides": True, "record_mutations": True, "refseq": True, + "minimal": True, }, "recipe_nucleotides_nonWF.slim": { "nonWF": True, @@ -183,6 +195,7 @@ "HM": True, "ML": True, "Y-": True, + "minimal": True, }, "recipe_chromosomes_adds_muts.slim": { "WF": True, @@ -214,6 +227,7 @@ "X": True, "Y": True, "H": True, + "minimal": True, }, } diff --git a/tests/test_metadata.py b/tests/test_metadata.py index e472e8ff..c3a1b727 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -2,6 +2,8 @@ Test cases for the metadata reading/writing of pyslim. """ +import sys + import numpy as np import pytest import tskit @@ -91,6 +93,11 @@ def test_default_metadata(self): decoded = schema.decode_row(encoded) assert entry == decoded + @pytest.mark.skipif( + sys.platform.startswith("win"), + reason="failing because of dict and OrderedDict comparison?", + ) + @pytest.mark.parametrize("recipe", recipe_eq("minimal"), indirect=True) def test_slim_metadata_schema_equality(self, recipe): num_chromosomes = len(recipe["ts"]) for ts in recipe["ts"].values(): diff --git a/tests/test_recipes/recipe_chromosomes_adds_muts.slim b/tests/test_recipes/recipe_chromosomes_adds_muts.slim index 2ffd3299..a3f14530 100644 --- a/tests/test_recipes/recipe_chromosomes_adds_muts.slim +++ b/tests/test_recipes/recipe_chromosomes_adds_muts.slim @@ -9,7 +9,7 @@ initialize() { initializeSex(); initializeMutationTypeNuc("m1", 0.5, "f", 0.0); initializeMutationType("m2", 0.5, "n", 0.0, 0.1); - initializeGenomicElementType("g1", m1, 1.0, mmJukesCantor(4e-4)); + initializeGenomicElementType("g1", m1, 1.0, mmJukesCantor(1e-4)); m1.convertToSubstitution = T; types = c("A", "X", "Y", "H", "Z", "W", "HF", "FL", "HM", "ML", "-Y"); diff --git a/tests/test_recipes/recipe_record_everyone_WF_early.slim b/tests/test_recipes/recipe_record_everyone_WF_early.slim index 88062774..53f69ed1 100644 --- a/tests/test_recipes/recipe_record_everyone_WF_early.slim +++ b/tests/test_recipes/recipe_record_everyone_WF_early.slim @@ -23,7 +23,7 @@ early() { sim.treeSeqRememberIndividuals(p1.individuals); } -10 early() { +5 early() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_record_everyone_WF_first.slim b/tests/test_recipes/recipe_record_everyone_WF_first.slim index 096c1e81..02792883 100644 --- a/tests/test_recipes/recipe_record_everyone_WF_first.slim +++ b/tests/test_recipes/recipe_record_everyone_WF_first.slim @@ -23,7 +23,7 @@ initialize() sim.treeSeqRememberIndividuals(p1.individuals); } -10 first() { +5 first() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_record_everyone_WF_late.slim b/tests/test_recipes/recipe_record_everyone_WF_late.slim index aa1b7eb2..8bcbffd5 100644 --- a/tests/test_recipes/recipe_record_everyone_WF_late.slim +++ b/tests/test_recipes/recipe_record_everyone_WF_late.slim @@ -24,7 +24,7 @@ late() { sim.treeSeqRememberIndividuals(p1.individuals); } -10 late() { +5 late() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_record_everyone_nonWF_early.slim b/tests/test_recipes/recipe_record_everyone_nonWF_early.slim index 7ad2e9f5..36041b3e 100644 --- a/tests/test_recipes/recipe_record_everyone_nonWF_early.slim +++ b/tests/test_recipes/recipe_record_everyone_nonWF_early.slim @@ -32,7 +32,7 @@ early() { sim.treeSeqRememberIndividuals(p1.individuals); } -10 early() { +5 early() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_record_everyone_nonWF_first.slim b/tests/test_recipes/recipe_record_everyone_nonWF_first.slim index b72a4ba3..541132f3 100644 --- a/tests/test_recipes/recipe_record_everyone_nonWF_first.slim +++ b/tests/test_recipes/recipe_record_everyone_nonWF_first.slim @@ -32,7 +32,7 @@ early() { sim.treeSeqRememberIndividuals(p1.individuals); } -10 first() { +5 first() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_recipes/recipe_record_everyone_nonWF_late.slim b/tests/test_recipes/recipe_record_everyone_nonWF_late.slim index 5841d48c..ac97918b 100644 --- a/tests/test_recipes/recipe_record_everyone_nonWF_late.slim +++ b/tests/test_recipes/recipe_record_everyone_nonWF_late.slim @@ -32,7 +32,7 @@ late() { sim.treeSeqRememberIndividuals(p1.individuals); } -10 late() { +5 late() { sim.treeSeqOutput(TREES_FILE); catn("Done."); sim.simulationFinished(); diff --git a/tests/test_tree_sequence.py b/tests/test_tree_sequence.py index 18a96111..ed41bd12 100644 --- a/tests/test_tree_sequence.py +++ b/tests/test_tree_sequence.py @@ -683,10 +683,12 @@ def test_post_simplify(self, recipe): rng = np.random.default_rng(seed=3) individual_times = ts.individuals_time md_tick = ts.metadata["SLiM"]["tick"] + # assumes tick hasn't been changed + inds = np.where(individual_times < md_tick - 1)[0] + assert len(inds) > 2 keep_indivs = rng.choice( - # assumes tick hasn't been changed - np.where(individual_times < md_tick - 1)[0], - size=30, + inds, + size=min(30, len(inds)), replace=False, ) keep_nodes = [] @@ -851,7 +853,6 @@ class TestReferenceSequence(tests.PyslimTestCase): def test_reference_sequence(self, recipe): for _, ts in recipe["ts"].items(): if ts.num_mutations > 0: - mut_md = ts.mutation(0).metadata has_nucleotides = "nucleotides" in recipe if not has_nucleotides: assert not ts.has_reference_sequence() @@ -881,7 +882,6 @@ def test_nucleotide_at_errors(self, recipe): for _, ts in recipe["ts"].items(): u = ts.samples()[0] if ts.num_mutations > 0: - mut_md = ts.mutation(0).metadata if not ts.has_reference_sequence(): with pytest.raises(ValueError, match="has no reference seq"): pyslim.nucleotide_at(ts, u, 3) @@ -921,46 +921,40 @@ def test_mutation_at(self, recipe): def test_nucleotide_at(self, recipe): random.seed(42) for _, ts in recipe["ts"].items(): - if ts.num_mutations > 0: - mut_metadata = pyslim.mutation_metadata(ts) - mut_md = ts.mutation(0).metadata - tsmd = ts.metadata - # check we've got nucleotide mutations - nucs = np.array([x["nucleotide"] for x in tsmd["SLiM_mutation_list"]]) - assert np.sum(nucs >= 0) > 1 - mut_info = { - str(mut["mutation_id"]): mut for mut in tsmd["SLiM_mutation_list"] - } - assert ts.has_reference_sequence() - assert len(ts.reference_sequence.data) == ts.sequence_length - for _ in range(100): - node = random.randint(0, ts.num_nodes - 1) - pos = random.randint(0, int(ts.sequence_length) - 1) - tree = ts.at(pos) - parent = tree.parent(node) - a = pyslim.nucleotide_at(ts, node, pos) - if parent == tskit.NULL: - nuc = ts.reference_sequence.data[int(pos)] - assert a == pyslim.NUCLEOTIDES.index(nuc) - else: - b = pyslim.nucleotide_at( - ts, parent, pos, mut_metadata=mut_metadata - ) - c = pyslim.nucleotide_at( - ts, - node, - pos, - ts.node(parent).time, - mut_metadata=mut_metadata, - ) - assert b == c - for k in np.where(node == ts.tables.mutations.node)[0]: - mut = ts.mutation(k) - if ts.site(mut.site).position == pos: - b = mut_info[mut.derived_state.split(",")[0]][ - "nucleotide" - ] - assert a == b + assert ts.num_mutations > 0 + mut_metadata = pyslim.mutation_metadata(ts) + tsmd = ts.metadata + # check we've got nucleotide mutations + nucs = np.array([x["nucleotide"] for x in mut_metadata.values()]) + assert np.sum(nucs >= 0) > 1 + assert ts.has_reference_sequence() + assert len(ts.reference_sequence.data) == ts.sequence_length + for _ in range(100): + node = random.randint(0, ts.num_nodes - 1) + pos = random.randint(0, int(ts.sequence_length) - 1) + tree = ts.at(pos) + parent = tree.parent(node) + a = pyslim.nucleotide_at(ts, node, pos, mut_metadata=mut_metadata) + if parent == tskit.NULL: + nuc = ts.reference_sequence.data[int(pos)] + assert a == pyslim.NUCLEOTIDES.index(nuc) + else: + b = pyslim.nucleotide_at(ts, parent, pos, mut_metadata=mut_metadata) + c = pyslim.nucleotide_at( + ts, + node, + pos, + ts.node(parent).time, + mut_metadata=mut_metadata, + ) + assert b == c + for k in np.where(node == ts.tables.mutations.node)[0]: + mut = ts.mutation(k) + if ts.site(mut.site).position == pos: + b = mut_metadata[int(mut.derived_state.split(",")[0])][ + "nucleotide" + ] + assert a == b @pytest.mark.parametrize("recipe", [next(recipe_eq("nucleotides"))], indirect=True) def test_nucleotide_at_without_mut_metadata(self, recipe): @@ -983,7 +977,7 @@ def test_nucleotide_spectrum(self, recipe): # access to the parental genome, so if two adjacent mutations # occur in the same meiosis then each will not know about the other. for _, ts in recipe["ts"].items(): - mut_info = pyslim.mutation_metadata(ts) + mut_metadata = pyslim.mutation_metadata(ts) mutation_spectrum = recipe["mutation_info"] M = { a + b + c + "," + d: 0 @@ -997,17 +991,27 @@ def test_nucleotide_spectrum(self, recipe): pos = ts.site(mut.site).position if pos > 0 and pos < ts.sequence_length - 1: nmuts += 1 - mut_list = [mut_info[int(k)] for k in mut.derived_state.split(",")] + mut_list = [ + mut_metadata[int(k)] for k in mut.derived_state.split(",") + ] k = np.argmax([u["slim_time"] for u in mut_list]) derived_nuc = mut_list[k]["nucleotide"] left_nuc = pyslim.nucleotide_at( - ts, mut.node, pos - 1, time=mut.time + 1.0 + ts, + mut.node, + pos - 1, + time=mut.time + 1.0, + mut_metadata=mut_metadata, ) right_nuc = pyslim.nucleotide_at( - ts, mut.node, pos + 1, time=mut.time + 1.0 + ts, + mut.node, + pos + 1, + time=mut.time + 1.0, + mut_metadata=mut_metadata, ) parent_nuc = pyslim.nucleotide_at( - ts, mut.node, pos, time=mut.time + 1.0 + ts, mut.node, pos, time=mut.time + 1.0, mut_metadata=mut_metadata ) context = "".join( [