tskit 1.0.2__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {tskit-1.0.2/tskit.egg-info → tskit-1.0.3}/PKG-INFO +1 -1
  2. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/core.c +1 -0
  3. {tskit-1.0.2 → tskit-1.0.3}/tests/test_drawing.py +2 -4
  4. {tskit-1.0.2 → tskit-1.0.3}/tests/test_metadata.py +231 -0
  5. {tskit-1.0.2 → tskit-1.0.3}/tests/test_phylo_formats.py +92 -0
  6. {tskit-1.0.2 → tskit-1.0.3}/tskit/_version.py +1 -1
  7. {tskit-1.0.2 → tskit-1.0.3}/tskit/drawing.py +18 -7
  8. {tskit-1.0.2 → tskit-1.0.3}/tskit/metadata.py +148 -1
  9. {tskit-1.0.2 → tskit-1.0.3}/tskit/tables.py +20 -1
  10. {tskit-1.0.2 → tskit-1.0.3}/tskit/text_formats.py +13 -1
  11. {tskit-1.0.2 → tskit-1.0.3}/tskit/trees.py +102 -2
  12. {tskit-1.0.2 → tskit-1.0.3/tskit.egg-info}/PKG-INFO +1 -1
  13. {tskit-1.0.2 → tskit-1.0.3}/LICENSE +0 -0
  14. {tskit-1.0.2 → tskit-1.0.3}/MANIFEST.in +0 -0
  15. {tskit-1.0.2 → tskit-1.0.3}/README.rst +0 -0
  16. {tskit-1.0.2 → tskit-1.0.3}/_tskitmodule.c +0 -0
  17. {tskit-1.0.2 → tskit-1.0.3}/lib/subprojects/kastore/kastore.c +0 -0
  18. {tskit-1.0.2 → tskit-1.0.3}/lib/subprojects/kastore/kastore.h +0 -0
  19. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/convert.c +0 -0
  20. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/convert.h +0 -0
  21. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/core.h +0 -0
  22. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/genotypes.c +0 -0
  23. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/genotypes.h +0 -0
  24. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/haplotype_matching.c +0 -0
  25. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/haplotype_matching.h +0 -0
  26. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/stats.c +0 -0
  27. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/stats.h +0 -0
  28. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/tables.c +0 -0
  29. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/tables.h +0 -0
  30. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/trees.c +0 -0
  31. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/trees.h +0 -0
  32. {tskit-1.0.2 → tskit-1.0.3}/lib/tskit.h +0 -0
  33. {tskit-1.0.2 → tskit-1.0.3}/lwt_interface/tskit_lwt_interface.h +0 -0
  34. {tskit-1.0.2 → tskit-1.0.3}/pyproject.toml +0 -0
  35. {tskit-1.0.2 → tskit-1.0.3}/setup.cfg +0 -0
  36. {tskit-1.0.2 → tskit-1.0.3}/setup.py +0 -0
  37. {tskit-1.0.2 → tskit-1.0.3}/tests/test_avl_tree.py +0 -0
  38. {tskit-1.0.2 → tskit-1.0.3}/tests/test_balance_metrics.py +0 -0
  39. {tskit-1.0.2 → tskit-1.0.3}/tests/test_cli.py +0 -0
  40. {tskit-1.0.2 → tskit-1.0.3}/tests/test_coalrate.py +0 -0
  41. {tskit-1.0.2 → tskit-1.0.3}/tests/test_combinatorics.py +0 -0
  42. {tskit-1.0.2 → tskit-1.0.3}/tests/test_dict_encoding.py +0 -0
  43. {tskit-1.0.2 → tskit-1.0.3}/tests/test_distance_metrics.py +0 -0
  44. {tskit-1.0.2 → tskit-1.0.3}/tests/test_divmat.py +0 -0
  45. {tskit-1.0.2 → tskit-1.0.3}/tests/test_extend_haplotypes.py +0 -0
  46. {tskit-1.0.2 → tskit-1.0.3}/tests/test_file_format.py +0 -0
  47. {tskit-1.0.2 → tskit-1.0.3}/tests/test_fileobj.py +0 -0
  48. {tskit-1.0.2 → tskit-1.0.3}/tests/test_genotype_matching.py +0 -0
  49. {tskit-1.0.2 → tskit-1.0.3}/tests/test_genotypes.py +0 -0
  50. {tskit-1.0.2 → tskit-1.0.3}/tests/test_haplotype_matching.py +0 -0
  51. {tskit-1.0.2 → tskit-1.0.3}/tests/test_highlevel.py +0 -0
  52. {tskit-1.0.2 → tskit-1.0.3}/tests/test_ibd.py +0 -0
  53. {tskit-1.0.2 → tskit-1.0.3}/tests/test_immutable_table_collection.py +0 -0
  54. {tskit-1.0.2 → tskit-1.0.3}/tests/test_intervals.py +0 -0
  55. {tskit-1.0.2 → tskit-1.0.3}/tests/test_jit.py +0 -0
  56. {tskit-1.0.2 → tskit-1.0.3}/tests/test_ld_matrix.py +0 -0
  57. {tskit-1.0.2 → tskit-1.0.3}/tests/test_ms.py +0 -0
  58. {tskit-1.0.2 → tskit-1.0.3}/tests/test_parsimony.py +0 -0
  59. {tskit-1.0.2 → tskit-1.0.3}/tests/test_provenance.py +0 -0
  60. {tskit-1.0.2 → tskit-1.0.3}/tests/test_python_c.py +0 -0
  61. {tskit-1.0.2 → tskit-1.0.3}/tests/test_reference_sequence.py +0 -0
  62. {tskit-1.0.2 → tskit-1.0.3}/tests/test_relatedness_vector.py +0 -0
  63. {tskit-1.0.2 → tskit-1.0.3}/tests/test_stats.py +0 -0
  64. {tskit-1.0.2 → tskit-1.0.3}/tests/test_table_transforms.py +0 -0
  65. {tskit-1.0.2 → tskit-1.0.3}/tests/test_tables.py +0 -0
  66. {tskit-1.0.2 → tskit-1.0.3}/tests/test_text_formats.py +0 -0
  67. {tskit-1.0.2 → tskit-1.0.3}/tests/test_threads.py +0 -0
  68. {tskit-1.0.2 → tskit-1.0.3}/tests/test_topology.py +0 -0
  69. {tskit-1.0.2 → tskit-1.0.3}/tests/test_tree_positioning.py +0 -0
  70. {tskit-1.0.2 → tskit-1.0.3}/tests/test_tree_stats.py +0 -0
  71. {tskit-1.0.2 → tskit-1.0.3}/tests/test_util.py +0 -0
  72. {tskit-1.0.2 → tskit-1.0.3}/tests/test_utilities.py +0 -0
  73. {tskit-1.0.2 → tskit-1.0.3}/tests/test_vcf.py +0 -0
  74. {tskit-1.0.2 → tskit-1.0.3}/tests/test_version.py +0 -0
  75. {tskit-1.0.2 → tskit-1.0.3}/tests/test_wright_fisher.py +0 -0
  76. {tskit-1.0.2 → tskit-1.0.3}/tskit/__init__.py +0 -0
  77. {tskit-1.0.2 → tskit-1.0.3}/tskit/__main__.py +0 -0
  78. {tskit-1.0.2 → tskit-1.0.3}/tskit/cli.py +0 -0
  79. {tskit-1.0.2 → tskit-1.0.3}/tskit/combinatorics.py +0 -0
  80. {tskit-1.0.2 → tskit-1.0.3}/tskit/exceptions.py +0 -0
  81. {tskit-1.0.2 → tskit-1.0.3}/tskit/genotypes.py +0 -0
  82. {tskit-1.0.2 → tskit-1.0.3}/tskit/intervals.py +0 -0
  83. {tskit-1.0.2 → tskit-1.0.3}/tskit/jit/__init__.py +0 -0
  84. {tskit-1.0.2 → tskit-1.0.3}/tskit/jit/numba.py +0 -0
  85. {tskit-1.0.2 → tskit-1.0.3}/tskit/provenance.py +0 -0
  86. {tskit-1.0.2 → tskit-1.0.3}/tskit/provenance.schema.json +0 -0
  87. {tskit-1.0.2 → tskit-1.0.3}/tskit/stats.py +0 -0
  88. {tskit-1.0.2 → tskit-1.0.3}/tskit/util.py +0 -0
  89. {tskit-1.0.2 → tskit-1.0.3}/tskit/vcf.py +0 -0
  90. {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/SOURCES.txt +0 -0
  91. {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/dependency_links.txt +0 -0
  92. {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/entry_points.txt +0 -0
  93. {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/requires.txt +0 -0
  94. {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tskit
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: The tree sequence toolkit.
5
5
  Author-email: Tskit Developers <admin@tskit.dev>
6
6
  License-Expression: MIT
@@ -121,6 +121,7 @@ tsk_generate_uuid(char *dest, int TSK_UNUSED(flags))
121
121
  out:
122
122
  return ret;
123
123
  }
124
+
124
125
  static const char *
125
126
  tsk_strerror_internal(int err)
126
127
  {
@@ -2929,11 +2929,9 @@ class TestDrawKnownSvg(TestDrawSvgBase):
2929
2929
  # set units
2930
2930
  tables.time_units = "generations"
2931
2931
  ts = tables.tree_sequence()
2932
- svg = ts.draw_svg(y_axis=True, debug_box=draw_plotbox)
2932
+ svg = ts.draw_svg(y_axis=True, title="Y axis test", debug_box=draw_plotbox)
2933
2933
  assert "Time ago (generations)" in svg
2934
- self.verify_known_svg(
2935
- svg, "ts_y_axis.svg", overwrite_viz, width=200 * ts.num_trees
2936
- )
2934
+ self.verify_known_svg(svg, "ts_y_axis.svg", True, width=200 * ts.num_trees)
2937
2935
 
2938
2936
  def test_known_svg_ts_y_axis_regular(self, overwrite_viz, draw_plotbox):
2939
2937
  # This should have gridlines
@@ -627,6 +627,237 @@ class TestJSONCodec:
627
627
  assert ms.decode_row(b"") == {}
628
628
 
629
629
 
630
+ class TestJSONStructCodec:
631
+ def test_requires_subschemas(self):
632
+ with pytest.raises(
633
+ tskit.MetadataSchemaValidationError,
634
+ match="requires 'json' and 'struct' schema mappings",
635
+ ):
636
+ tskit.MetadataSchema({"codec": "json+struct"})
637
+
638
+ def test_disallow_duplicate_keys(self):
639
+ schema = {
640
+ "codec": "json+struct",
641
+ "json": {"type": "object", "properties": {"x": {"type": "number"}}},
642
+ "struct": {
643
+ "type": "object",
644
+ "properties": {"x": {"type": "number", "binaryFormat": "i"}},
645
+ },
646
+ }
647
+ with pytest.raises(
648
+ tskit.MetadataSchemaValidationError, match="must not share property names"
649
+ ):
650
+ tskit.MetadataSchema(schema)
651
+
652
+ def test_round_trip_with_struct_and_json(self):
653
+ schema = {
654
+ "codec": "json+struct",
655
+ "json": {
656
+ "type": "object",
657
+ "properties": {
658
+ "label": {"type": "string"},
659
+ "count": {"type": "number"},
660
+ "stuff": {"type": "array"},
661
+ },
662
+ "required": ["label"],
663
+ },
664
+ "struct": {
665
+ "type": "object",
666
+ "properties": {
667
+ "b0": {"type": "number", "binaryFormat": "i"},
668
+ "b1": {"type": "number", "binaryFormat": "i"},
669
+ "xyz": {
670
+ "type": "array",
671
+ "arrayLengthFormat": "H",
672
+ "items": {"type": "number", "binaryFormat": "i"},
673
+ },
674
+ },
675
+ },
676
+ }
677
+ ms = tskit.MetadataSchema(schema)
678
+ for v in [[], [0, 2, 12], [5] * 1000]:
679
+ row = {
680
+ "label": "abcdef xyz",
681
+ "count": 7,
682
+ "b0": 123,
683
+ "b1": 0,
684
+ "stuff": [1, 3, 2, -1.5, "abc", None],
685
+ "xyz": v,
686
+ "another_thing": "since JSON is permissive this is allowed",
687
+ }
688
+ encoded = ms.validate_and_encode_row(row)
689
+ out = ms.decode_row(encoded)
690
+ assert out == row
691
+
692
+ def schema_with_binary(self, num_binary_ints):
693
+ # produces a json+struct schema having num_binary_ints integers
694
+ # encoded in binary, labeled b0, ... bX
695
+ schema = {
696
+ "codec": "json+struct",
697
+ "json": {
698
+ "type": "object",
699
+ "properties": {
700
+ "label": {"type": "string"},
701
+ "count": {"type": "number"},
702
+ },
703
+ "required": ["label"],
704
+ },
705
+ "struct": {
706
+ "type": "object",
707
+ "properties": {
708
+ f"b{j}": {
709
+ "type": "integer",
710
+ "binaryFormat": "i",
711
+ }
712
+ for j in range(num_binary_ints)
713
+ },
714
+ },
715
+ }
716
+ return tskit.MetadataSchema(schema)
717
+
718
+ @pytest.mark.parametrize("k", (0, 1, 5, 1001))
719
+ def test_byte_alignment(self, k):
720
+ # We want to test whether the binary portion begins byte-aligned.
721
+ # To verify this, we (somewhat pedantically) let:
722
+ # X = (bytes to encode the json without any binary)
723
+ # Y = (bytes to encode the same json with k ints in binary)
724
+ # and then:
725
+ # (a) if padding is correct, X should be divisible by 8;
726
+ # but to make sure that in fact the binary portion starts after X bytes,
727
+ # we also check that:
728
+ # (b) Y-X is equal to k * (bytes per int)
729
+ ms = self.schema_with_binary(k)
730
+ ms0 = self.schema_with_binary(0)
731
+ bytes_per_int = len(struct.pack("i", 0))
732
+ for s in [
733
+ "",
734
+ "a",
735
+ "ab",
736
+ "abc",
737
+ "abcd",
738
+ "abcde",
739
+ "abcdef",
740
+ "abcdefg",
741
+ "abcdefgh",
742
+ " " * 1000 + "foo" + " " * 1000,
743
+ ]:
744
+ row = {"label": s, "count": 7}
745
+ encoded0 = ms0.validate_and_encode_row(row)
746
+ row.update({f"b{j}": j for j in range(k)})
747
+ encoded = ms.validate_and_encode_row(row)
748
+ out = ms.decode_row(encoded)
749
+ assert out == row
750
+ # validate byte alignment
751
+ assert len(encoded0) % 8 == 0
752
+ assert len(encoded) - len(encoded0) == k * bytes_per_int
753
+
754
+ def test_json_defaults_applied(self):
755
+ schema = {
756
+ "codec": "json+struct",
757
+ "json": {
758
+ "type": "object",
759
+ "properties": {"number": {"type": "number", "default": 5}},
760
+ },
761
+ "struct": {"type": "object", "properties": {}},
762
+ }
763
+ ms = tskit.MetadataSchema(schema)
764
+ assert ms.decode_row(ms.validate_and_encode_row({})) == {"number": 5}
765
+ assert ms.decode_row(ms.validate_and_encode_row({"number": 9})) == {"number": 9}
766
+
767
+ def test_nested_default_error(self):
768
+ schema = {
769
+ "codec": "json+struct",
770
+ "json": {
771
+ "type": "object",
772
+ "properties": {
773
+ "obj": {
774
+ "type": "object",
775
+ "properties": {
776
+ "nested_obj_no_default": {
777
+ "type": "object",
778
+ "properties": {},
779
+ },
780
+ "nested_obj": {
781
+ "type": "object",
782
+ "properties": {},
783
+ "default": {"foo": "bar"},
784
+ },
785
+ },
786
+ }
787
+ },
788
+ },
789
+ "struct": {"type": "object", "properties": {}},
790
+ }
791
+ with pytest.raises(
792
+ tskit.MetadataSchemaValidationError,
793
+ match="Defaults can only be specified at the top level for JSON codec",
794
+ ):
795
+ tskit.MetadataSchema(schema)
796
+
797
+ def test_decode_without_magic_errors(self):
798
+ ms = tskit.MetadataSchema(
799
+ {
800
+ "codec": "json+struct",
801
+ "json": {"type": "object", "properties": {}},
802
+ "struct": {"type": "object", "properties": {}},
803
+ }
804
+ )
805
+ with pytest.raises(ValueError, match="missing magic header"):
806
+ ms.decode_row(b"{}")
807
+
808
+ def test_decode_version_mismatch(self):
809
+ ms = tskit.MetadataSchema(
810
+ {
811
+ "codec": "json+struct",
812
+ "json": {"type": "object", "properties": {}},
813
+ "struct": {"type": "object", "properties": {}},
814
+ }
815
+ )
816
+ header = metadata.JSONStructCodec._HDR.pack(
817
+ metadata.JSONStructCodec.MAGIC,
818
+ metadata.JSONStructCodec.VERSION + 1,
819
+ len(b"{}"),
820
+ 0,
821
+ )
822
+ with pytest.raises(
823
+ ValueError,
824
+ match="Unsupported json\\+struct version",
825
+ ):
826
+ ms.decode_row(header + b"{}")
827
+
828
+ def test_decode_truncated_lengths(self):
829
+ schema = {
830
+ "codec": "json+struct",
831
+ "json": {"type": "object", "properties": {}},
832
+ "struct": {"type": "object", "properties": {}},
833
+ }
834
+ ms = tskit.MetadataSchema(schema)
835
+ header = metadata.JSONStructCodec._HDR.pack(
836
+ metadata.JSONStructCodec.MAGIC, metadata.JSONStructCodec.VERSION, 5, 0
837
+ )
838
+ with pytest.raises(ValueError, match="declared lengths exceed buffer size"):
839
+ ms.decode_row(header + b"abc")
840
+
841
+ header = metadata.JSONStructCodec._HDR.pack(
842
+ metadata.JSONStructCodec.MAGIC, metadata.JSONStructCodec.VERSION, 1, 3
843
+ )
844
+ with pytest.raises(ValueError, match="declared lengths exceed buffer size"):
845
+ ms.decode_row(header + b"a")
846
+
847
+ def test_missing_struct_property_fails_validation(self):
848
+ schema = {
849
+ "codec": "json+struct",
850
+ "json": {"type": "object", "properties": {}},
851
+ "struct": {
852
+ "type": "object",
853
+ "properties": {"payload": {"type": "integer", "binaryFormat": "i"}},
854
+ },
855
+ }
856
+ ms = tskit.MetadataSchema(schema)
857
+ with pytest.raises(tskit.MetadataValidationError, match="required property"):
858
+ ms.validate_and_encode_row({})
859
+
860
+
630
861
  class TestStructCodec:
631
862
  def encode_decode(self, method_name, sub_schema, obj, buffer):
632
863
  assert (
@@ -26,6 +26,7 @@ Tests for phylogenetics export functions, newick, nexus, FASTA etc.
26
26
 
27
27
  import functools
28
28
  import io
29
+ import random
29
30
  import textwrap
30
31
 
31
32
  import dendropy
@@ -334,6 +335,97 @@ class TestNexusIncludeSections:
334
335
  )
335
336
 
336
337
 
338
+ class TestNexusNodeLabels:
339
+ @tests.cached_example
340
+ def balanced_tree(self):
341
+ # 4
342
+ # ┏━┻┓
343
+ # ┃ 3
344
+ # ┃ ┏┻┓
345
+ # 0 1 2
346
+ return tskit.Tree.generate_balanced(3)
347
+
348
+ def test_as_nexus_labels_basic(self):
349
+ ts = self.balanced_tree().tree_sequence
350
+ labels = {0: "human", 1: "chimp", 2: "bonobo"}
351
+ expected = textwrap.dedent(
352
+ """\
353
+ #NEXUS
354
+ BEGIN TAXA;
355
+ DIMENSIONS NTAX=3;
356
+ TAXLABELS human chimp bonobo;
357
+ END;
358
+ BEGIN TREES;
359
+ TRANSLATE n0 human, n1 chimp, n2 bonobo;
360
+ TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
361
+ END;
362
+ """
363
+ )
364
+ assert expected == ts.as_nexus(include_alignments=False, node_labels=labels)
365
+
366
+ def test_as_nexus_labels_partial(self):
367
+ ts = self.balanced_tree().tree_sequence
368
+ labels = {0: "human", 2: "bonobo"}
369
+ expected = textwrap.dedent(
370
+ """\
371
+ #NEXUS
372
+ BEGIN TAXA;
373
+ DIMENSIONS NTAX=3;
374
+ TAXLABELS human n1 bonobo;
375
+ END;
376
+ BEGIN TREES;
377
+ TRANSLATE n0 human, n2 bonobo;
378
+ TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
379
+ END;
380
+ """
381
+ )
382
+ assert expected == ts.as_nexus(include_alignments=False, node_labels=labels)
383
+
384
+ def test_as_nexus_labels_none(self):
385
+ ts = self.balanced_tree().tree_sequence
386
+ expected = textwrap.dedent(
387
+ """\
388
+ #NEXUS
389
+ BEGIN TAXA;
390
+ DIMENSIONS NTAX=3;
391
+ TAXLABELS n0 n1 n2;
392
+ END;
393
+ BEGIN TREES;
394
+ TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
395
+ END;
396
+ """
397
+ )
398
+ assert expected == ts.as_nexus(include_alignments=False, node_labels=None)
399
+
400
+ @pytest.mark.parametrize("ts", get_example_tree_sequences())
401
+ def test_parseable(self, ts):
402
+ def all_samples_are_leaves(ts):
403
+ internal_nodes = np.unique(ts.edges_parent)
404
+ is_internal_sample = np.isin(ts.samples(), internal_nodes)
405
+ return not np.any(is_internal_sample)
406
+
407
+ if not all_samples_are_leaves(ts):
408
+ # TRANSLATE doesn't support translating internal nodes
409
+ return
410
+
411
+ for tree in ts.trees():
412
+ if not tree.has_single_root:
413
+ return
414
+
415
+ labels = {}
416
+ samples = ts.samples()
417
+ k = random.randint(1, len(samples))
418
+ for node in random.sample(list(samples), k):
419
+ labels[node] = f"new_node_which_was_{node}"
420
+
421
+ nexus = ts.as_nexus(include_alignments=False, node_labels=labels)
422
+ ds = dendropy.DataSet.get(data=nexus, schema="nexus")
423
+ tree = ds.tree_lists[0][0]
424
+ dendropy_labels = [node.taxon.label for node in tree.nodes() if node.taxon]
425
+ for label in labels.values():
426
+ assert label.replace("_", " ") in dendropy_labels
427
+
428
+
337
429
  class TestNewickCodePaths:
338
430
  """
339
431
  Test that the different code paths we use under the hood lead to
@@ -1,4 +1,4 @@
1
1
  # Definitive location for the version number.
2
2
  # During development, should be x.y.z.devN
3
3
  # For beta should be x.y.zbN
4
- tskit_version = "1.0.2"
4
+ tskit_version = "1.0.3"
@@ -283,6 +283,15 @@ class Timescaling:
283
283
  y_scale = self.plot_range / (self.max_time - self.min_time)
284
284
  return self.plot_min - (y - self.min_time) * y_scale
285
285
 
286
+ def with_offset(self, y_offset):
287
+ return Timescaling(
288
+ max_time=self.max_time,
289
+ min_time=self.min_time,
290
+ plot_min=self.plot_min + y_offset,
291
+ plot_range=self.plot_range,
292
+ use_log_transform=self.use_log_transform,
293
+ )
294
+
286
295
 
287
296
  class SVGString(str):
288
297
  "A string containing an SVG representation"
@@ -1460,7 +1469,7 @@ class SvgTreeSequence(SvgAxisPlot):
1460
1469
  size=subplot_size, num_skipped=tree.index - last_used_index
1461
1470
  )
1462
1471
  )
1463
- y = self.plotbox.top
1472
+ y_offset = self.plotbox.top
1464
1473
  if title is not None:
1465
1474
  self.add_text_in_group(
1466
1475
  title,
@@ -1481,14 +1490,16 @@ class SvgTreeSequence(SvgAxisPlot):
1481
1490
  tick_length_upper=self.default_tick_length_site, # TODO - parameterize
1482
1491
  x_regions=x_regions,
1483
1492
  )
1484
- y_low = self.tree_plotbox.bottom
1485
- if y_axis is not None:
1493
+ y_low = self.tree_plotbox.bottom + y_offset
1494
+ if self.y_axis:
1486
1495
  tscales = {s.timescaling for s in subplots if s.timescaling}
1487
1496
  if len(tscales) > 1:
1488
1497
  raise ValueError(
1489
1498
  "Can't draw a tree sequence Y axis if trees vary in timescale"
1490
1499
  )
1491
- self.timescaling = tscales.pop()
1500
+ # The timescaling of all subplots is used for outer box, but we
1501
+ # need to shift it by the top padding to account for e.g. titles
1502
+ self.timescaling = tscales.pop().with_offset(y_offset)
1492
1503
  y_low = self.timescaling.transform(self.timescaling.min_time)
1493
1504
  if y_ticks is None:
1494
1505
  used_nodes = edge_and_sample_nodes(ts, breaks[skipbreaks])
@@ -1499,11 +1510,11 @@ class SvgTreeSequence(SvgAxisPlot):
1499
1510
 
1500
1511
  self.draw_y_axis(
1501
1512
  ticks=check_y_ticks(y_ticks),
1502
- upper=self.tree_plotbox.top,
1513
+ upper=self.tree_plotbox.top + y_offset,
1503
1514
  lower=y_low,
1504
1515
  tick_length_outer=self.default_tick_length,
1505
1516
  gridlines=y_gridlines,
1506
- side="right" if y_axis == "right" else "left",
1517
+ side="right" if self.y_axis == "right" else "left",
1507
1518
  )
1508
1519
 
1509
1520
  subplot_x = self.plotbox.left
@@ -1513,7 +1524,7 @@ class SvgTreeSequence(SvgAxisPlot):
1513
1524
  svg_subplot = container.add(
1514
1525
  self.drawing.g(
1515
1526
  class_=subplot.svg_class,
1516
- transform=f"translate({rnd(subplot_x)} {y})",
1527
+ transform=f"translate({rnd(subplot_x)} {y_offset})",
1517
1528
  )
1518
1529
  )
1519
1530
  for svg_items in subplot.root_groups.values():
@@ -195,6 +195,149 @@ class NOOPCodec(AbstractMetadataCodec):
195
195
  return data
196
196
 
197
197
 
198
+ class JSONStructCodec(AbstractMetadataCodec):
199
+ """
200
+ Pack canonical JSON metadata together with a struct-encoded binary payload.
201
+ The codec expects a metadata schema with separate ``json`` and ``struct``
202
+ subschemas and produces a single dict containing the union of the keys from
203
+ those subschemas after decoding.
204
+
205
+ The structure of the encoded metadata is as follows: first, a fixed-size
206
+ header that contains the number of bytes for both ``json`` and ``struct``
207
+ portions; next, the json; then a variable number of zeroed padding bytes
208
+ that brings the length to a multiple of 8 for alignment; and finally
209
+ the struct-encoded binary portion.
210
+ """
211
+
212
+ MAGIC = b"JBLB"
213
+ VERSION = 1
214
+ _HDR = struct.Struct("<4sBQQ") # magic, version, json_len, blob_len
215
+
216
+ @classmethod
217
+ def is_schema_trivial(cls, schema: Mapping) -> bool:
218
+ return False
219
+
220
+ def __init__(self, schema: Mapping[str, Any]) -> None:
221
+ json_schema = schema.get("json")
222
+ struct_schema = schema.get("struct")
223
+ if not isinstance(json_schema, Mapping) or not isinstance(
224
+ struct_schema, Mapping
225
+ ):
226
+ raise exceptions.MetadataSchemaValidationError(
227
+ "json+struct requires 'json' and 'struct' schema mappings"
228
+ )
229
+
230
+ json_schema = copy.deepcopy(dict(json_schema, codec="json"))
231
+ struct_schema = copy.deepcopy(dict(struct_schema, codec="struct"))
232
+
233
+ try:
234
+ json_schema = JSONCodec.modify_schema(json_schema)
235
+ JSONCodec.schema_validator.check_schema(json_schema)
236
+ except jsonschema.exceptions.SchemaError as ve:
237
+ raise exceptions.MetadataSchemaValidationError(str(ve)) from ve
238
+ try:
239
+ struct_schema = StructCodec.modify_schema(struct_schema)
240
+ StructCodecSchemaValidator.check_schema(struct_schema)
241
+ except jsonschema.exceptions.SchemaError as ve:
242
+ raise exceptions.MetadataSchemaValidationError(str(ve)) from ve
243
+
244
+ self.json_schema = json_schema
245
+ self.struct_schema = struct_schema
246
+ json_props = self.json_schema.get("properties", {})
247
+ struct_props = self.struct_schema.get("properties", {})
248
+ overlap = set(json_props).intersection(struct_props)
249
+ if overlap:
250
+ raise exceptions.MetadataSchemaValidationError(
251
+ "json and struct schemas must not share property names: "
252
+ + ", ".join(sorted(overlap))
253
+ )
254
+ for name, sub_schema in (
255
+ ("json", self.json_schema),
256
+ ("struct", self.struct_schema),
257
+ ):
258
+ sub_type = sub_schema.get("type")
259
+ if sub_type is None:
260
+ continue
261
+ if isinstance(sub_type, list):
262
+ is_object = "object" in sub_type
263
+ else:
264
+ is_object = sub_type == "object"
265
+ if not is_object:
266
+ raise exceptions.MetadataSchemaValidationError(
267
+ f"{name} subschema must describe an object for json+struct codec"
268
+ )
269
+
270
+ self.json_codec = JSONCodec(self.json_schema)
271
+ self.struct_codec = StructCodec(self.struct_schema)
272
+ self._struct_keys = set(struct_props.keys())
273
+ self._validate_json = TSKITMetadataSchemaValidator(self.json_schema).validate
274
+ self._validate_struct = TSKITMetadataSchemaValidator(self.struct_schema).validate
275
+
276
+ def validate_row(self, row: Any) -> None:
277
+ if not isinstance(row, dict):
278
+ raise exceptions.MetadataValidationError(
279
+ "json+struct metadata must be a mapping"
280
+ )
281
+ struct_data = {k: v for k, v in row.items() if k in self._struct_keys}
282
+ json_data = {k: v for k, v in row.items() if k not in self._struct_keys}
283
+ try:
284
+ self._validate_json(json_data)
285
+ self._validate_struct(struct_data)
286
+ except jsonschema.exceptions.ValidationError as ve:
287
+ raise exceptions.MetadataValidationError(str(ve)) from ve
288
+
289
+ def encode(self, obj: Any) -> bytes:
290
+ if not isinstance(obj, dict):
291
+ raise exceptions.MetadataEncodingError(
292
+ "json+struct metadata must be a mapping"
293
+ )
294
+ json_bytes = self.json_codec.encode(
295
+ {k: v for k, v in obj.items() if k not in self._struct_keys}
296
+ )
297
+ blob_bytes = self.struct_codec.encode(
298
+ {k: v for k, v in obj.items() if k in self._struct_keys}
299
+ )
300
+ header = self._HDR.pack(
301
+ self.MAGIC, self.VERSION, len(json_bytes), len(blob_bytes)
302
+ )
303
+ padding_bytes = bytes((8 - ((len(header) + len(json_bytes)) % 8)) % 8)
304
+ return header + json_bytes + padding_bytes + blob_bytes
305
+
306
+ def decode(self, encoded: bytes) -> Any:
307
+ if len(encoded) >= self._HDR.size and encoded[:4] == self.MAGIC:
308
+ _, version, jlen, blen = self._HDR.unpack_from(encoded)
309
+ if version != self.VERSION:
310
+ raise ValueError("Unsupported json+struct version")
311
+ start = self._HDR.size
312
+ # the second mod 8 is here because if start+jlen % 8 is 0 we get 8, not 0
313
+ padding_length = (8 - ((start + jlen) % 8)) % 8
314
+ if (
315
+ jlen > len(encoded) - start
316
+ or blen > len(encoded) - start - jlen - padding_length
317
+ ):
318
+ raise ValueError(
319
+ "Invalid json+struct payload: declared lengths exceed buffer size"
320
+ )
321
+ json_bytes = encoded[start : start + jlen]
322
+ blob_bytes = encoded[
323
+ start + jlen + padding_length : start + jlen + padding_length + blen
324
+ ]
325
+ json_data = self.json_codec.decode(json_bytes)
326
+ struct_data = self.struct_codec.decode(blob_bytes)
327
+ overlap = set(json_data).intersection(struct_data)
328
+ if overlap:
329
+ raise ValueError(
330
+ "json+struct decoded duplicate keys: " + ", ".join(sorted(overlap))
331
+ )
332
+ combined = dict(json_data)
333
+ combined.update(struct_data)
334
+ return combined
335
+ raise ValueError("Invalid json+struct payload: missing magic header")
336
+
337
+
338
+ register_metadata_codec(JSONStructCodec, "json+struct")
339
+
340
+
198
341
  def binary_format_validator(validator, types, instance, schema):
199
342
  # We're hooking into jsonschemas validation code here, which works by creating
200
343
  # generators of exceptions, hence the yielding
@@ -805,7 +948,11 @@ class MetadataSchema:
805
948
  self._schema = codec_cls.modify_schema(schema)
806
949
  self.codec_instance = codec_cls(self._schema)
807
950
  self._string = tskit.canonical_json(self._schema)
808
- self._validate_row = TSKITMetadataSchemaValidator(self._schema).validate
951
+ self._validate_row = getattr(
952
+ self.codec_instance,
953
+ "validate_row",
954
+ TSKITMetadataSchemaValidator(self._schema).validate,
955
+ )
809
956
  self._bypass_validation = codec_cls.is_schema_trivial(schema)
810
957
  self.encode_row = self.codec_instance.encode
811
958
  self.decode_row = self.codec_instance.decode
@@ -3455,6 +3455,24 @@ class TableCollection(metadata.MetadataProvider):
3455
3455
 
3456
3456
  @classmethod
3457
3457
  def load(cls, file_or_path, *, skip_tables=False, skip_reference_sequence=False):
3458
+ """
3459
+ Load a :class:`TableCollection` from a file or path, saved in the format defined
3460
+ by :meth:`.dump`. Although the file must be in the correct format, unlike
3461
+ :func:`tskit.load` further validation is not performed to check that the tables
3462
+ meet the :ref:`sec_valid_tree_sequence_requirements`. For instance,
3463
+ unsorted tables can be loaded without error and then sorted before
3464
+ :meth:`creating <TableCollection.tree_sequence>` a :class:`TreeSequence`.
3465
+
3466
+ :param file_or_path: The file object or path from which to load the
3467
+ TableCollection.
3468
+ :param bool skip_tables: If True, no tables are read from the file and
3469
+ only the top-level information is populated in the returned TableCollection.
3470
+ :param bool skip_reference_sequence: If True, tables are read
3471
+ without loading any reference sequence.
3472
+ :return: A TableCollection instance
3473
+ :rtype: TableCollection
3474
+ :raises: **tskit.FileFormatError** -- If the file is not in a valid format.
3475
+ """
3458
3476
  file, local_file = util.convert_file_like_to_open_file(file_or_path, "rb")
3459
3477
  ll_tc = _tskit.TableCollection()
3460
3478
  try:
@@ -3474,7 +3492,8 @@ class TableCollection(metadata.MetadataProvider):
3474
3492
  """
3475
3493
  Writes the table collection to the specified path or file object.
3476
3494
 
3477
- :param str file_or_path: The file object or path to write the TreeSequence to.
3495
+ :param str file_or_path: The file object or path to which to write this
3496
+ TableCollection.
3478
3497
  """
3479
3498
  file, local_file = util.convert_file_like_to_open_file(file_or_path, "wb")
3480
3499
  try:
@@ -120,6 +120,7 @@ def write_nexus(
120
120
  include_alignments,
121
121
  reference_sequence,
122
122
  missing_data_character,
123
+ node_labels,
123
124
  isolated_as_missing=None,
124
125
  ):
125
126
  # See TreeSequence.write_nexus for documentation on parameters.
@@ -134,7 +135,13 @@ def write_nexus(
134
135
  print("#NEXUS", file=out)
135
136
  print("BEGIN TAXA;", file=out)
136
137
  print("", f"DIMENSIONS NTAX={ts.num_samples};", sep=indent, file=out)
137
- taxlabels = " ".join(f"n{u}" for u in ts.samples())
138
+
139
+ if node_labels is not None:
140
+ taxlabels = " ".join(
141
+ node_labels[u] if u in node_labels else f"n{u}" for u in ts.samples()
142
+ )
143
+ else:
144
+ taxlabels = " ".join(f"n{u}" for u in ts.samples())
138
145
  print("", f"TAXLABELS {taxlabels};", sep=indent, file=out)
139
146
  print("END;", file=out)
140
147
 
@@ -166,6 +173,11 @@ def write_nexus(
166
173
  include_trees = True if include_trees is None else include_trees
167
174
  if include_trees:
168
175
  print("BEGIN TREES;", file=out)
176
+
177
+ if node_labels is not None:
178
+ translations = ", ".join(f"n{u} {name}" for u, name in node_labels.items())
179
+ print(f" TRANSLATE {translations};", file=out)
180
+
169
181
  for tree in ts.trees():
170
182
  start_interval = "{0:.{1}f}".format(tree.interval.left, pos_precision)
171
183
  end_interval = "{0:.{1}f}".format(tree.interval.right, pos_precision)
@@ -6797,6 +6797,7 @@ class TreeSequence:
6797
6797
  reference_sequence=None,
6798
6798
  missing_data_character=None,
6799
6799
  isolated_as_missing=None,
6800
+ node_labels=None,
6800
6801
  ):
6801
6802
  """
6802
6803
  Returns a `nexus encoding <https://en.wikipedia.org/wiki/Nexus_file>`_
@@ -6896,6 +6897,10 @@ class TreeSequence:
6896
6897
  :param str missing_data_character: As for the :meth:`.alignments` method,
6897
6898
  but defaults to "?".
6898
6899
  :param bool isolated_as_missing: As for the :meth:`.alignments` method.
6900
+ :param node_labels: A map of type `{node_id: name}`. Samples present
6901
+ in the map will have the given name instead of `n{node_id}`. Note
6902
+ that the names must not have whitespace (spaces should be replaced
6903
+ by underscores) or puncuation in them.
6899
6904
  :return: A nexus representation of this :class:`TreeSequence`
6900
6905
  :rtype: str
6901
6906
  """
@@ -6908,6 +6913,7 @@ class TreeSequence:
6908
6913
  reference_sequence=reference_sequence,
6909
6914
  missing_data_character=missing_data_character,
6910
6915
  isolated_as_missing=isolated_as_missing,
6916
+ node_labels=node_labels,
6911
6917
  )
6912
6918
 
6913
6919
  def as_nexus(self, **kwargs):
@@ -10930,12 +10936,106 @@ class TreeSequence:
10930
10936
  def ld_matrix(
10931
10937
  self,
10932
10938
  sample_sets=None,
10933
- sites=None,
10934
- positions=None,
10935
10939
  mode="site",
10936
10940
  stat="r2",
10941
+ sites=None,
10942
+ positions=None,
10937
10943
  indexes=None,
10938
10944
  ):
10945
+ r"""
10946
+
10947
+ Returns a matrix of the specified two-locus statistic (default
10948
+ :math:`r^2`) computed from sample allelic states or branch lengths.
10949
+ The resulting linkage disequilibrium (LD) matrix represents either the
10950
+ two-locus statistic as computed between all pairs of specified
10951
+ ``sites`` (``"site"`` mode, producing a
10952
+ ``len(sites)``-by-``len(sites)`` sized matrix), or as computed from the
10953
+ branch structures at marginal trees between pairs of trees at all
10954
+ specified ``positions`` (``"branch"`` mode, producing a
10955
+ ``len(positions)``-by-``len(positions)`` sized matrix).
10956
+
10957
+ The sites considered for ``"site"`` mode defaults to all sites (which may
10958
+ result in a very large matrix!), but can be restricted using
10959
+ the ``sites`` argument. Sites must be passed as a list of lists,
10960
+ specifying the ``[row_sites, col_sites]``, resulting in a
10961
+ rectangular matrix, or by specifying a single list of ``[sites]``, in
10962
+ which a square matrix will be produced (see
10963
+ :ref:`sec_stats_two_locus_site` for examples). Here, ``sites``,
10964
+ ``row_sites``, and ``col_sites`` are each lists of site indexes.
10965
+
10966
+ Similarly, in the ``"branch"`` mode, the ``positions`` argument specifies
10967
+ genomic coordinates at which the expectation for the two-locus statistic
10968
+ is computed, given the local tree structure.
10969
+ (See :ref:`sec_stats_two_locus_branch` for explanation of in what sense
10970
+ this is an expectation.) This defaults to computing
10971
+ the LD for each pair of distinct trees (this is equivalent to passing in
10972
+ the leftmost coordinates of each tree's span, since intervals are closed on
10973
+ the left and open on the right). Similar to the site mode, a nested list
10974
+ of row and column positions can be specified separately (resulting in a
10975
+ rectangular matrix) or a single list of a specified positions results
10976
+ in a square matrix (see :ref:`sec_stats_two_locus_branch` for
10977
+ examples). Like ``sites``, the ``positions`` must be specified as a list
10978
+ of lists.
10979
+
10980
+ Some LD statistics are defined for both within a single set of samples
10981
+ and for two sample sets. If the ``indexes`` argument is specified, then
10982
+ ``indexes`` specifies the indexes of the sample sets in the
10983
+ ``sample_sets`` list between which to compute LD. For instance, this
10984
+ results in a 3D array whose ``[k,:,:]``-th slice contains LD values
10985
+ between ``sample_sets[i]`` and ``sample_sets[j]``, where ``(i, j)`` is
10986
+ the ``k``-th element of ``indexes``.
10987
+
10988
+ For more on how the ``indexes`` and ``sample_sets`` interact with the
10989
+ output dimensions, see the :ref:`sec_stats_two_locus_sample_sets`
10990
+ section. Statistics are defined in the
10991
+ :ref:`sec_stats_two_locus_summary_functions_two_way` section.
10992
+
10993
+ **Available Stats** (use ``Stat Name`` in the ``stat`` keyword
10994
+ argument). Statistics marked as "multi sample set" allow
10995
+ (but do not require) computation from two sample sets
10996
+ via the ``indexes`` argument.
10997
+
10998
+ ======================= ========== ================ ==============
10999
+ Stat Polarised Multi Sample Set Stat Name
11000
+ ======================= ========== ================ ==============
11001
+ :math:`r^2` n y "r2"
11002
+ :math:`r` y n "r"
11003
+ :math:`D^2` n y "D2"
11004
+ :math:`D` y n "D"
11005
+ :math:`D'` y n "D_prime"
11006
+ :math:`D_z` n n "Dz"
11007
+ :math:`\pi_2` n n "pi2"
11008
+ :math:`\widehat{D^2}` n y "D2_unbiased"
11009
+ :math:`\widehat{D_z}` n n "Dz_unbiased"
11010
+ :math:`\widehat{\pi_2}` n n "pi2_unbiased"
11011
+ ======================= ========== ================ ==============
11012
+
11013
+ :param list sample_sets: A list, or a list of lists of sample node IDs,
11014
+ specifying the groups of nodes to compute the statistic with. Defaults
11015
+ to all samples.
11016
+ :param str mode: A string giving the "type" of the statistic to be
11017
+ computed. Defaults to "site", can be "site" or "branch".
11018
+ :param str stat: A string giving the selected two-locus statistic to
11019
+ compute. Defaults to "r2".
11020
+ :param list sites: A list of lists of sites over which to compute an
11021
+ LD matrix. Can be specified as a list of lists to control the row
11022
+ and column sites. Only available in "site" mode. Specify as
11023
+ ``[row_sites, col_sites]`` or ``[all_sites]``.
11024
+ Defaults to all sites.
11025
+ :param list positions: A list of lists of genomic positions where
11026
+ expected LD is computed based on tree topologies and branch
11027
+ lengths. Only applicable in "branch" mode. Specify as a list of
11028
+ two lists to control the row and column positions, as
11029
+ ``[row_positions, col_positions]``, or ``[all_positions]``.
11030
+ Defaults to the leftmost coordinates of all trees and computes
11031
+ LD between all pairs of trees.
11032
+ :param list indexes: A list of 2-tuples or a single 2-tuple, specifying
11033
+ the indexes of two sample sets over which to compute a two-way LD
11034
+ statistic. Only :math:`r^2`, :math:`D^2`, and :math:`\widehat{D^2}`
11035
+ are implemented for two-way statistics.
11036
+ :return: A 2D or 3D array of LD matrices.
11037
+ :rtype: numpy.ndarray
11038
+ """
10939
11039
  one_way_stats = {
10940
11040
  "D": self._ll_tree_sequence.D_matrix,
10941
11041
  "D2": self._ll_tree_sequence.D2_matrix,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tskit
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: The tree sequence toolkit.
5
5
  Author-email: Tskit Developers <admin@tskit.dev>
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes