38
38
from tsinfer import formats
39
39
40
40
41
- def ts_to_dataset (ts , chunks = None , samples = None ):
41
+ def ts_to_dataset (ts , chunks = None , samples = None , contigs = None ):
42
42
"""
43
43
# From https://github.com/sgkit-dev/sgkit/blob/main/sgkit/tests/test_popgen.py#L63
44
44
Convert the specified tskit tree sequence into an sgkit dataset.
@@ -63,7 +63,7 @@ def ts_to_dataset(ts, chunks=None, samples=None):
63
63
genotypes = np .expand_dims (genotypes , axis = 2 )
64
64
65
65
ds = sgkit .create_genotype_call_dataset (
66
- variant_contig_names = ["1" ],
66
+ variant_contig_names = ["1" ] if contigs is None else contigs ,
67
67
variant_contig = np .zeros (len (tables .sites ), dtype = int ),
68
68
variant_position = tables .sites .position .astype (int ),
69
69
variant_allele = alleles ,
@@ -280,18 +280,102 @@ def test_simulate_genotype_call_dataset(tmp_path):
280
280
ts = msprime .sim_ancestry (4 , sequence_length = 1000 , random_seed = 123 )
281
281
ts = msprime .sim_mutations (ts , rate = 2e-3 , random_seed = 123 )
282
282
ds = ts_to_dataset (ts )
283
- ds .update ({"variant_ancestral_allele" : ds ["variant_allele" ][:, 0 ]})
284
283
ds .to_zarr (tmp_path , mode = "w" )
285
- sd = tsinfer .VariantData (tmp_path , "variant_ancestral_allele" )
286
- ts = tsinfer .infer (sd )
287
- for v , ds_v , sd_v in zip (ts .variants (), ds .call_genotype , sd .sites_genotypes ):
284
+ vdata = tsinfer .VariantData (tmp_path , ds [ "variant_allele" ][:, 0 ]. values . astype ( str ) )
285
+ ts = tsinfer .infer (vdata )
286
+ for v , ds_v , vd_v in zip (ts .variants (), ds .call_genotype , vdata .sites_genotypes ):
288
287
assert np .all (v .genotypes == ds_v .values .flatten ())
289
- assert np .all (v .genotypes == sd_v )
288
+ assert np .all (v .genotypes == vd_v )
289
+
290
+
291
+ def test_simulate_genotype_call_dataset_length (tmp_path ):
292
+ # create_genotype_call_dataset does not save contig lengths
293
+ ts = msprime .sim_ancestry (4 , sequence_length = 1000 , random_seed = 123 )
294
+ ts = msprime .sim_mutations (ts , rate = 2e-3 , random_seed = 123 )
295
+ ds = ts_to_dataset (ts )
296
+ assert "contig_length" not in ds
297
+ ds .to_zarr (tmp_path , mode = "w" )
298
+ vdata = tsinfer .VariantData (tmp_path , ds ["variant_allele" ][:, 0 ].values .astype (str ))
299
+ assert vdata .sequence_length == ts .sites_position [- 1 ] + 1
300
+
301
+
302
+ class TestMultiContig :
303
+ def make_two_ts_dataset (self , path ):
304
+ # split ts into 2; put them as different contigs in the same dataset
305
+ ts = msprime .sim_ancestry (4 , sequence_length = 1000 , random_seed = 123 )
306
+ ts = msprime .sim_mutations (ts , rate = 2e-3 , random_seed = 123 )
307
+ split_at_site = 7
308
+ assert ts .num_sites > 10
309
+ site_break = ts .site (split_at_site ).position
310
+ ts1 = ts .keep_intervals ([(0 , site_break )]).rtrim ()
311
+ ts2 = ts .keep_intervals ([(site_break , ts .sequence_length )]).ltrim ()
312
+ ds = ts_to_dataset (ts , contigs = ["chr1" , "chr2" ])
313
+ ds .update ({"variant_ancestral_allele" : ds ["variant_allele" ][:, 0 ]})
314
+ variant_contig = ds ["variant_contig" ][:]
315
+ variant_contig [split_at_site :] = 1
316
+ ds .update ({"variant_contig" : variant_contig })
317
+ variant_position = ds ["variant_position" ].values
318
+ variant_position [split_at_site :] -= int (site_break )
319
+ ds .update ({"variant_position" : ds ["variant_position" ]})
320
+ ds .update (
321
+ {"contig_length" : np .array ([ts1 .sequence_length , ts2 .sequence_length ])}
322
+ )
323
+ ds .to_zarr (path , mode = "w" )
324
+ return ts1 , ts2
325
+
326
+ def test_unmasked (self , tmp_path ):
327
+ self .make_two_ts_dataset (tmp_path )
328
+ with pytest .raises (ValueError , match = r'multiple contigs \("chr1", "chr2"\)' ):
329
+ tsinfer .VariantData (tmp_path , "variant_ancestral_allele" )
330
+
331
+ def test_mask (self , tmp_path ):
332
+ ts1 , ts2 = self .make_two_ts_dataset (tmp_path )
333
+ vdata = tsinfer .VariantData (
334
+ tmp_path ,
335
+ "variant_ancestral_allele" ,
336
+ site_mask = np .array (ts1 .num_sites * [True ] + ts2 .num_sites * [False ]),
337
+ )
338
+ assert np .all (ts2 .sites_position == vdata .sites_position )
339
+ assert vdata .contig_id == "chr2"
340
+ assert vdata .sequence_length == ts2 .sequence_length
341
+
342
+ @pytest .mark .parametrize ("contig_id" , ["chr1" , "chr2" ])
343
+ def test_contig_id_param (self , contig_id , tmp_path ):
344
+ tree_seqs = {}
345
+ tree_seqs ["chr1" ], tree_seqs ["chr2" ] = self .make_two_ts_dataset (tmp_path )
346
+ vdata = tsinfer .VariantData (
347
+ tmp_path , "variant_ancestral_allele" , contig_id = contig_id
348
+ )
349
+ assert np .all (tree_seqs [contig_id ].sites_position == vdata .sites_position )
350
+ assert vdata .contig_id == contig_id
351
+ assert vdata .sequence_length == tree_seqs [contig_id ].sequence_length
352
+
353
+ def test_contig_id_param_and_mask (self , tmp_path ):
354
+ ts1 , ts2 = self .make_two_ts_dataset (tmp_path )
355
+ vdata = tsinfer .VariantData (
356
+ tmp_path ,
357
+ "variant_ancestral_allele" ,
358
+ site_mask = np .array (
359
+ (ts1 .num_sites + 1 ) * [True ] + (ts2 .num_sites - 1 ) * [False ]
360
+ ),
361
+ contig_id = "chr2" ,
362
+ )
363
+ assert np .all (ts2 .sites_position [1 :] == vdata .sites_position )
364
+ assert vdata .contig_id == "chr2"
365
+
366
+ @pytest .mark .parametrize ("contig_id" , ["chr1" , "chr2" ])
367
+ def test_contig_length (self , contig_id , tmp_path ):
368
+ tree_seqs = {}
369
+ tree_seqs ["chr1" ], tree_seqs ["chr2" ] = self .make_two_ts_dataset (tmp_path )
370
+ vdata = tsinfer .VariantData (
371
+ tmp_path , "variant_ancestral_allele" , contig_id = contig_id
372
+ )
373
+ assert vdata .sequence_length == tree_seqs [contig_id ].sequence_length
290
374
291
375
292
376
@pytest .mark .skipif (sys .platform == "win32" , reason = "File permission errors on Windows" )
293
377
class TestSgkitMask :
294
- @pytest .mark .parametrize ("sites" , [[1 , 2 , 3 , 5 , 9 , 27 ], [0 ], [] ])
378
+ @pytest .mark .parametrize ("sites" , [[1 , 2 , 3 , 5 , 9 , 27 ], [0 ]])
295
379
def test_sgkit_variant_mask (self , tmp_path , sites ):
296
380
ts , zarr_path = tsutil .make_ts_and_zarr (tmp_path )
297
381
ds = sgkit .load_dataset (zarr_path )
@@ -800,6 +884,20 @@ def test_bad_ancestral_state(self, tmp_path):
800
884
with pytest .raises (ValueError , match = "cannot contain empty strings" ):
801
885
tsinfer .VariantData (path , ancestral_state )
802
886
887
+ def test_ancestral_state_len_not_same_as_mask (self , tmp_path ):
888
+ path = tmp_path / "data.zarr"
889
+ ds = self .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
890
+ sgkit .save_dataset (ds , path )
891
+ ancestral_state = ds ["variant_allele" ][:, 0 ].values .astype (str )
892
+ site_mask = np .zeros (ds .sizes ["variants" ], dtype = bool )
893
+ site_mask [0 ] = True
894
+ with pytest .raises (
895
+ ValueError ,
896
+ match = "Ancestral state array must be the same length as the number of"
897
+ " selected sites" ,
898
+ ):
899
+ tsinfer .VariantData (path , ancestral_state , site_mask = site_mask )
900
+
803
901
def test_empty_alleles_not_at_end (self , tmp_path ):
804
902
path = tmp_path / "data.zarr"
805
903
ds = self .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , n_ploidy = 1 )
@@ -831,3 +929,62 @@ def test_unimplemented_from_tree_sequence(self):
831
929
# Requires e.g. https://github.com/tskit-dev/tsinfer/issues/924
832
930
with pytest .raises (NotImplementedError ):
833
931
tsinfer .VariantData .from_tree_sequence (None )
932
+
933
+ def test_multiple_contigs (self , tmp_path ):
934
+ path = tmp_path / "data.zarr"
935
+ ds = sgkit .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
936
+ ds ["contig_id" ] = (
937
+ ds ["contig_id" ].dims ,
938
+ np .array (["c10" , "c11" ], dtype = "<U3" ),
939
+ )
940
+ ds ["variant_contig" ] = (
941
+ ds ["variant_contig" ].dims ,
942
+ np .array ([0 , 0 , 1 ], dtype = ds ["variant_contig" ].dtype ),
943
+ )
944
+ sgkit .save_dataset (ds , path )
945
+ with pytest .raises (
946
+ ValueError , match = r'Sites belong to multiple contigs \("c10", "c11"\)'
947
+ ):
948
+ tsinfer .VariantData (path , ds ["variant_allele" ][:, 0 ].astype (str ))
949
+
950
+ def test_all_masked (self , tmp_path ):
951
+ path = tmp_path / "data.zarr"
952
+ ds = sgkit .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
953
+ sgkit .save_dataset (ds , path )
954
+ with pytest .raises (ValueError , match = "All sites have been masked out" ):
955
+ tsinfer .VariantData (
956
+ path , ds ["variant_allele" ][:, 0 ].astype (str ), site_mask = np .ones (3 , bool )
957
+ )
958
+
959
+ def test_bad_contig_param (self , tmp_path ):
960
+ path = tmp_path / "data.zarr"
961
+ ds = sgkit .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
962
+ sgkit .save_dataset (ds , path )
963
+ with pytest .raises (ValueError , match = '"XX" not found' ):
964
+ tsinfer .VariantData (
965
+ path , ds ["variant_allele" ][:, 0 ].astype (str ), contig_id = "XX"
966
+ )
967
+
968
+ def test_multiple_contig_param (self , tmp_path ):
969
+ path = tmp_path / "data.zarr"
970
+ ds = sgkit .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
971
+ ds ["contig_id" ] = (
972
+ ds ["contig_id" ].dims ,
973
+ np .array (["chr1" , "chr1" ], dtype = "<U4" ),
974
+ )
975
+ sgkit .save_dataset (ds , path )
976
+ with pytest .raises (ValueError , match = 'Multiple contigs named "chr1"' ):
977
+ tsinfer .VariantData (
978
+ path , ds ["variant_allele" ][:, 0 ].astype (str ), contig_id = "chr1"
979
+ )
980
+
981
+ def test_missing_sites_time (self , tmp_path ):
982
+ path = tmp_path / "data.zarr"
983
+ ds = sgkit .simulate_genotype_call_dataset (n_variant = 3 , n_sample = 3 , phased = True )
984
+ sgkit .save_dataset (ds , path )
985
+ with pytest .raises (
986
+ ValueError , match = "The sites time array XX was not found in the dataset"
987
+ ):
988
+ tsinfer .VariantData (
989
+ path , ds ["variant_allele" ][:, 0 ].astype (str ), sites_time = "XX"
990
+ )
0 commit comments