Skip to content

Commit

Permalink
add a check for remapping an out-of-range pop table index
Browse files Browse the repository at this point in the history
  • Loading branch information
bhaller committed Aug 9, 2022
1 parent 208c57b commit 8028e45
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 9 deletions.
2 changes: 1 addition & 1 deletion QtSLiM/help/SLiMHelpClasses.html
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@
<p class="p6"><span class="s3">As of SLiM 3.0, this method will read and restore the ages of individuals if that information is present in the output file and the simulation is based upon the nonWF model.<span class="Apple-converted-space">  </span>If ages are present but the simulation uses a WF model, an error will result; the WF model does not use age information.<span class="Apple-converted-space">  </span>If ages are not present but the simulation uses a nonWF model, an error will also result; the nonWF model requires age information.</span></p>
<p class="p6"><span class="s3">As of SLiM 3.3, this method will restore the nucleotides of nucleotide-based mutations, and will restore the ancestral nucleotide sequence, if that information is present in the output file.<span class="Apple-converted-space">  </span>Loading an output file that contains nucleotide information in a non-nucleotide-based model, and <i>vice versa</i>, will produce an error.</span></p>
<p class="p6">As of SLiM 3.5, this method will read and restore the pedigree IDs of individuals and genomes if that information is present in the output file (as requested with <span class="s1">outputFull(pedigreeIDs=T)</span>) <i>and</i> if SLiM’s optional pedigree tracking has been enabled with <span class="s1">initializeSLiMOptions(keepPedigrees=T)</span>.</p>
<p class="p6">This method can also be used to read tree-sequence (<span class="s1">.trees</span>) files saved by <span class="s1">treeSeqOutput()</span> or generated by the Python <span class="s1">pyslim</span> package.<span class="Apple-converted-space">  </span>Beginning with SLiM 4, the <span class="s1">subpopMap</span> parameter may be supplied to re-order the populations of the input tree sequence when it is loaded in to SLiM.<span class="Apple-converted-space">  </span>This parameter must have a value that is a <span class="s1">Dictionary</span>; the keys of this dictionary should be SLiM population identifiers as <span class="s1">string</span> values (e.g., <span class="s1">"p2"</span>), and the values should be indexes of populations in the input tree sequence; a key/value pair of <span class="s1">"p2", 4</span> would mean that the fourth population in the input should become <span class="s1">p2</span> on loading into SLiM.<span class="Apple-converted-space">  </span>If <span class="s1">subpopMap</span> is non-<span class="s1">NULL</span>, <i>all</i> populations in the tree sequence must be explicitly mapped, even if their index will not change and even if they will not be used by SLiM.<span class="Apple-converted-space">  </span>For instance, suppose we have a tree sequence in which population <span class="s1">0</span> is unused, population <span class="s1">1</span> is not a SLiM population (for example, an ancestral population produced by <span class="s1">msprime</span>), and population 2 is a SLiM population, and we want to load this in with population 2 as <span class="s1">p0</span> in SLiM.<span class="Apple-converted-space">  </span>To do this, we could supply a value of <span class="s1">Dictionary("p0", 2, "p1", 1, "p2", 0)</span> for <span class="s1">subpopMap</span>.<span class="Apple-converted-space">  </span>Although this cannot be used to remove populations in the tree sequence, note that it may <i>add</i> populations that will be visible when <span class="s1">treeSeqOutput()</span> is called (although these will not be SLiM populations); if, in this example, we had used <span class="s1">Dictionary("p0", 0, "p1", 1, "p5", 2)</span> and then we wrote the result out with <span class="s1">treeSeqOutput()</span>, the resulting tree sequence would have six populations, although three of them would be empty and would not be used by SLiM.<span class="Apple-converted-space">  </span>The use of subpopMap makes it easier to load simulation data that was generated in Python, since that typically uses an id of <span class="s1">0</span>.<span class="Apple-converted-space">  </span>The <span class="s1">subpopMap</span> parameter may not be used with file formats other than tree-sequence files, at the present time; setting up the correct subpopulation ids is typically easier when working with those other formats.</p>
<p class="p6">This method can also be used to read tree-sequence (<span class="s1">.trees</span>) files saved by <span class="s1">treeSeqOutput()</span> or generated by the Python <span class="s1">pyslim</span> package.<span class="Apple-converted-space">  </span>Beginning with SLiM 4, the <span class="s1">subpopMap</span> parameter may be supplied to re-order the populations of the input tree sequence when it is loaded in to SLiM.<span class="Apple-converted-space">  </span>This parameter must have a value that is a <span class="s1">Dictionary</span>; the keys of this dictionary should be SLiM population identifiers as <span class="s1">string</span> values (e.g., <span class="s1">"p2"</span>), and the values should be indexes of populations in the input tree sequence; a key/value pair of <span class="s1">"p2", 4</span> would mean that the fifth population in the input (the one at zero-based index <span class="s1">4</span>) should become <span class="s1">p2</span> on loading into SLiM.<span class="Apple-converted-space">  </span>If <span class="s1">subpopMap</span> is non-<span class="s1">NULL</span>, <i>all</i> populations in the tree sequence must be explicitly mapped, even if their index will not change and even if they will not be used by SLiM; the only exception is for unused slots in the population table, which can be explicitly remapped but do not have to be.<span class="Apple-converted-space">  </span>For instance, suppose we have a tree sequence in which population <span class="s1">0</span> is unused, population <span class="s1">1</span> is not a SLiM population (for example, an ancestral population produced by <span class="s1">msprime</span>), and population 2 is a SLiM population, and we want to load this in with population 2 as <span class="s1">p0</span> in SLiM.<span class="Apple-converted-space">  </span>To do this, we could supply a value of <span class="s1">Dictionary("p0", 2, "p1", 1, "p2", 0)</span> for <span class="s1">subpopMap</span>, or we could leave out slot <span class="s1">0</span> since it is unused, with <span class="s1">Dictionary("p0", 2, "p1", 1)</span>.<span class="Apple-converted-space">  </span>Although this facility cannot be used to remove populations in the tree sequence, note that it may <i>add</i> populations that will be visible when <span class="s1">treeSeqOutput()</span> is called (although these will not be SLiM populations); if, in this example, we had used <span class="s1">Dictionary("p0", 0, "p1", 1, "p5", 2)</span> and then we wrote the result out with <span class="s1">treeSeqOutput()</span>, the resulting tree sequence would have six populations, although three of them would be empty and would not be used by SLiM.<span class="Apple-converted-space">  </span>The use of <span class="s1">subpopMap</span> makes it easier to load simulation data that was generated in Python, since that typically uses an id of <span class="s1">0</span>.<span class="Apple-converted-space">  </span>The <span class="s1">subpopMap</span> parameter may not be used with file formats other than tree-sequence files, at the present time; setting up the correct subpopulation ids is typically easier when working with those other formats.<span class="Apple-converted-space">  </span>Note the <span class="s1">tskit</span> command-line interface can be used, like <span class="s1">python3 -m tskit populations file.trees</span>, to find out the number of subpopulations in a tree-sequence file and their IDs.</p>
<p class="p6">When loading a tree sequence, a crosscheck of the loaded data will be performed to ensure that the tree sequence was well-formed and was loaded correctly.<span class="Apple-converted-space">  </span>When running a Release build of SLiM, however, this crosscheck will only occur the first time that <span class="s1">readFromPopulationFile()</span> is called to load a tree sequence; subsequent calls will not perform this crosscheck, for greater speed when running models that load saved population state many times (such as models that are conditional on fixation).<span class="Apple-converted-space">  </span>If you suspect that a tree sequence file might be corrupted or read incorrectly, running a Debug build of SLiM enables crosschecks after every load.</p>
<p class="p3">– (void)recalculateFitness([Ni$ tick = NULL])</p>
<p class="p6">Force an immediate recalculation of fitness values for all individuals in all subpopulations.<span class="Apple-converted-space">  </span>Normally fitness values are calculated at a fixed point in each tick, and those values are cached and used until the next recalculation.<span class="Apple-converted-space">  </span>If simulation parameters are changed in script in a way that affects fitness calculations, and if you wish those changes to take effect immediately rather than taking effect at the next automatic recalculation, you may call <span class="s1">recalculateFitness()</span> to force an immediate recalculation and recache.</p>
Expand Down
28 changes: 20 additions & 8 deletions SLiMgui/SLiMHelpClasses.rtf
Original file line number Diff line number Diff line change
Expand Up @@ -5975,7 +5975,8 @@ As of SLiM 3.3, this method will restore the nucleotides of nucleotide-based mut
\f4\i0 if SLiM\'92s optional pedigree tracking has been enabled with
\f3\fs18 initializeSLiMOptions(keepPedigrees=T)
\f4\fs20 .\
This method can also be used to read tree-sequence (
\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0
\cf2 This method can also be used to read tree-sequence (
\f3\fs18 .trees
\f4\fs20 ) files saved by
\f3\fs18 treeSeqOutput()
Expand All @@ -5991,15 +5992,17 @@ This method can also be used to read tree-sequence (
\f3\fs18 "p2"
\f4\fs20 ), and the values should be indexes of populations in the input tree sequence; a key/value pair of
\f3\fs18 "p2", 4
\f4\fs20 would mean that the fourth population in the input should become
\f4\fs20 would mean that the fifth population in the input (the one at zero-based index
\f3\fs18 4
\f4\fs20 ) should become
\f3\fs18 p2
\f4\fs20 on loading into SLiM. If
\f3\fs18 subpopMap
\f4\fs20 is non-
\f3\fs18 NULL
\f4\fs20 ,
\f1\i all
\f4\i0 populations in the tree sequence must be explicitly mapped, even if their index will not change and even if they will not be used by SLiM. For instance, suppose we have a tree sequence in which population
\f4\i0 populations in the tree sequence must be explicitly mapped, even if their index will not change and even if they will not be used by SLiM; the only exception is for unused slots in the population table, which can be explicitly remapped but do not have to be. For instance, suppose we have a tree sequence in which population
\f3\fs18 0
\f4\fs20 is unused, population
\f3\fs18 1
Expand All @@ -6011,21 +6014,30 @@ This method can also be used to read tree-sequence (
\f3\fs18 Dictionary("p0", 2, "p1", 1, "p2", 0)
\f4\fs20 for
\f3\fs18 subpopMap
\f4\fs20 . Although this cannot be used to remove populations in the tree sequence, note that it may
\f4\fs20 , or we could leave out slot
\f3\fs18 0
\f4\fs20 since it is unused, with
\f3\fs18 Dictionary("p0", 2, "p1", 1)
\f4\fs20 . Although this facility cannot be used to remove populations in the tree sequence, note that it may
\f1\i add
\f4\i0 populations that will be visible when
\f3\fs18 treeSeqOutput()
\f4\fs20 is called (although these will not be SLiM populations); if, in this example, we had used
\f3\fs18 Dictionary("p0", 0, "p1", 1, "p5", 2)
\f4\fs20 and then we wrote the result out with
\f3\fs18 treeSeqOutput()
\f4\fs20 , the resulting tree sequence would have six populations, although three of them would be empty and would not be used by SLiM. The use of subpopMap makes it easier to load simulation data that was generated in Python, since that typically uses an id of
\f4\fs20 , the resulting tree sequence would have six populations, although three of them would be empty and would not be used by SLiM. The use of
\f3\fs18 subpopMap
\f4\fs20 makes it easier to load simulation data that was generated in Python, since that typically uses an id of
\f3\fs18 0
\f4\fs20 . The
\f3\fs18 subpopMap
\f4\fs20 parameter may not be used with file formats other than tree-sequence files, at the present time; setting up the correct subpopulation ids is typically easier when working with those other formats.\
\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0
\cf2 When loading a tree sequence, a crosscheck of the loaded data will be performed to ensure that the tree sequence was well-formed and was loaded correctly. When running a Release build of SLiM, however, this crosscheck will only occur the first time that
\f4\fs20 parameter may not be used with file formats other than tree-sequence files, at the present time; setting up the correct subpopulation ids is typically easier when working with those other formats. Note the
\f3\fs18 tskit
\f4\fs20 command-line interface can be used, like
\f3\fs18 python3 -m tskit populations file.trees
\f4\fs20 , to find out the number of subpopulations in a tree-sequence file and their IDs.\
When loading a tree sequence, a crosscheck of the loaded data will be performed to ensure that the tree sequence was well-formed and was loaded correctly. When running a Release build of SLiM, however, this crosscheck will only occur the first time that
\f3\fs18 readFromPopulationFile()
\f4\fs20 is called to load a tree sequence; subsequent calls will not perform this crosscheck, for greater speed when running models that load saved population state many times (such as models that are conditional on fixation). If you suspect that a tree sequence file might be corrupted or read incorrectly, running a Debug build of SLiM enables crosschecks after every load.\
\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0
Expand Down
18 changes: 18 additions & 0 deletions core/species.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6779,6 +6779,24 @@ void Species::__RemapSubpopulationIDs(SUBPOP_REMAP_HASH &p_subpop_map, int p_fil
tsk_population_table_t &pop_table = tables_.populations;
tsk_size_t pop_count = pop_table.num_rows;

// Start by checking that no remap entry references a population table index that is out of range
if (pop_count == 0)
EIDOS_TERMINATION << "ERROR (Species::__RemapSubpopulationIDs): the population table is empty, and therefore cannot be remapped." << EidosTerminate(nullptr);

for (auto &remap_entry : p_subpop_map)
{
int64_t table_index = remap_entry.first;
//slim_objectid_t remapped_index = remap_entry.second;

//std::cout << "index " << table_index << " being remapped to " << remapped_index << std::endl;

if (table_index < 0)
EIDOS_TERMINATION << "ERROR (Species::__RemapSubpopulationIDs): (internal error) index " << table_index << " is out of range (less than zero)." << EidosTerminate(nullptr);
if (table_index >= (int64_t)pop_count)
EIDOS_TERMINATION << "ERROR (Species::__RemapSubpopulationIDs): index " << table_index << " is out of range (last valid index " << ((int64_t)pop_count - 1) << ")." << EidosTerminate(nullptr);
}

// OK, population table indices are in range; check the population table entry remappings one by one
for (tsk_size_t pop_index = 0; pop_index < pop_count; pop_index++)
{
// validate and parse metadata
Expand Down

0 comments on commit 8028e45

Please sign in to comment.