pav3 3.0.0.dev19__tar.gz → 3.0.0.dev20__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 (98) hide show
  1. {pav3-3.0.0.dev19/src/pav3.egg-info → pav3-3.0.0.dev20}/PKG-INFO +1 -1
  2. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/__init__.py +1 -1
  3. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/rules/align.smk +1 -2
  4. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/rules/call.smk +13 -10
  5. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/vcf.py +150 -77
  6. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20/src/pav3.egg-info}/PKG-INFO +1 -1
  7. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/LICENSE +0 -0
  8. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/README.md +0 -0
  9. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/pyproject.toml +0 -0
  10. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/setup.cfg +0 -0
  11. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/__main__.py +0 -0
  12. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/__init__.py +0 -0
  13. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/features.py +0 -0
  14. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lcmodel/__init__.py +0 -0
  15. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lcmodel/_lcmodel.py +0 -0
  16. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lcmodel/_lcmodel_logistic.py +0 -0
  17. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lcmodel/_lcmodel_null.py +0 -0
  18. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lcmodel/_util.py +0 -0
  19. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/lift.py +0 -0
  20. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/op.py +0 -0
  21. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/records.py +0 -0
  22. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/score.py +0 -0
  23. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/tables.py +0 -0
  24. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/align/trim.py +0 -0
  25. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/anno.py +0 -0
  26. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/call/__init__.py +0 -0
  27. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/call/expr.py +0 -0
  28. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/call/integrate.py +0 -0
  29. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/call/intra.py +0 -0
  30. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/call/util.py +0 -0
  31. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/__init__.py +0 -0
  32. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/_cli.py +0 -0
  33. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/_common_opt.py +0 -0
  34. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/_subcommand_batch.py +0 -0
  35. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/_subcommand_call.py +0 -0
  36. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/cli/_subcommand_license.py +0 -0
  37. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/const.py +0 -0
  38. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/default/model.json +0 -0
  39. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_95_80/model.json +0 -0
  40. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_95_80/model_info.tar.gz +0 -0
  41. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_95_80/weights.npz +0 -0
  42. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_98_80/model.json +0 -0
  43. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_98_80/model_info.tar.gz +0 -0
  44. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/human_98_80/weights.npz +0 -0
  45. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/prim_90_50/model.json +0 -0
  46. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/prim_90_50/model_info.tar.gz +0 -0
  47. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/prim_90_50/weights.npz +0 -0
  48. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/primdiv_85_50/model.json +0 -0
  49. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/primdiv_85_50/model_info.tar.gz +0 -0
  50. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/lcmodel/primdiv_85_50/weights.npz +0 -0
  51. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/tracks/alignment_track_fields.tsv +0 -0
  52. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/tracks/variant_track_fields.tsv +0 -0
  53. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/Snakefile +0 -0
  54. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/profiles/pav_default/config.yaml +0 -0
  55. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/rules/data.smk +0 -0
  56. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/rules/pipeline.smk +0 -0
  57. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/data/workflow/rules/tracks.smk +0 -0
  58. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/__init__.py +0 -0
  59. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/imports.py +0 -0
  60. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/inter.py +0 -0
  61. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/path.py +0 -0
  62. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/sm.py +0 -0
  63. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/dev/tables.py +0 -0
  64. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/fig/__init__.py +0 -0
  65. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/fig/const.py +0 -0
  66. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/fig/density.py +0 -0
  67. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/fig/tracks.py +0 -0
  68. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/fig/util.py +0 -0
  69. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/inv.py +0 -0
  70. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/io.py +0 -0
  71. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/kde.py +0 -0
  72. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/__init__.py +0 -0
  73. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/call.py +0 -0
  74. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/chain.py +0 -0
  75. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/interval.py +0 -0
  76. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/io.py +0 -0
  77. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/region_kde.py +0 -0
  78. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/resources.py +0 -0
  79. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/struct.py +0 -0
  80. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/lgsv/variant.py +0 -0
  81. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/params.py +0 -0
  82. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/pipeline.py +0 -0
  83. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/region.py +0 -0
  84. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/schema.py +0 -0
  85. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/seq.py +0 -0
  86. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/util.py +0 -0
  87. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/__init__.py +0 -0
  88. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/__pycache__/__init__.cpython-312.pyc +0 -0
  89. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/__pycache__/call.cpython-312.pyc +0 -0
  90. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/__pycache__/resource.cpython-312.pyc +0 -0
  91. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/call.py +0 -0
  92. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3/workflow/resource.py +0 -0
  93. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3.egg-info/SOURCES.txt +0 -0
  94. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3.egg-info/dependency_links.txt +0 -0
  95. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3.egg-info/entry_points.txt +0 -0
  96. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3.egg-info/requires.txt +0 -0
  97. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/src/pav3.egg-info/top_level.txt +0 -0
  98. {pav3-3.0.0.dev19 → pav3-3.0.0.dev20}/tests/test_score_dtype.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pav3
3
- Version: 3.0.0.dev19
3
+ Version: 3.0.0.dev20
4
4
  Summary: Assembly-based variant discovery
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  """PAV Python library routines."""
2
2
 
3
- __version__ = '3.0.0.dev19'
3
+ __version__ = '3.0.0.dev20'
4
4
 
5
5
  __all__ = [
6
6
  'align',
@@ -14,7 +14,6 @@ import os
14
14
 
15
15
  import agglovar
16
16
  import pav3
17
- import snakemake.io
18
17
  import polars as pl
19
18
 
20
19
  global PAV_CONFIG
@@ -35,7 +34,7 @@ global REF_FAI
35
34
  #
36
35
 
37
36
  def align_index_files(
38
- wildcards: snakemake.io.Namedlist | dict[str, str]
37
+ wildcards: dict[str, str]
39
38
  ):
40
39
  """
41
40
  Get a list of reference index files needed by an aligner.
@@ -51,7 +51,6 @@ rule call_vcf:
51
51
  benchmark: 'log/benchmark/{asm_name}/call_vcf.tsv'
52
52
  threads: POLARS_MAX_THREADS
53
53
  run:
54
-
55
54
  pattern_var = 'results/{asm_name}/call/call_{vartype}.parquet'
56
55
  pattern_callable = 'results/{asm_name}/call_hap/callable_ref_{hap}.parquet'
57
56
 
@@ -62,22 +61,26 @@ rule call_vcf:
62
61
  df_ref_info = pl.read_parquet(input.ref_info)
63
62
 
64
63
  # Get haplotypes
65
- hap_source = pav3.vcf.get_hap_source(
64
+ hap_list = pav3.vcf.resolve_hap_list(
66
65
  pav3.pipeline.get_hap_list(wildcards.asm_name, ASM_TABLE),
67
66
  pav_params.vcf_haplotypes,
68
67
  )
69
68
 
70
- if not hap_source:
69
+ if not hap_list:
71
70
  raise ValueError(
72
71
  f'No haplotypes defined for assembly "{wildcards.asm_name}"'
73
72
  )
74
73
 
74
+ hap_order = [
75
+ (wildcards.asm_name, hap) for hap in hap_list
76
+ ]
77
+
75
78
  # Add callable table for each genotype
76
- hap_callable = {
77
- hap: pl.scan_parquet(
79
+ callable_dict = {
80
+ (wildcards.asm_name, hap): pl.scan_parquet(
78
81
  pattern_callable.format(asm_name=wildcards.asm_name, hap=hap)
79
82
  )
80
- for hap in hap_source.keys()
83
+ for hap in hap_list
81
84
  }
82
85
 
83
86
  # Get genotype tables
@@ -97,7 +100,7 @@ rule call_vcf:
97
100
  .with_row_index('_index')
98
101
  )
99
102
 
100
- if vartype == 'snv':
103
+ if vartype == 'snv' and df.collect_schema().get('vartype') is None:
101
104
  df = df.with_columns(pl.lit('SNV').alias('vartype'))
102
105
 
103
106
  # Initialize VCF fields (all but FORMAT and sample columns)
@@ -122,10 +125,10 @@ rule call_vcf:
122
125
  try:
123
126
  df = (
124
127
  df.join(
125
- pav3.vcf.gt_column(
128
+ pav3.vcf.gt_column_asm(
126
129
  df,
127
- hap_source,
128
- hap_callable,
130
+ wildcards.asm_name,
131
+ callable_dict,
129
132
  col_name='_vcf_field_gt',
130
133
  separator='|',
131
134
  ),
@@ -17,13 +17,14 @@ __all__ = [
17
17
  'vcf_fields_seq_insdel',
18
18
  'vcf_fields_sym',
19
19
  'vcf_fields_snv',
20
- 'gt_column',
20
+ 'gt_column_iterator',
21
+ 'gt_column_hap',
21
22
  'standard_info_fields',
22
23
  'reformat_vcf_table',
23
- 'get_hap_source',
24
24
  ]
25
25
 
26
26
 
27
+ import collections
27
28
  from collections.abc import Iterable, Mapping
28
29
  import datetime
29
30
  from pathlib import Path
@@ -116,10 +117,10 @@ class AltField:
116
117
  VCF_VERSION = '4.5'
117
118
 
118
119
  INFO_FIELDS = [
119
- InfoField(
120
- 'ID', '1', 'String',
121
- 'Unique variant ID',
122
- ),
120
+ # InfoField(
121
+ # 'ID', '1', 'String',
122
+ # 'Unique variant ID',
123
+ # ),
123
124
  InfoField(
124
125
  'SVLEN', '.', 'Integer',
125
126
  'Variant length',
@@ -207,8 +208,8 @@ VARTYPE_COMPAT = {
207
208
 
208
209
 
209
210
  def get_headers(
210
- ref_filename: Optional[str | Path] = None,
211
- df_ref_info: Optional[pl.DataFrame] = None,
211
+ ref_filename: Optional[str | Path] = None,
212
+ df_ref_info: Optional[pl.DataFrame] = None,
212
213
  ) -> list[str]:
213
214
 
214
215
  # Lead headers
@@ -255,16 +256,13 @@ def get_headers(
255
256
  return headers
256
257
 
257
258
  def init_vcf_fields(
258
- df: pl.LazyFrame,
259
- ref_fa: Optional[str | Path] = None,
260
- use_sym: Optional[bool] = None,
261
- vartype: Optional[str] = None,
259
+ df: pl.LazyFrame,
260
+ ref_fa: Optional[str | Path] = None,
261
+ use_sym: Optional[bool] = None,
262
+ vartype: Optional[str] = None,
262
263
  ) -> pl.LazyFrame:
263
264
  """Initialize VCF fields.
264
265
 
265
- If `hap_source` and `hap_callable` are provided, genotypes will be added to the table. If one is provided and
266
- the other is None, an error is raised.
267
-
268
266
  :param df: Variant table with an "_index" column.
269
267
  :param ref_fa: Reference FASTA filename. Required for non-SNV variants.
270
268
  :param use_sym: Whether to use symbolic alternate records. If None, INS, DEL, and SNV ore not
@@ -273,7 +271,8 @@ def init_vcf_fields(
273
271
  this field ensures the variant types is handled consisntently if all variant types
274
272
  are not in table `df` or `df` is empty.
275
273
  """
276
- df_schema = df.collect_schema()
274
+ if df.collect_schema().get('_index') is None:
275
+ df = df.with_row_index('_index')
277
276
 
278
277
  vartype_set = set(
279
278
  df.select(pl.col('vartype').unique()).collect().to_series()
@@ -388,8 +387,8 @@ def init_vcf_fields(
388
387
 
389
388
 
390
389
  def vcf_fields_seq_insdel(
391
- df: pl.LazyFrame,
392
- ref_fa: Path | str,
390
+ df: pl.LazyFrame,
391
+ ref_fa: Path | str,
393
392
  ) -> pl.LazyFrame:
394
393
  """Set POS, REF, and ALT VCF fields for insertions and deletions (non-symbolic).
395
394
 
@@ -606,43 +605,122 @@ def vcf_fields_snv(
606
605
  )
607
606
 
608
607
 
609
- def gt_column(
610
- df: pl.LazyFrame,
611
- hap_source: dict[str, int],
612
- hap_callable: dict[str, pl.LazyFrame],
613
- col_name: str = 'gt',
614
- separator: str = '|',
615
- ):
616
- """Get a table with formatted genotypes.
608
+ def gt_column_iterator(
609
+ df: pl.LazyFrame | pl.DataFrame,
610
+ hap_order: Iterable[tuple[str, str]],
611
+ callable_dict: dict[tuple[str, str], pl.LazyFrame | pl.DataFrame],
612
+ gt_col_name: str = 'gt',
613
+ ) -> Iterable[tuple[str, pl.LazyFrame]]:
614
+ """Get genotype columns for all assemblies/samples.
615
+
616
+ Argument `hap_order` is a list of assembly/haplotype pairs, and it determines the order of
617
+ assemblies (or "samples") and the order of haplotypes for each sample. Generally, it should
618
+ list all the haplotypes for a sample sequentially (e.g.
619
+ `[("a", "h1"), ("a", "h2"), ("b", "h1"), ("b", "h2")]`). If assemblies are not listed
620
+ sequentially, then the first accurance of each assembly name determines the order, but
621
+ haplotype order for each assembly is still preserved.
622
+
623
+ Returns an iterator of assembly name and genotype table. The genotype table contains two
624
+ columns: Column "_index" is the index of `df`, and the second column is string genotypes for
625
+ each record with a column name defined by argument `gt_col_name`.
626
+
627
+ :param df: Table of merged variants.
628
+ :param hap_order: An iterable of assembly/haplotype tuples in the order they will appear.
629
+ :param callable_dict: A dict keyed by assembly name and haplotype (tuple) pointing to tables
630
+ of callable reference regions.
631
+ :param gt_col_name: Name of the genotype column in the returned genotype tables. May not be
632
+ "_index".
633
+
634
+ :returns: An iterator of tuples (assembly name, and genotype table).
635
+ """
636
+ if gt_col_name is None or not (gt_col_name := gt_col_name.strip()):
637
+ gt_col_name = 'gt'
638
+
639
+ if gt_col_name == '_index':
640
+ raise ValueError(
641
+ 'Genotype column argument "gt_col_name" cannot use reserved '
642
+ 'column name for row indexes, "_index"'
643
+ )
644
+
645
+ # Set and check assembly and haplotype orders
646
+ hap_order = list(hap_order)
647
+
648
+ if dup_asm_hap := [
649
+ asm_hap for asm_hap, count in collections.Counter(f'{asm_name}-{hap}' for asm_name, hap in hap_order).items()
650
+ if count > 1
651
+ ]:
652
+ n = len(dup_asm_hap)
653
+ dup_asm_hap = ', '.join(dup_asm_hap[:3]) + ('...' if n > 3 else '')
654
+ raise ValueError(
655
+ f'Assembly/haplotype pairs must be unique when separated by a dash: '
656
+ f'Found {n} duplicate pairs: {dup_asm_hap}'
657
+ )
658
+
659
+ asm_order = list(dict.fromkeys((asm_name for asm_name, hap in hap_order)).keys())
660
+
661
+ # List of ordered haplotypes per assembly
662
+ hap_dict = {
663
+ asm_name_ordered: list(dict.fromkeys((
664
+ hap for asm_name, hap in hap_order if asm_name == asm_name_ordered
665
+ )).keys())
666
+ for asm_name_ordered in asm_order
667
+ }
668
+
669
+ # Callable dict should have LazyFrame
670
+ callable_dict = dict(callable_dict)
671
+
672
+ for (asm_name, hap), df_callable in callable_dict.values():
673
+ if isinstance(df_callable, pl.DataFrame):
674
+ df_callable[(asm_name, hap)] = df_callable.lazy()
675
+
676
+ for asm_name, hap_list in hap_dict.items():
677
+ yield asm_name, gt_column_asm(
678
+ df,
679
+ asm_name=asm_name,
680
+ callable_dict=callable_dict,
681
+ col_name='gt',
682
+ separator='|',
683
+ )
684
+
685
+
686
+ def gt_column_asm(
687
+ df: pl.LazyFrame,
688
+ asm_name: str,
689
+ callable_dict: dict[tuple[str, str], pl.LazyFrame],
690
+ col_name: str = 'gt',
691
+ separator: str = '|',
692
+ ) -> pl.LazyFrame:
693
+ """Get a table of genotypes for a single assembly.
694
+
695
+ The genotype string contains "1" (alternate allele), "0" (reference allele), and "." (no call).
696
+ The order of alleles is defined by keys in `callable_dict` for all records matching the
697
+ assembly name.
617
698
 
618
699
  :param df: Variant table with an "_index" column.
619
- :param hap_source: A dictionary mapping haplotype names (keuys) to the index of the haplotype in merged order
620
- (0 is the first merged haplotype, 1 is the second, etc).
621
- :param hap_callable: A dictionary mapping haplotype names to a polars LazyFrame of callable regions for that
622
- haplotype.
700
+ :param asm_name: Assembly name.
701
+ :param callable_dict: A dictionary keys tuple(assembly name, haplotype name) and values
702
+ containing a polars LazyFrame of callable regions.
623
703
  :param col_name: Name of the genotype column in the output table.
624
704
  :param separator: Genotype separator character ("|" for phased, "/" for unphased).
625
705
 
626
- :return: A polars LazyFrame with two columns, "_index" and the genoytpe string (column name is set by `col_name`).
706
+ :return: A polars LazyFrame with two columns, "_index" and the genotype string (column name is
707
+ set by `col_name`).
627
708
  """
628
-
629
- if len(hap_source) == 0:
630
- raise ValueError(f'No haplotypes to get genotypes for')
631
-
632
- if '_index' not in df.collect_schema():
709
+ if df.collect_schema().get('_index') is None:
633
710
  df = df.with_row_index('_index')
634
711
 
635
712
  # Start with an index, add genotype columns
636
713
  df_gt = df.select('_index')
637
714
 
638
- for hap in hap_source.keys():
639
- hap_index = hap_source[hap]
715
+ for (callable_asm_name, hap), df_callable in callable_dict.items():
716
+ if callable_asm_name != asm_name:
717
+ continue
640
718
 
641
719
  # Determine if each value matches this haplotype.
642
720
  callable_hap = (
643
721
  agglovar.bed.intersect.as_proportion(
644
722
  df,
645
- hap_callable[hap],
723
+ df_callable,
646
724
  'callable_hap'
647
725
  )
648
726
  .with_columns(
@@ -655,12 +733,10 @@ def gt_column(
655
733
  '_index', 'chrom', 'pos', 'end',
656
734
  (
657
735
  pl.col('mg_src')
658
- .list.eval(
659
- pl.element().struct.field('index')
660
- )
661
- .list.contains(hap_index)
662
- .alias('in_hap')
663
- ),
736
+ .list.filter(
737
+ pl.element().struct.field('id') == f'{asm_name}-{hap}'
738
+ ).list.len() > 0
739
+ ).alias('in_hap'),
664
740
  )
665
741
  .join(
666
742
  callable_hap, on='_index', how='left'
@@ -693,7 +769,7 @@ def gt_column(
693
769
 
694
770
 
695
771
  def standard_info_fields(
696
- df: pl.LazyFrame,
772
+ df: pl.LazyFrame,
697
773
  ) -> pl.LazyFrame:
698
774
  """Set standard INFO columns.
699
775
 
@@ -713,12 +789,12 @@ def standard_info_fields(
713
789
  .alias('_vcf_info')
714
790
  )
715
791
 
716
- if 'id' in cols:
717
- df = df.with_columns(
718
- pl.col('_vcf_info').list.concat(
719
- pl.concat_str(pl.lit('ID='), 'id')
720
- )
721
- )
792
+ # if 'id' in cols:
793
+ # df = df.with_columns(
794
+ # pl.col('_vcf_info').list.concat(
795
+ # pl.concat_str(pl.lit('ID='), 'id')
796
+ # )
797
+ # )
722
798
 
723
799
  if 'varlen' in cols:
724
800
  df = df.with_columns(
@@ -865,7 +941,7 @@ def standard_info_fields(
865
941
  .then(
866
942
  pl.col('_vcf_info').list.concat(
867
943
  pl.concat_str(
868
- pl.lit('HOMREF='),
944
+ pl.lit('HOMQRY='),
869
945
  pl.col('hom_ref').struct.field('up'),
870
946
  pl.lit(','),
871
947
  pl.col('hom_ref').struct.field('dn'),
@@ -930,8 +1006,8 @@ def standard_info_fields(
930
1006
 
931
1007
 
932
1008
  def reformat_vcf_table(
933
- df: pl.LazyFrame,
934
- sample_columns: Optional[Mapping[int, str]] = None,
1009
+ df: pl.LazyFrame,
1010
+ sample_columns: Optional[Mapping[int, str]] = None,
935
1011
  ) -> pl.LazyFrame:
936
1012
  """Reformat VCF table to VCF format."""
937
1013
 
@@ -967,41 +1043,38 @@ def reformat_vcf_table(
967
1043
  )
968
1044
 
969
1045
 
970
- def get_hap_source(
971
- hap_list: Iterable[str],
972
- vcf_haplotypes: Optional[str],
973
- ) -> dict[str, int]:
974
- """Get a dict of haplotypes with keys in the order they should appear in the VCF genotype field.
1046
+ def resolve_hap_list(
1047
+ hap_list: Iterable[str],
1048
+ vcf_haplotypes: Optional[str],
1049
+ ) -> list[str]:
1050
+ """Get a list of haplotypes in the order they should appear for a sample.
975
1051
 
976
1052
  :param hap_list: List of haplotypes defined for this assembly.
977
- :param vcf_haplotypes: Optional, a string of haplotypes separated by commas to include in the VCF haplotypes. If
978
- defined, all haplotypes must appear in `hap_list` and the retured dictionary is subset and ordered by this list.
1053
+ :param vcf_haplotypes: Optional, a string of haplotypes separated by commas to include in the
1054
+ VCF haplotypes. If defined, all haplotypes must appear in `hap_list` and the returned list
1055
+ is subset and ordered by this list.
979
1056
 
980
- :param: A dictionary of haplotype name (keys) and haplotype indices (values) that will match
1057
+ :returns: List of haplotypes.
981
1058
  """
982
- hap_source = {hap: i for i, hap in enumerate(hap_list)}
1059
+ hap_list = list(hap_list)
983
1060
 
984
1061
  if vcf_haplotypes is not None:
985
- vcf_haps = [hap.strip() for hap in vcf_haplotypes.split(',') if hap.strip()]
986
-
987
- if (missing_hap := set(vcf_haps) - set(hap_list)):
988
- missing_hap = [
989
- f'"{hap}"' for hap in sorted(missing_hap)
990
- ]
1062
+ vcf_hap_list = [hap.strip() for hap in vcf_haplotypes.split(',') if hap.strip()]
991
1063
 
1064
+ if missing_hap := [hap for hap in vcf_hap_list if hap not in set(hap_list)]:
992
1065
  missing_n = len(missing_hap)
993
1066
 
994
- missing_hap = ', '.join(missing_hap[:3]) + ('...' if missing_n > 3 else '')
1067
+ missing_hap = ', '.join([
1068
+ f'"{hap}"' for hap in missing_hap
1069
+ ]) + ('...' if missing_n > 3 else '')
995
1070
 
996
1071
  raise ValueError(
997
- f'Configuration item "vcf_haplotypes" defines {missing_n} haplotypes that are not in the assembly '
998
- f'table for "{wildcards.asm_name}": {missing_hap}'
1072
+ f'Configuration item "vcf_haplotypes" defines {missing_n} haplotypes: '
1073
+ f'{missing_hap}'
999
1074
  )
1000
1075
 
1001
- # Filter and order hap_source to VCF order
1002
- hap_source = {hap: hap_source[hap] for hap in vcf_haps}
1003
-
1004
- return hap_source
1076
+ return vcf_hap_list
1077
+ return hap_list
1005
1078
 
1006
1079
 
1007
1080
  class _LockingPysam():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pav3
3
- Version: 3.0.0.dev19
3
+ Version: 3.0.0.dev20
4
4
  Summary: Assembly-based variant discovery
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.12
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