Repository: tskit-dev/pyslim

Issue:
annotate fails on empty tree sequence
This is too bad, because sometimes one would want to put the metadata schema on the tables before building them.
```
>>> t = tskit.TableCollection()
>>> pyslim.annotate_tables(t, model_type='WF', tick=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/peter/projects/tskit-dev/pyslim/pyslim/methods.py", line 605, in annotate_tables
    _annotate_populations(tables)
  File "/home/peter/projects/tskit-dev/pyslim/pyslim/methods.py", line 705, in _annotate_populations
    num_pops = max(np.max(tables.nodes.population), np.max(do_pops)) + 1
  File "<__array_function__ internals>", line 5, in amax
  File "/usr/lib/python3/dist-packages/numpy/core/fromnumeric.py", line 2754, in amax
    return _wrapreduction(a, np.maximum, 'max', axis, None, out,
  File "/usr/lib/python3/dist-packages/numpy/core/fromnumeric.py", line 86, in _wrapreduction
    return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
ValueError: zero-size array to reduction operation maximum which has no identity
```

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5d807ad..0ca9e99 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,18 +8,20 @@
 
 **Breaking changes**:
 
-- Removed `SlimTreeSequence` class entirely.
+- Removed `SlimTreeSequence` class entirely (it was previously deprecated).
+    All its methods are either available in `tskit.TreeSequence`
+    or are now called by `pyslim.fn(ts, ...)` instead of `ts.fn(...)`.
 
 - TODO: Deprecated `util.unique_labels_by_group`.
 
-- TODO: what about `slim_ts.mutation_at` and `slim_ts.nucleotide_at` (used in a Recipe)
-
 - Moved some methods of `SlimTreeSequence` to pyslim:
     * instead of `slim_ts.slim_time(t)` do `pyslim.slim_time(ts, t)`
     * instead of `slim_ts.individuals_alive_at(t)` do `pyslim.individuals_alive_at(ts, t)`
     * instead of `slim_ts.individuals_parents(t)` do `pyslim.individuals_parents(ts, t)`
     * instead of `slim_ts.individuals_ages(t)` do `pyslim.individuals_ages(ts, t)`
 
+- The methods `slim_ts.mutation_at( )` and `slim_ts.nucleotide_at( )`
+    are now methods of pyslim, whose first argument is the tree sequence.
 
 - In SLiM v4 "generation" has been renamed to "tick", and so corresponding things
   in pyslim have been renamed: top-level metadata now has `ts.metadata["SLiM"]["tick"]`
@@ -32,7 +34,7 @@
   need to set up those individuals yourself.
 
 - To update a tree sequence produced by an old version of SLiM to the current one,
-  use `pyslim.update_tables( )`. (However, note that reading it in to SLiM and
+  use `pyslim.update( )`. (However, note that reading it in to SLiM and
   writing it out again might be even easier.)
 
 - The method `pyslim.set_tree_sequence_metadata` now has arguments `tick` and `cycle`
diff --git a/docs/previous_versions.md b/docs/previous_versions.md
index 70fbbef..5bbf4d8 100644
--- a/docs/previous_versions.md
+++ b/docs/previous_versions.md
@@ -27,22 +27,61 @@ tables = ts.tables
 
 ## 1.0
 
-TODO: explain changes.
-Rough outline:
-
-1. change `generation` to `tick`
-2. change `pyslim.load( )` to `tskit.load( )`
-3. remove `SlimTreeSequence( )`
-4. change `ts.individuals_alive_at(t)` to `pyslim.individuals_alive_at(ts, t)`
-5. change `pyslim.annotate_defaults( )` to `pyslim.annotate( )`
-
-* Several properties previously provided by SlimTreeSequence are now provided
-    by TreeSequence (e.g., `ts.individual_times`); so these need no change.
-    However, these were briefly available as pyslim methods, so would need changing:
-
-    - change `pyslim.individual_times(ts)` to `ts.individuals_time`
-    - change `pyslim.individual_populations(ts)` to `ts.individuals_population`
-    - change `pyslim.individual_locations(ts)` to `ts.individuals_location``
+The pyslim 1.0 release coincides with that of SLiM v4,
+which introduced a number of changes to SLiM.
+pyslim remains backwards compatible, in that pyslim 1.0
+will happily read tree sequences produced by previous versions of SLiM or pyslim,
+and will convert them to the current version.
+However, previous pyslim code may not work, due to two sets of changes:
+(1) much of the functionality originally in pyslim has moved to tskit
+(e.g., metadata processing), and (2) minor changes to terminology in SLiM v4
+("generation" is now "tick").
+
+Converting previous code should be straightforward, as there are exact replacements.
+The most important changes are to remove calls to `pyslim.load( )` or `SlimTreeSequence( )`,
+and change "generation=" arguments to "tick=".
+
+In more detail, to upgrade code you should:
+
+1. Change `pyslim.load( )` to `tskit.load( )`.
+2. Remove calls to `SlimTreeSequence( )`. They are not needed.
+3. Change `generation` to `tick` in any arguments to functions, or in metadata.
+4. Change `pyslim.annotate_defaults( )` to `pyslim.annotate( )`.
+    and `pyslim.annotate_defaults_tables( )` to `pyslim.annotate_tables( )`.
+5. Change `pyslim.update_tables( )` to `pyslim.update( )`.
+
+Some methods of SlimTreeSequence are now methods of pyslim that take a tree sequence
+as their first argument:
+
+6. Change `ts.recapitate(...)` to `pyslim.recapitate(ts, ...)`.
+7. Change `ts.individuals_alive_at(t)` to `pyslim.individuals_alive_at(ts, t)`.
+8. Change `ts.has_individual_parents()` to `pyslim.has_individual_parents(ts)`,
+    and `ts.individual_parents()` to `pyslim.individual_parents(ts)`.
+9. Replace `ts.first_generation_individuals()` with
+    an appropriate call to `pyslim.individuals_alive_at( )`.
+10. Change `ts.mutation_at(...)` to `pyslim.mutation_at(ts, ...)`.
+    and `ts.nucleotide_at(...)` to `pyslim.nucleotide_at(ts, ...)`.
+
+Several properties previously provided by SlimTreeSequence are now provided
+by TreeSequence (e.g., `ts.individual_times`); so these need no change.
+However, these were briefly available as pyslim methods, so would need changing:
+
+11. Change `pyslim.individual_times(ts)` to `ts.individuals_time`,
+    `pyslim.individual_populations(ts)` to `ts.individuals_population`, and
+    `pyslim.individual_locations(ts)` to `ts.individuals_location`
+
+The change from `pyslim.annotate_defaults( )` to `pyslim.annotate( )`
+also entailed some small changes in behavior. Most notably,
+since msprime.sim_ancestry() now simulates individuals
+by default, annotation does not set up individuals: if you have a tree
+sequence without individuals (e.g., produced by msprime.simulate()) then you
+need to set up those individuals yourself.
+
+To update a tree sequence produced by an old version of SLiM to the current one,
+use `pyslim.update( )`. (However, note that reading it in to SLiM and
+writing it out again might be even easier.)
+
+Also see notes below for 0.700.
 
 
 ## 0.700
diff --git a/pyslim/methods.py b/pyslim/methods.py
index b2a7933..443efac 100644
--- a/pyslim/methods.py
+++ b/pyslim/methods.py
@@ -702,7 +702,6 @@ def _annotate_populations(tables):
             )
         ]
     )
-    num_pops = max(np.max(tables.nodes.population), np.max(do_pops)) + 1
     for j, p in enumerate(tables.populations):
         if j in do_pops:
             if "slim_id" not in p.metadata:


Test patch:
diff --git a/tests/test_annotation.py b/tests/test_annotation.py
index a17a116..1fcf449 100644
--- a/tests/test_annotation.py
+++ b/tests/test_annotation.py
@@ -418,7 +418,7 @@ class TestAnnotate(tests.PyslimTestCase):
                 md = x.metadata
                 assert np.isclose(md['mutation_list'][0]["selection_coeff"], selcoefs[j])
 
-    def test_dont_annotate_mutations(self, helper_functions):
+    def test_dont_annotate_mutations(self):
         # Test the option to not overwrite mutation annotations
         ts = msprime.sim_ancestry(10, random_seed=5)
         ts = msprime.sim_mutations(ts, rate=5, random_seed=3)
@@ -436,6 +436,13 @@ class TestAnnotate(tests.PyslimTestCase):
         pre_mutations.metadata_schema = tables.mutations.metadata_schema
         assert tables.mutations.equals(pre_mutations)
 
+    def test_annotate_empty(self):
+        t = tskit.TableCollection(sequence_length=10)
+        at = pyslim.annotate_tables(t, model_type='nonWF', tick=1)
+        assert t.metadata_schema == pyslim.slim_metadata_schemas['tree_sequence']
+        for x in ("population", "individual", "node", "site", "mutation"):
+            assert getattr(t, x + "s").metadata_schema == pyslim.slim_metadata_schemas[x]
+
     def test_no_populations_errors(self, helper_functions, tmp_path):
         # test SLiM errors when individuals are in non-SLiM populations
         ts = msprime.sim_ancestry(5, population_size=10, sequence_length=100, random_seed=456)