dnakit 0.1.0.dev0__py3-none-any.whl

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 (210) hide show
  1. dnakit/__init__.py +108 -0
  2. dnakit/_version.py +5 -0
  3. dnakit/alignment/__init__.py +6 -0
  4. dnakit/alignment/pairwise.py +512 -0
  5. dnakit/alignment/results.py +188 -0
  6. dnakit/backends/__init__.py +80 -0
  7. dnakit/backends/external.py +517 -0
  8. dnakit/backends/registry.py +207 -0
  9. dnakit/batch.py +452 -0
  10. dnakit/cache/__init__.py +5 -0
  11. dnakit/cache/store.py +424 -0
  12. dnakit/chunking.py +920 -0
  13. dnakit/cli/__init__.py +5 -0
  14. dnakit/cli/__main__.py +6 -0
  15. dnakit/cli/app.py +814 -0
  16. dnakit/cli/workflow.py +94 -0
  17. dnakit/config/__init__.py +5 -0
  18. dnakit/config/loader.py +381 -0
  19. dnakit/core/__init__.py +80 -0
  20. dnakit/core/_json.py +206 -0
  21. dnakit/core/backend_info.py +70 -0
  22. dnakit/core/collection.py +163 -0
  23. dnakit/core/coordinates.py +295 -0
  24. dnakit/core/enums.py +95 -0
  25. dnakit/core/facade.py +719 -0
  26. dnakit/core/feature.py +90 -0
  27. dnakit/core/gap.py +82 -0
  28. dnakit/core/issues.py +62 -0
  29. dnakit/core/provenance.py +305 -0
  30. dnakit/core/record.py +161 -0
  31. dnakit/core/results.py +134 -0
  32. dnakit/core/sequence.py +327 -0
  33. dnakit/datasets/__init__.py +100 -0
  34. dnakit/datasets/_advanced_shared.py +198 -0
  35. dnakit/datasets/_metadata.py +33 -0
  36. dnakit/datasets/advanced_deduplicate.py +183 -0
  37. dnakit/datasets/advanced_split.py +302 -0
  38. dnakit/datasets/clustering.py +286 -0
  39. dnakit/datasets/config.py +531 -0
  40. dnakit/datasets/deduplicate.py +303 -0
  41. dnakit/datasets/evaluation.py +244 -0
  42. dnakit/datasets/filter.py +135 -0
  43. dnakit/datasets/neural_clustering.py +221 -0
  44. dnakit/datasets/results.py +520 -0
  45. dnakit/datasets/split.py +450 -0
  46. dnakit/descriptors/__init__.py +102 -0
  47. dnakit/descriptors/_dinucleotide.py +345 -0
  48. dnakit/descriptors/_shared.py +180 -0
  49. dnakit/descriptors/all.py +917 -0
  50. dnakit/descriptors/basic.py +193 -0
  51. dnakit/descriptors/codon.py +195 -0
  52. dnakit/descriptors/complexity.py +201 -0
  53. dnakit/descriptors/entropy.py +101 -0
  54. dnakit/descriptors/homopolymer.py +105 -0
  55. dnakit/descriptors/kmer.py +88 -0
  56. dnakit/descriptors/results.py +286 -0
  57. dnakit/descriptors/schema.py +414 -0
  58. dnakit/descriptors/window.py +203 -0
  59. dnakit/download/__init__.py +54 -0
  60. dnakit/download/catalogs.py +166 -0
  61. dnakit/download/ena.py +131 -0
  62. dnakit/download/encode.py +103 -0
  63. dnakit/download/files.py +469 -0
  64. dnakit/download/indexes.py +233 -0
  65. dnakit/download/metadata.py +164 -0
  66. dnakit/download/models.py +227 -0
  67. dnakit/download/ncbi.py +333 -0
  68. dnakit/download/sequence.py +133 -0
  69. dnakit/download/ucsc.py +99 -0
  70. dnakit/evaluation/__init__.py +81 -0
  71. dnakit/evaluation/_shared.py +288 -0
  72. dnakit/evaluation/collection.py +270 -0
  73. dnakit/evaluation/config.py +591 -0
  74. dnakit/evaluation/distribution.py +287 -0
  75. dnakit/evaluation/frechet.py +223 -0
  76. dnakit/evaluation/generative.py +381 -0
  77. dnakit/evaluation/reference.py +409 -0
  78. dnakit/evaluation/results.py +137 -0
  79. dnakit/evaluation/scorecard.py +211 -0
  80. dnakit/evaluation/sequence.py +403 -0
  81. dnakit/evaluation/synthesis.py +249 -0
  82. dnakit/exceptions.py +189 -0
  83. dnakit/fingerprints/__init__.py +109 -0
  84. dnakit/fingerprints/_shared.py +116 -0
  85. dnakit/fingerprints/advanced.py +829 -0
  86. dnakit/fingerprints/bit.py +229 -0
  87. dnakit/fingerprints/encoding.py +351 -0
  88. dnakit/fingerprints/kmer.py +170 -0
  89. dnakit/fingerprints/results.py +531 -0
  90. dnakit/fingerprints/sketch.py +246 -0
  91. dnakit/io/__init__.py +112 -0
  92. dnakit/io/_advanced_common.py +105 -0
  93. dnakit/io/_formats.py +994 -0
  94. dnakit/io/annotations.py +1179 -0
  95. dnakit/io/api.py +759 -0
  96. dnakit/io/config.py +315 -0
  97. dnakit/io/genbank.py +523 -0
  98. dnakit/io/indexed.py +1146 -0
  99. dnakit/io/metadata.py +452 -0
  100. dnakit/io/parquet.py +56 -0
  101. dnakit/io/results.py +131 -0
  102. dnakit/io/source.py +138 -0
  103. dnakit/io/tables.py +1237 -0
  104. dnakit/molbio/__init__.py +101 -0
  105. dnakit/molbio/_shared.py +266 -0
  106. dnakit/molbio/assembly.py +279 -0
  107. dnakit/molbio/crispr.py +181 -0
  108. dnakit/molbio/optimization.py +897 -0
  109. dnakit/molbio/primer3_design.py +667 -0
  110. dnakit/molbio/primers.py +899 -0
  111. dnakit/molbio/restriction.py +573 -0
  112. dnakit/molbio/results.py +583 -0
  113. dnakit/ops/__init__.py +588 -0
  114. dnakit/ops/_common.py +201 -0
  115. dnakit/ops/circular.py +193 -0
  116. dnakit/ops/concat.py +204 -0
  117. dnakit/ops/direction.py +27 -0
  118. dnakit/ops/edit.py +443 -0
  119. dnakit/ops/evolution.py +991 -0
  120. dnakit/ops/mutation.py +151 -0
  121. dnakit/ops/records.py +1012 -0
  122. dnakit/ops/translation.py +257 -0
  123. dnakit/patterns/__init__.py +81 -0
  124. dnakit/patterns/_shared.py +282 -0
  125. dnakit/patterns/coding.py +360 -0
  126. dnakit/patterns/crispr.py +337 -0
  127. dnakit/patterns/motif.py +721 -0
  128. dnakit/patterns/regions.py +341 -0
  129. dnakit/patterns/repeats.py +480 -0
  130. dnakit/patterns/restriction.py +281 -0
  131. dnakit/patterns/results.py +411 -0
  132. dnakit/py.typed +1 -0
  133. dnakit/references/__init__.py +27 -0
  134. dnakit/references/models.py +155 -0
  135. dnakit/references/ncbi.py +785 -0
  136. dnakit/representations/__init__.py +29 -0
  137. dnakit/representations/backends.py +928 -0
  138. dnakit/representations/checkpoints.py +356 -0
  139. dnakit/representations/extraction.py +220 -0
  140. dnakit/representations/models.py +395 -0
  141. dnakit/representations/results.py +79 -0
  142. dnakit/search/__init__.py +132 -0
  143. dnakit/search/_http.py +271 -0
  144. dnakit/search/_shared.py +108 -0
  145. dnakit/search/blast.py +516 -0
  146. dnakit/search/ena.py +245 -0
  147. dnakit/search/encode.py +102 -0
  148. dnakit/search/ensembl.py +874 -0
  149. dnakit/search/models.py +159 -0
  150. dnakit/search/ncbi.py +756 -0
  151. dnakit/search/ucsc.py +388 -0
  152. dnakit/secondary_structure/__init__.py +38 -0
  153. dnakit/secondary_structure/dotbracket.py +410 -0
  154. dnakit/secondary_structure/nupack.py +589 -0
  155. dnakit/secondary_structure/results.py +142 -0
  156. dnakit/similarity/__init__.py +125 -0
  157. dnakit/similarity/_shared.py +152 -0
  158. dnakit/similarity/approximate.py +258 -0
  159. dnakit/similarity/compare.py +162 -0
  160. dnakit/similarity/dashing.py +936 -0
  161. dnakit/similarity/distance.py +279 -0
  162. dnakit/similarity/index.py +365 -0
  163. dnakit/similarity/matrix.py +254 -0
  164. dnakit/similarity/results.py +676 -0
  165. dnakit/similarity/search.py +286 -0
  166. dnakit/similarity/sketch.py +96 -0
  167. dnakit/similarity/vector.py +662 -0
  168. dnakit/standardize/__init__.py +77 -0
  169. dnakit/standardize/_shared.py +100 -0
  170. dnakit/standardize/config.py +411 -0
  171. dnakit/standardize/gaps.py +1060 -0
  172. dnakit/standardize/normalize.py +565 -0
  173. dnakit/standardize/results.py +247 -0
  174. dnakit/standardize/validate.py +487 -0
  175. dnakit/structure3d/__init__.py +47 -0
  176. dnakit/structure3d/_shared.py +47 -0
  177. dnakit/structure3d/geometry.py +773 -0
  178. dnakit/structure3d/pdb.py +253 -0
  179. dnakit/structure3d/results.py +233 -0
  180. dnakit/structure3d/threedna.py +174 -0
  181. dnakit/thermodynamics/__init__.py +112 -0
  182. dnakit/thermodynamics/_shared.py +92 -0
  183. dnakit/thermodynamics/backends.py +653 -0
  184. dnakit/thermodynamics/calculations.py +837 -0
  185. dnakit/thermodynamics/config.py +175 -0
  186. dnakit/thermodynamics/equilibrium.py +340 -0
  187. dnakit/thermodynamics/optics.py +385 -0
  188. dnakit/thermodynamics/parameters.py +79 -0
  189. dnakit/thermodynamics/results.py +445 -0
  190. dnakit/visualization/__init__.py +43 -0
  191. dnakit/visualization/_svg.py +185 -0
  192. dnakit/visualization/advanced.py +314 -0
  193. dnakit/visualization/config.py +255 -0
  194. dnakit/visualization/export.py +349 -0
  195. dnakit/visualization/matrix.py +279 -0
  196. dnakit/visualization/report.py +431 -0
  197. dnakit/visualization/results.py +236 -0
  198. dnakit/visualization/sequence.py +459 -0
  199. dnakit/workflows/__init__.py +45 -0
  200. dnakit/workflows/manifest.py +365 -0
  201. dnakit/workflows/runner.py +1548 -0
  202. dnakit/workflows/schema.py +898 -0
  203. dnakit-0.1.0.dev0.dist-info/METADATA +126 -0
  204. dnakit-0.1.0.dev0.dist-info/RECORD +210 -0
  205. dnakit-0.1.0.dev0.dist-info/WHEEL +5 -0
  206. dnakit-0.1.0.dev0.dist-info/entry_points.txt +2 -0
  207. dnakit-0.1.0.dev0.dist-info/licenses/DISCLAIMER.md +26 -0
  208. dnakit-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
  209. dnakit-0.1.0.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
  210. dnakit-0.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,414 @@
1
+ """Fixed 240-field DNA descriptor schema with formulas, units, and sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from itertools import product
7
+
8
+ from dnakit.descriptors._dinucleotide import DINUCLEOTIDE_PROPERTY_SPECS
9
+
10
+ DESCRIPTOR_SCHEMA_VERSION = "descriptor_schema_v1"
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class DescriptorField:
15
+ """Metadata for one ordered field in the 240-value descriptor vector."""
16
+
17
+ index: int
18
+ name: str
19
+ category: str
20
+ unit: str
21
+ formula: str
22
+ source: str
23
+
24
+ def to_dict(self) -> dict[str, int | str]:
25
+ return {
26
+ "index": self.index,
27
+ "name": self.name,
28
+ "category": self.category,
29
+ "unit": self.unit,
30
+ "formula": self.formula,
31
+ "source": self.source,
32
+ }
33
+
34
+
35
+ def _build_schema() -> tuple[DescriptorField, ...]:
36
+ fields: list[DescriptorField] = []
37
+
38
+ def add(name: str, category: str, unit: str, formula: str, source: str) -> None:
39
+ fields.append(DescriptorField(len(fields) + 1, name, category, unit, formula, source))
40
+
41
+ native = "DNAKit descriptor_schema_v1"
42
+ for name, unit, formula in (
43
+ ("symbol_length", "nt", "number of nucleotide symbols; explicit gaps excluded"),
44
+ (
45
+ "coordinate_span",
46
+ "nt",
47
+ "symbol_length + sum(known gap lengths); undefined with an unknown gap",
48
+ ),
49
+ ("canonical_base_count", "count", "count(A,C,G,T)"),
50
+ ("ambiguity_symbol_count", "count", "symbol_length - canonical_base_count"),
51
+ ("gap_object_count", "count", "number of explicit Gap objects"),
52
+ ("known_gap_nt", "nt", "sum(length of known explicit gaps)"),
53
+ ("unknown_gap_count", "count", "number of explicit gaps with unknown length"),
54
+ ("canonical_symbol_fraction", "fraction", "canonical_base_count / symbol_length"),
55
+ ("ambiguity_symbol_fraction", "fraction", "ambiguity_symbol_count / symbol_length"),
56
+ ("known_gap_fraction", "fraction", "known_gap_nt / coordinate_span"),
57
+ (
58
+ "canonical_run_count",
59
+ "count",
60
+ "number of uninterrupted A/C/G/T runs split by ambiguity or gaps",
61
+ ),
62
+ (
63
+ "longest_canonical_run_nt",
64
+ "nt",
65
+ "maximum uninterrupted A/C/G/T run length",
66
+ ),
67
+ ):
68
+ add(name, "basic", unit, formula, native)
69
+
70
+ composition_fields = (
71
+ ("purine_count", "count", "count(A)+count(G)"),
72
+ ("purine_fraction", "fraction", "purine_count / canonical_base_count"),
73
+ ("pyrimidine_count", "count", "count(C)+count(T)"),
74
+ (
75
+ "pyrimidine_fraction",
76
+ "fraction",
77
+ "pyrimidine_count / canonical_base_count",
78
+ ),
79
+ ("amino_count", "count", "count(A)+count(C)"),
80
+ ("amino_fraction", "fraction", "amino_count / canonical_base_count"),
81
+ ("keto_count", "count", "count(G)+count(T)"),
82
+ ("keto_fraction", "fraction", "keto_count / canonical_base_count"),
83
+ ("weak_count", "count", "count(A)+count(T)"),
84
+ ("weak_fraction", "fraction", "weak_count / canonical_base_count"),
85
+ ("strong_count", "count", "count(C)+count(G)"),
86
+ ("strong_fraction", "fraction", "strong_count / canonical_base_count"),
87
+ (
88
+ "purine_pyrimidine_skew",
89
+ "ratio",
90
+ "(purine_count-pyrimidine_count)/(purine_count+pyrimidine_count)",
91
+ ),
92
+ (
93
+ "amino_keto_skew",
94
+ "ratio",
95
+ "(amino_count-keto_count)/(amino_count+keto_count)",
96
+ ),
97
+ (
98
+ "weak_strong_skew",
99
+ "ratio",
100
+ "(weak_count-strong_count)/(weak_count+strong_count)",
101
+ ),
102
+ ("gc_at_ratio", "ratio", "strong_count / weak_count"),
103
+ )
104
+ for name, unit, formula in composition_fields:
105
+ add(name, "composition", unit, formula, native)
106
+
107
+ for k in range(1, 4):
108
+ for symbols in product("ACGT", repeat=k):
109
+ word = "".join(symbols)
110
+ add(
111
+ f"k{k}_{word}_frequency",
112
+ "kmer",
113
+ "fraction",
114
+ f"overlapping count({word}) / valid canonical {k}-mer positions",
115
+ "DNAKit exact overlapping k-mer definition",
116
+ )
117
+
118
+ skew_cpg_fields = (
119
+ ("gc_skew", "ratio", "(count(G)-count(C))/(count(G)+count(C))"),
120
+ ("at_skew", "ratio", "(count(A)-count(T))/(count(A)+count(T))"),
121
+ ("cpg_count", "count", "overlapping count(CG)"),
122
+ ("cpg_density", "fraction", "count(CG) / valid canonical dinucleotide positions"),
123
+ (
124
+ "cpg_observed_expected",
125
+ "ratio",
126
+ "count(CG)*canonical_base_count/(count(C)*count(G))",
127
+ ),
128
+ ("gpc_count", "count", "overlapping count(GC)"),
129
+ ("gpc_density", "fraction", "count(GC) / valid canonical dinucleotide positions"),
130
+ ("cpg_gpc_ratio", "ratio", "count(CG) / count(GC)"),
131
+ (
132
+ "cumulative_gc_skew_max",
133
+ "count",
134
+ "max prefix cumulative score where G=+1,C=-1,A/T=0",
135
+ ),
136
+ (
137
+ "cumulative_gc_skew_min",
138
+ "count",
139
+ "min prefix cumulative score where G=+1,C=-1,A/T=0",
140
+ ),
141
+ (
142
+ "cumulative_gc_skew_range",
143
+ "count",
144
+ "cumulative_gc_skew_max - cumulative_gc_skew_min",
145
+ ),
146
+ (
147
+ "cumulative_at_skew_max",
148
+ "count",
149
+ "max prefix cumulative score where A=+1,T=-1,C/G=0",
150
+ ),
151
+ (
152
+ "cumulative_at_skew_min",
153
+ "count",
154
+ "min prefix cumulative score where A=+1,T=-1,C/G=0",
155
+ ),
156
+ (
157
+ "cumulative_at_skew_range",
158
+ "count",
159
+ "cumulative_at_skew_max - cumulative_at_skew_min",
160
+ ),
161
+ (
162
+ "dinucleotide_rc_total_variation",
163
+ "fraction",
164
+ "0.5*sum_xy(abs(f_xy-f_reverse_complement(xy)))",
165
+ ),
166
+ (
167
+ "mono_chargaff_l1_distance",
168
+ "fraction",
169
+ "abs(f_A-f_T)+abs(f_C-f_G)",
170
+ ),
171
+ )
172
+ for name, unit, formula in skew_cpg_fields:
173
+ add(name, "skew_cpg", unit, formula, native)
174
+
175
+ shannon = "Shannon 1948; DOI 10.1002/j.1538-7305.1948.tb01338.x"
176
+ for k in range(1, 4):
177
+ add(
178
+ f"shannon_entropy_k{k}_bits",
179
+ "complexity",
180
+ "bits",
181
+ f"-sum(p({k}-mer)*log2(p({k}-mer)))",
182
+ shannon,
183
+ )
184
+ for k in range(1, 4):
185
+ add(
186
+ f"normalized_entropy_k{k}",
187
+ "complexity",
188
+ "fraction",
189
+ f"shannon_entropy_k{k}_bits / log2(4**{k})",
190
+ shannon,
191
+ )
192
+ for k in range(2, 7):
193
+ add(
194
+ f"linguistic_complexity_k{k}",
195
+ "complexity",
196
+ "fraction",
197
+ f"unique {k}-mers / min(4**{k}, valid {k}-mer positions)",
198
+ "Observed/possible k-word linguistic complexity",
199
+ )
200
+ add(
201
+ "linguistic_complexity_product_k1_k6",
202
+ "complexity",
203
+ "fraction",
204
+ "product of defined linguistic_complexity_k values for k=1..6",
205
+ "Observed/possible k-word linguistic complexity",
206
+ )
207
+ add(
208
+ "lz76_complexity",
209
+ "complexity",
210
+ "count",
211
+ "number of phrases in exhaustive Lempel-Ziv 1976 parsing",
212
+ "Lempel and Ziv 1976; DOI 10.1109/TIT.1976.1055501",
213
+ )
214
+ add(
215
+ "normalized_lz76_complexity",
216
+ "complexity",
217
+ "ratio",
218
+ "lz76_complexity*log_base4(canonical_base_count)/canonical_base_count",
219
+ "Lempel and Ziv 1976; DOI 10.1109/TIT.1976.1055501",
220
+ )
221
+ add("longest_homopolymer_nt", "complexity", "nt", "max canonical homopolymer run", native)
222
+ for base in "ACGT":
223
+ add(
224
+ f"longest_homopolymer_{base.lower()}_nt",
225
+ "complexity",
226
+ "nt",
227
+ f"max homopolymer run of {base}",
228
+ native,
229
+ )
230
+ add(
231
+ "exact_tandem_repeat_coverage_fraction",
232
+ "complexity",
233
+ "fraction",
234
+ "union bases covered by exact tandem repeats / canonical_base_count",
235
+ "DNAKit exact tandem repeat scanner; units 1..20; minimum repeats 2",
236
+ )
237
+
238
+ coding_fields = (
239
+ ("frame0_codon_count", "count", "number of valid forward frame-0 codons"),
240
+ ("frame0_unique_codon_count", "count", "number of distinct forward frame-0 codons"),
241
+ ("frame0_start_codon_count", "count", "count(ATG) in forward frame 0"),
242
+ (
243
+ "frame0_stop_codon_count",
244
+ "count",
245
+ "count(TAA,TAG,TGA) in forward frame 0",
246
+ ),
247
+ (
248
+ "frame0_start_codon_fraction",
249
+ "fraction",
250
+ "frame0_start_codon_count / frame0_codon_count",
251
+ ),
252
+ (
253
+ "frame0_stop_codon_fraction",
254
+ "fraction",
255
+ "frame0_stop_codon_count / frame0_codon_count",
256
+ ),
257
+ (
258
+ "frame0_codon_entropy_bits",
259
+ "bits",
260
+ "-sum(frame0 codon frequency*log2(frequency))",
261
+ ),
262
+ (
263
+ "frame0_effective_number_of_codons",
264
+ "count",
265
+ "2**frame0_codon_entropy_bits",
266
+ ),
267
+ ("frame0_gc1_fraction", "fraction", "GC bases at position 1 / valid frame-0 codons"),
268
+ ("frame0_gc2_fraction", "fraction", "GC bases at position 2 / valid frame-0 codons"),
269
+ ("frame0_gc3_fraction", "fraction", "GC bases at position 3 / valid frame-0 codons"),
270
+ (
271
+ "six_frame_complete_orf_count",
272
+ "count",
273
+ "complete start-to-next-stop ORFs across three frames on both strands",
274
+ ),
275
+ (
276
+ "six_frame_forward_complete_orf_count",
277
+ "count",
278
+ "complete ORFs across three forward frames",
279
+ ),
280
+ (
281
+ "six_frame_reverse_complete_orf_count",
282
+ "count",
283
+ "complete ORFs across three reverse-complement frames",
284
+ ),
285
+ (
286
+ "six_frame_longest_complete_orf_nt",
287
+ "nt",
288
+ "maximum complete six-frame ORF length including terminal stop",
289
+ ),
290
+ (
291
+ "six_frame_complete_orf_coverage_fraction",
292
+ "fraction",
293
+ "union of symbol positions covered by complete six-frame ORFs / canonical_base_count",
294
+ ),
295
+ )
296
+ for name, unit, formula in coding_fields:
297
+ add(name, "coding", unit, formula, "NCBI standard genetic code table 1; DNAKit ORF rules")
298
+
299
+ physchem_fields = (
300
+ ("mw_ss_oh_da", "Da", "anhydrous mass of one ssDNA strand with 5-prime OH"),
301
+ (
302
+ "mw_ss_5p_phosphate_da",
303
+ "Da",
304
+ "anhydrous mass of one ssDNA strand with 5-prime phosphate",
305
+ ),
306
+ (
307
+ "mw_ds_oh_da",
308
+ "Da",
309
+ "anhydrous mass of sequence plus complete reverse complement with 5-prime OH",
310
+ ),
311
+ (
312
+ "mw_ds_5p_phosphate_da",
313
+ "Da",
314
+ "anhydrous mass of sequence plus complete reverse complement; "
315
+ "both 5-prime phosphorylated",
316
+ ),
317
+ (
318
+ "epsilon260_ss_m_inverse_cm_inverse",
319
+ "M^-1 cm^-1",
320
+ "nearest-neighbor epsilon260 pair sum minus internal-base sum",
321
+ ),
322
+ (
323
+ "nmol_per_a260_1ml_1cm",
324
+ "nmol",
325
+ "1e6 / epsilon260 for A260=1, volume=1 mL, path=1 cm",
326
+ ),
327
+ (
328
+ "ug_per_a260_1ml_1cm",
329
+ "ug",
330
+ "1000*mw_ss_oh_da/epsilon260 for A260=1, volume=1 mL, path=1 cm",
331
+ ),
332
+ ("tm_wallace_c", "degree C", "2*(A+T)+4*(G+C)"),
333
+ (
334
+ "stacking_delta_h_kcal_per_mol",
335
+ "kcal/mol",
336
+ "sum SantaLucia nearest-neighbor stacking delta H",
337
+ ),
338
+ (
339
+ "stacking_delta_s_cal_per_k_mol_k",
340
+ "cal/(K mol)",
341
+ "sum SantaLucia nearest-neighbor stacking delta S",
342
+ ),
343
+ (
344
+ "stacking_delta_g37_kcal_per_mol",
345
+ "kcal/mol",
346
+ "stacking delta H - 310.15*stacking delta S/1000",
347
+ ),
348
+ (
349
+ "nn_delta_h_kcal_per_mol",
350
+ "kcal/mol",
351
+ "SantaLucia complete-duplex delta H with initiation and symmetry",
352
+ ),
353
+ (
354
+ "nn_delta_s_cal_per_mol_k",
355
+ "cal/(K mol)",
356
+ "SantaLucia complete-duplex delta S with initiation, symmetry, and salt",
357
+ ),
358
+ (
359
+ "nn_delta_g37_kcal_per_mol",
360
+ "kcal/mol",
361
+ "complete-duplex delta H - 310.15*delta S/1000",
362
+ ),
363
+ ("nn_tm_c", "degree C", "SantaLucia concentration- and sodium-adjusted Tm"),
364
+ ("self_complementary", "boolean", "sequence == reverse_complement(sequence)"),
365
+ )
366
+ for name, unit, formula in physchem_fields:
367
+ if name.startswith("mw_"):
368
+ source = "DNAKit anhydrous DNA residue mass table v1"
369
+ elif "epsilon260" in name or "a260" in name:
370
+ source = "Warshaw-Tinoco 1966 and Cantor-Warshaw-Shapiro 1970"
371
+ elif name == "tm_wallace_c":
372
+ source = "Wallace short-oligo 2AT+4GC rule"
373
+ else:
374
+ source = "SantaLucia 1998; DOI 10.1073/pnas.95.4.1460"
375
+ add(name, "physicochemical", unit, formula, source)
376
+
377
+ for item in DINUCLEOTIDE_PROPERTY_SPECS:
378
+ for statistic in ("mean", "sd", "min", "max"):
379
+ formula = (
380
+ f"population {statistic} of {item.display_name} values over valid "
381
+ "overlapping dinucleotides"
382
+ )
383
+ add(
384
+ f"diprodb_{item.key}_{statistic}",
385
+ "dinucleotide_property",
386
+ item.unit,
387
+ formula,
388
+ "Caller-supplied table; DNAKit bundles no numerical values",
389
+ )
390
+
391
+ if len(fields) != 240:
392
+ raise AssertionError(f"Descriptor schema v1 must contain 240 fields, got {len(fields)}.")
393
+ if len({field.name for field in fields}) != len(fields):
394
+ raise AssertionError("Descriptor schema v1 field names must be unique.")
395
+ return tuple(fields)
396
+
397
+
398
+ DESCRIPTOR_SCHEMA_V1 = _build_schema()
399
+ DESCRIPTOR_NAMES_V1 = tuple(field.name for field in DESCRIPTOR_SCHEMA_V1)
400
+
401
+
402
+ def descriptor_schema_v1() -> tuple[DescriptorField, ...]:
403
+ """Return the immutable, ordered 240-field schema."""
404
+
405
+ return DESCRIPTOR_SCHEMA_V1
406
+
407
+
408
+ __all__ = [
409
+ "DESCRIPTOR_NAMES_V1",
410
+ "DESCRIPTOR_SCHEMA_V1",
411
+ "DESCRIPTOR_SCHEMA_VERSION",
412
+ "DescriptorField",
413
+ "descriptor_schema_v1",
414
+ ]
@@ -0,0 +1,203 @@
1
+ """Native multi-descriptor sliding windows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections import Counter
7
+ from collections.abc import Iterable, Iterator
8
+ from itertools import islice
9
+
10
+ from dnakit.core._json import FrozenDict
11
+ from dnakit.core.gap import Gap
12
+ from dnakit.descriptors._shared import (
13
+ DescriptorAmbiguityPolicy,
14
+ SequenceInput,
15
+ canonical_runs,
16
+ coerce_ambiguity_policy,
17
+ reject_ambiguity,
18
+ sequence_and_id,
19
+ symbol_view,
20
+ validate_bool,
21
+ validate_positive_int,
22
+ )
23
+ from dnakit.descriptors.results import WindowDescriptorResult, WindowResult
24
+ from dnakit.exceptions import ConfigurationError
25
+
26
+ _SUPPORTED_DESCRIPTORS = frozenset({"gc", "entropy", "cpg"})
27
+
28
+
29
+ def _ranges(segment_ids: tuple[int, ...], *, cross_gaps: bool) -> Iterator[tuple[int, int]]:
30
+ if not segment_ids:
31
+ return
32
+ if cross_gaps:
33
+ yield 0, len(segment_ids)
34
+ return
35
+ start = 0
36
+ for index in range(1, len(segment_ids)):
37
+ if segment_ids[index] != segment_ids[index - 1]:
38
+ yield start, index
39
+ start = index
40
+ yield start, len(segment_ids)
41
+
42
+
43
+ def _resolve_descriptors(descriptors: Iterable[str]) -> tuple[str, ...]:
44
+ if isinstance(descriptors, (str, bytes)):
45
+ raise ConfigurationError("descriptors must be an iterable of descriptor names.")
46
+ try:
47
+ resolved = tuple(islice(iter(descriptors), len(_SUPPORTED_DESCRIPTORS) + 1))
48
+ except TypeError as exc:
49
+ raise ConfigurationError("descriptors must be an iterable of descriptor names.") from exc
50
+ if not resolved:
51
+ raise ConfigurationError("At least one window descriptor is required.")
52
+ if any(not isinstance(name, str) or name not in _SUPPORTED_DESCRIPTORS for name in resolved):
53
+ raise ConfigurationError(
54
+ "Unsupported window descriptor.",
55
+ context={"descriptors": resolved},
56
+ hint="Choose from: gc, entropy, cpg.",
57
+ )
58
+ if len(set(resolved)) != len(resolved):
59
+ raise ConfigurationError("Window descriptor names must be unique.")
60
+ return resolved
61
+
62
+
63
+ def _window_values(text: str, descriptors: tuple[str, ...], log_base: float) -> FrozenDict:
64
+ canonical = tuple(symbol for symbol in text if symbol in "ACGT")
65
+ values: dict[str, object] = {}
66
+ if "gc" in descriptors:
67
+ denominator = len(canonical)
68
+ gc_count = canonical.count("G") + canonical.count("C")
69
+ values.update(
70
+ {
71
+ "gc_fraction": gc_count / denominator if denominator else None,
72
+ "at_fraction": (denominator - gc_count) / denominator if denominator else None,
73
+ "canonical_base_denominator": denominator,
74
+ }
75
+ )
76
+ if "entropy" in descriptors:
77
+ counts = Counter(canonical)
78
+ total = sum(counts.values())
79
+ entropy = (
80
+ -sum((count / total) * math.log(count / total, log_base) for count in counts.values())
81
+ if total
82
+ else 0.0
83
+ )
84
+ values.update(
85
+ {
86
+ "shannon_entropy": entropy,
87
+ "entropy_observation_count": total,
88
+ }
89
+ )
90
+ if "cpg" in descriptors:
91
+ runs = tuple(canonical_runs(text))
92
+ cpg_count = sum(run.count("CG") for run in runs)
93
+ pair_denominator = sum(max(0, len(run) - 1) for run in runs)
94
+ canonical_length = sum(len(run) for run in runs)
95
+ c_count = sum(run.count("C") for run in runs)
96
+ g_count = sum(run.count("G") for run in runs)
97
+ values.update(
98
+ {
99
+ "cpg_count": cpg_count,
100
+ "cpg_density": cpg_count / pair_denominator if pair_denominator else None,
101
+ "cpg_observed_expected": (
102
+ cpg_count * canonical_length / (c_count * g_count)
103
+ if c_count and g_count
104
+ else None
105
+ ),
106
+ "cpg_pair_denominator": pair_denominator,
107
+ }
108
+ )
109
+ return FrozenDict(values)
110
+
111
+
112
+ def window_descriptors(
113
+ value: SequenceInput,
114
+ descriptors: Iterable[str],
115
+ *,
116
+ window_size: int,
117
+ step: int = 1,
118
+ include_partial: bool = False,
119
+ entropy_log_base: float = 2.0,
120
+ ambiguity_policy: DescriptorAmbiguityPolicy | str = DescriptorAmbiguityPolicy.ERROR,
121
+ cross_gaps: bool = False,
122
+ ) -> WindowDescriptorResult:
123
+ """Calculate GC, Shannon entropy, and/or CpG values in ordered windows.
124
+
125
+ Windows use zero-based, half-open symbol coordinates. By default each gap
126
+ starts a new window series, so no row crosses a gap. ``include_partial``
127
+ emits trailing windows shorter than ``window_size`` at each series boundary.
128
+ """
129
+
130
+ sequence, sequence_id = sequence_and_id(value)
131
+ policy = coerce_ambiguity_policy(ambiguity_policy)
132
+ reject_ambiguity(sequence, policy)
133
+ resolved_descriptors = _resolve_descriptors(descriptors)
134
+ validate_positive_int(window_size, "window_size")
135
+ validate_positive_int(step, "step")
136
+ validate_bool(include_partial, "include_partial")
137
+ validate_bool(cross_gaps, "cross_gaps")
138
+ if (
139
+ isinstance(entropy_log_base, bool)
140
+ or not isinstance(entropy_log_base, (int, float))
141
+ or not math.isfinite(entropy_log_base)
142
+ or entropy_log_base <= 0
143
+ or entropy_log_base == 1
144
+ ):
145
+ raise ConfigurationError(
146
+ "entropy_log_base must be finite, positive, and different from one.",
147
+ context={"entropy_log_base": entropy_log_base},
148
+ )
149
+
150
+ view = symbol_view(sequence)
151
+ rows: list[WindowResult] = []
152
+ range_segments = view.crossable_segment_ids if cross_gaps else view.segment_ids
153
+ for range_start, range_end in _ranges(range_segments, cross_gaps=False):
154
+ last_start = range_end if include_partial else range_end - window_size + 1
155
+ for start in range(range_start, max(range_start, last_start), step):
156
+ end = min(start + window_size, range_end)
157
+ if end <= start or (not include_partial and end - start < window_size):
158
+ continue
159
+ first_segment = view.segment_ids[start]
160
+ last_segment = view.segment_ids[end - 1]
161
+ crossed_gap_count = last_segment - first_segment
162
+ crossed_unknown = any(
163
+ boundary in view.unknown_gap_boundaries
164
+ for boundary in range(first_segment + 1, last_segment + 1)
165
+ )
166
+ coordinate_start = view.coordinate_positions[start]
167
+ last_coordinate = view.coordinate_positions[end - 1]
168
+ rows.append(
169
+ WindowResult(
170
+ symbol_start=start,
171
+ symbol_end=end,
172
+ coordinate_start=coordinate_start,
173
+ coordinate_end=None if last_coordinate is None else last_coordinate + 1,
174
+ is_partial=end - start < window_size,
175
+ crossed_gap_count=crossed_gap_count,
176
+ crossed_unknown_gap=crossed_unknown,
177
+ values=_window_values(
178
+ view.text[start:end],
179
+ resolved_descriptors,
180
+ float(entropy_log_base),
181
+ ),
182
+ )
183
+ )
184
+
185
+ gaps = tuple(part for part in sequence.parts if isinstance(part, Gap))
186
+ return WindowDescriptorResult(
187
+ name="window_descriptors",
188
+ method="sliding_symbol_window",
189
+ sequence_id=sequence_id,
190
+ ambiguity_policy=policy,
191
+ cross_gaps=cross_gaps,
192
+ gap_count=len(gaps),
193
+ unknown_gap_count=sum(gap.length is None for gap in gaps),
194
+ window_size=window_size,
195
+ step=step,
196
+ include_partial=include_partial,
197
+ entropy_log_base=float(entropy_log_base),
198
+ descriptors=resolved_descriptors,
199
+ windows=tuple(rows),
200
+ )
201
+
202
+
203
+ __all__ = ["window_descriptors"]
@@ -0,0 +1,54 @@
1
+ """Unified, checksummed downloads from public biological databases."""
2
+
3
+ from dnakit.download.catalogs import expression, variants
4
+ from dnakit.download.ena import reads
5
+ from dnakit.download.encode import DEFAULT_ENCODE_DOWNLOAD_URL
6
+ from dnakit.download.encode import files as encode_files
7
+ from dnakit.download.files import ProgressCallback, dataset, download_file, tracks
8
+ from dnakit.download.indexes import IndexProgress, build_index
9
+ from dnakit.download.metadata import MetadataFormat, metadata
10
+ from dnakit.download.models import (
11
+ DatasetDownloadResult,
12
+ DownloadConfig,
13
+ DownloadedFile,
14
+ DownloadProgress,
15
+ IndexArtifact,
16
+ IndexBuildResult,
17
+ RemoteFile,
18
+ )
19
+ from dnakit.download.ncbi import annotation, gene, genome, genome_package, taxonomy, virus_package
20
+ from dnakit.download.sequence import sequence
21
+ from dnakit.download.ucsc import DEFAULT_UCSC_DOWNLOAD_URL
22
+ from dnakit.download.ucsc import files as ucsc_files
23
+
24
+ __all__ = [
25
+ "DEFAULT_ENCODE_DOWNLOAD_URL",
26
+ "DEFAULT_UCSC_DOWNLOAD_URL",
27
+ "DatasetDownloadResult",
28
+ "DownloadConfig",
29
+ "DownloadProgress",
30
+ "DownloadedFile",
31
+ "IndexArtifact",
32
+ "IndexBuildResult",
33
+ "IndexProgress",
34
+ "MetadataFormat",
35
+ "ProgressCallback",
36
+ "RemoteFile",
37
+ "annotation",
38
+ "build_index",
39
+ "dataset",
40
+ "download_file",
41
+ "encode_files",
42
+ "expression",
43
+ "gene",
44
+ "genome",
45
+ "genome_package",
46
+ "metadata",
47
+ "reads",
48
+ "sequence",
49
+ "taxonomy",
50
+ "tracks",
51
+ "ucsc_files",
52
+ "variants",
53
+ "virus_package",
54
+ ]