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,105 @@
1
+ """Native exact homopolymer descriptors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dnakit.core._json import FrozenDict
6
+ from dnakit.core.gap import Gap
7
+ from dnakit.descriptors._shared import (
8
+ DescriptorAmbiguityPolicy,
9
+ SequenceInput,
10
+ coerce_ambiguity_policy,
11
+ reject_ambiguity,
12
+ sequence_and_id,
13
+ symbol_view,
14
+ validate_bool,
15
+ validate_positive_int,
16
+ )
17
+ from dnakit.descriptors.results import HomopolymerResult, HomopolymerRun
18
+
19
+
20
+ def homopolymer_runs(
21
+ value: SequenceInput,
22
+ *,
23
+ min_run_length: int = 2,
24
+ ambiguity_policy: DescriptorAmbiguityPolicy | str = DescriptorAmbiguityPolicy.ERROR,
25
+ cross_gaps: bool = False,
26
+ ) -> HomopolymerResult:
27
+ """Find canonical-base runs in zero-based, half-open symbol coordinates.
28
+
29
+ Ignored IUPAC symbols always terminate a run. Gaps also terminate a run
30
+ unless ``cross_gaps=True``; any such crossing is recorded on the run.
31
+ """
32
+
33
+ sequence, sequence_id = sequence_and_id(value)
34
+ policy = coerce_ambiguity_policy(ambiguity_policy)
35
+ reject_ambiguity(sequence, policy)
36
+ validate_positive_int(min_run_length, "min_run_length")
37
+ validate_bool(cross_gaps, "cross_gaps")
38
+ view = symbol_view(sequence)
39
+ run_segments = view.crossable_segment_ids if cross_gaps else view.segment_ids
40
+ found: list[HomopolymerRun] = []
41
+ longest = {base: 0 for base in "ACGT"}
42
+ run_start: int | None = None
43
+
44
+ def close_run(end: int) -> None:
45
+ nonlocal run_start
46
+ if run_start is None:
47
+ return
48
+ base = view.text[run_start]
49
+ length = end - run_start
50
+ longest[base] = max(longest[base], length)
51
+ if length >= min_run_length:
52
+ first_segment = view.segment_ids[run_start]
53
+ last_segment = view.segment_ids[end - 1]
54
+ crossed_gap_count = last_segment - first_segment
55
+ crossed_unknown = any(
56
+ boundary in view.unknown_gap_boundaries
57
+ for boundary in range(first_segment + 1, last_segment + 1)
58
+ )
59
+ coordinate_start = view.coordinate_positions[run_start]
60
+ last_coordinate = view.coordinate_positions[end - 1]
61
+ found.append(
62
+ HomopolymerRun(
63
+ base=base,
64
+ length=length,
65
+ symbol_start=run_start,
66
+ symbol_end=end,
67
+ coordinate_start=coordinate_start,
68
+ coordinate_end=None if last_coordinate is None else last_coordinate + 1,
69
+ crossed_gap_count=crossed_gap_count,
70
+ crossed_unknown_gap=crossed_unknown,
71
+ )
72
+ )
73
+ run_start = None
74
+
75
+ for index, symbol in enumerate(view.text):
76
+ same_segment = run_start is None or run_segments[index] == run_segments[index - 1]
77
+ if symbol not in "ACGT":
78
+ close_run(index)
79
+ elif run_start is None:
80
+ run_start = index
81
+ elif symbol != view.text[run_start] or not same_segment:
82
+ close_run(index)
83
+ run_start = index
84
+ close_run(len(view.text))
85
+
86
+ gaps = tuple(part for part in sequence.parts if isinstance(part, Gap))
87
+ return HomopolymerResult(
88
+ name="homopolymer",
89
+ method="exact_canonical_run",
90
+ sequence_id=sequence_id,
91
+ ambiguity_policy=policy,
92
+ cross_gaps=cross_gaps,
93
+ gap_count=len(gaps),
94
+ unknown_gap_count=sum(gap.length is None for gap in gaps),
95
+ min_run_length=min_run_length,
96
+ longest_length=max(longest.values(), default=0),
97
+ longest_by_base=FrozenDict(longest),
98
+ runs=tuple(found),
99
+ ignored_ambiguity_count=(
100
+ sequence.ambiguity_count if policy is DescriptorAmbiguityPolicy.IGNORE else 0
101
+ ),
102
+ )
103
+
104
+
105
+ __all__ = ["homopolymer_runs"]
@@ -0,0 +1,88 @@
1
+ """Native exact k-mer statistics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+
7
+ from dnakit.core._json import FrozenDict
8
+ from dnakit.core.gap import Gap
9
+ from dnakit.descriptors._shared import (
10
+ DescriptorAmbiguityPolicy,
11
+ SequenceInput,
12
+ coerce_ambiguity_policy,
13
+ iter_kmers,
14
+ reject_ambiguity,
15
+ sequence_and_id,
16
+ validate_bool,
17
+ )
18
+ from dnakit.descriptors.results import KmerResult
19
+ from dnakit.exceptions import ConfigurationError
20
+
21
+ _COMPLEMENT = str.maketrans("ACGT", "TGCA")
22
+
23
+
24
+ def canonical_kmer(value: str) -> str:
25
+ """Return the lexicographically smaller strand representation."""
26
+
27
+ if not isinstance(value, str) or not value or any(base not in "ACGT" for base in value):
28
+ raise ConfigurationError(
29
+ "A canonical k-mer must be a non-empty A/C/G/T string.",
30
+ context={"kmer": value},
31
+ )
32
+ reverse_complement = value.translate(_COMPLEMENT)[::-1]
33
+ return min(value, reverse_complement)
34
+
35
+
36
+ def kmer_statistics(
37
+ value: SequenceInput,
38
+ k: int,
39
+ *,
40
+ overlapping: bool = True,
41
+ canonical: bool = False,
42
+ ambiguity_policy: DescriptorAmbiguityPolicy | str = DescriptorAmbiguityPolicy.ERROR,
43
+ cross_gaps: bool = False,
44
+ ) -> KmerResult:
45
+ """Return sparse count, frequency, and presence data for valid k-mers.
46
+
47
+ Under ``ignore``, any window containing an IUPAC ambiguity is omitted and
48
+ k-mers never bridge that symbol. If ``k`` exceeds every eligible run, the
49
+ three sparse outputs are empty and the denominator is zero.
50
+ """
51
+
52
+ sequence, sequence_id = sequence_and_id(value)
53
+ policy = coerce_ambiguity_policy(ambiguity_policy)
54
+ reject_ambiguity(sequence, policy)
55
+ validate_bool(canonical, "canonical")
56
+ observed = iter_kmers(
57
+ sequence,
58
+ k=k,
59
+ overlapping=overlapping,
60
+ cross_gaps=cross_gaps,
61
+ )
62
+ counts = Counter(canonical_kmer(item) if canonical else item for item in observed)
63
+ ordered_counts = dict(sorted(counts.items()))
64
+ denominator = sum(ordered_counts.values())
65
+ frequencies = {item: count / denominator for item, count in ordered_counts.items()}
66
+ gaps = tuple(part for part in sequence.parts if isinstance(part, Gap))
67
+ return KmerResult(
68
+ name="kmer",
69
+ method="exact_sliding_kmer",
70
+ sequence_id=sequence_id,
71
+ ambiguity_policy=policy,
72
+ cross_gaps=cross_gaps,
73
+ gap_count=len(gaps),
74
+ unknown_gap_count=sum(gap.length is None for gap in gaps),
75
+ k=k,
76
+ overlapping=overlapping,
77
+ canonical=canonical,
78
+ counts=FrozenDict(ordered_counts),
79
+ frequencies=FrozenDict(frequencies),
80
+ presence=tuple(ordered_counts),
81
+ denominator=denominator,
82
+ ignored_ambiguity_count=(
83
+ sequence.ambiguity_count if policy is DescriptorAmbiguityPolicy.IGNORE else 0
84
+ ),
85
+ )
86
+
87
+
88
+ __all__ = ["canonical_kmer", "kmer_statistics"]
@@ -0,0 +1,286 @@
1
+ """Immutable result objects returned by native descriptor functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, cast
7
+
8
+ from dnakit.core import Provenance
9
+ from dnakit.core._json import FrozenDict, to_json_compatible
10
+ from dnakit.descriptors._shared import DescriptorAmbiguityPolicy
11
+ from dnakit.descriptors.schema import DESCRIPTOR_NAMES_V1, DESCRIPTOR_SCHEMA_VERSION
12
+ from dnakit.exceptions import ConfigurationError
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class DescriptorResult:
17
+ """Common auditable context shared by all descriptor result types."""
18
+
19
+ name: str
20
+ method: str
21
+ sequence_id: str | None
22
+ ambiguity_policy: DescriptorAmbiguityPolicy | None
23
+ cross_gaps: bool
24
+ gap_count: int
25
+ unknown_gap_count: int
26
+
27
+ def to_dict(self) -> dict[str, Any]:
28
+ return cast(dict[str, Any], to_json_compatible(self))
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class AllDescriptorsResult:
33
+ """One ordered 240-field vector plus explicit unavailable-value reasons."""
34
+
35
+ schema_version: str
36
+ sequence_id: str | None
37
+ values: FrozenDict
38
+ unavailable_reasons: FrozenDict
39
+ conditions: FrozenDict
40
+ provenance: Provenance
41
+
42
+ def __post_init__(self) -> None:
43
+ if self.schema_version != DESCRIPTOR_SCHEMA_VERSION:
44
+ raise ConfigurationError("Unknown all-descriptor schema version.")
45
+ if self.sequence_id is not None and (
46
+ not isinstance(self.sequence_id, str) or not self.sequence_id.strip()
47
+ ):
48
+ raise ConfigurationError("AllDescriptorsResult sequence_id must be non-empty or None.")
49
+ if tuple(self.values) != DESCRIPTOR_NAMES_V1:
50
+ raise ConfigurationError(
51
+ "AllDescriptorsResult values must exactly match descriptor_schema_v1 order."
52
+ )
53
+ unavailable = {name for name, value in self.values.items() if value is None}
54
+ if set(self.unavailable_reasons) != unavailable or any(
55
+ not isinstance(reason, str) or not reason.strip()
56
+ for reason in self.unavailable_reasons.values()
57
+ ):
58
+ raise ConfigurationError(
59
+ "Every unavailable descriptor must have exactly one non-empty reason."
60
+ )
61
+ if not isinstance(self.conditions, FrozenDict):
62
+ raise ConfigurationError("AllDescriptorsResult conditions must be FrozenDict.")
63
+ if not isinstance(self.provenance, Provenance):
64
+ raise ConfigurationError("AllDescriptorsResult provenance must be Provenance.")
65
+
66
+ def to_dict(self) -> dict[str, Any]:
67
+ return cast(dict[str, Any], to_json_compatible(self))
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class LengthResult(DescriptorResult):
72
+ """Explicit symbol, coordinate, ambiguity, and gap lengths."""
73
+
74
+ symbol_length: int
75
+ coordinate_span: int | None
76
+ canonical_base_count: int
77
+ ambiguity_length: int
78
+ known_gap_length: int
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class CompositionResult(DescriptorResult):
83
+ """Canonical base counts and fractions with an explicit denominator."""
84
+
85
+ counts: FrozenDict
86
+ fractions: FrozenDict
87
+ denominator: int
88
+ ignored_ambiguity_count: int
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class ContentResult(DescriptorResult):
93
+ """GC and AT composition calculated over canonical bases."""
94
+
95
+ gc_count: int
96
+ at_count: int
97
+ gc_fraction: float | None
98
+ at_fraction: float | None
99
+ denominator: int
100
+ ignored_ambiguity_count: int
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class SkewResult(DescriptorResult):
105
+ """GC=(G-C)/(G+C) and AT=(A-T)/(A+T), or None at zero denominator."""
106
+
107
+ gc_skew: float | None
108
+ at_skew: float | None
109
+ gc_denominator: int
110
+ at_denominator: int
111
+ ignored_ambiguity_count: int
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class CpGResult(DescriptorResult):
116
+ """CpG count, adjacency density, and observed/expected ratio."""
117
+
118
+ cpg_count: int
119
+ density: float | None
120
+ observed_expected: float | None
121
+ adjacent_pair_denominator: int
122
+ expected_length_denominator: int
123
+ c_count: int
124
+ g_count: int
125
+ ignored_ambiguity_count: int
126
+ density_formula: str
127
+ observed_expected_formula: str
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class KmerResult(DescriptorResult):
132
+ """Sparse k-mer counts, frequencies, and presence values."""
133
+
134
+ k: int
135
+ overlapping: bool
136
+ canonical: bool
137
+ counts: FrozenDict
138
+ frequencies: FrozenDict
139
+ presence: tuple[str, ...]
140
+ denominator: int
141
+ ignored_ambiguity_count: int
142
+
143
+
144
+ @dataclass(frozen=True)
145
+ class EntropyResult(DescriptorResult):
146
+ """Shannon entropy of canonical bases or valid canonical k-mers."""
147
+
148
+ entropy: float
149
+ unit: str
150
+ k: int
151
+ log_base: float
152
+ observation_count: int
153
+ category_count: int
154
+ ignored_ambiguity_count: int
155
+
156
+
157
+ @dataclass(frozen=True)
158
+ class HomopolymerRun:
159
+ """One canonical-base run in zero-based, half-open coordinates."""
160
+
161
+ base: str
162
+ length: int
163
+ symbol_start: int
164
+ symbol_end: int
165
+ coordinate_start: int | None
166
+ coordinate_end: int | None
167
+ crossed_gap_count: int
168
+ crossed_unknown_gap: bool
169
+
170
+
171
+ @dataclass(frozen=True)
172
+ class HomopolymerResult(DescriptorResult):
173
+ """Longest runs and all runs meeting the requested minimum length."""
174
+
175
+ min_run_length: int
176
+ longest_length: int
177
+ longest_by_base: FrozenDict
178
+ runs: tuple[HomopolymerRun, ...]
179
+ ignored_ambiguity_count: int
180
+
181
+
182
+ @dataclass(frozen=True)
183
+ class WindowResult:
184
+ """Descriptors for one zero-based, half-open symbol window."""
185
+
186
+ symbol_start: int
187
+ symbol_end: int
188
+ coordinate_start: int | None
189
+ coordinate_end: int | None
190
+ is_partial: bool
191
+ crossed_gap_count: int
192
+ crossed_unknown_gap: bool
193
+ values: FrozenDict
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class WindowDescriptorResult(DescriptorResult):
198
+ """Ordered position-by-feature rows for a sliding-window calculation."""
199
+
200
+ window_size: int
201
+ step: int
202
+ include_partial: bool
203
+ entropy_log_base: float
204
+ descriptors: tuple[str, ...]
205
+ windows: tuple[WindowResult, ...]
206
+
207
+
208
+ @dataclass(frozen=True)
209
+ class CodonResult(DescriptorResult):
210
+ """In-frame codon counts, frequencies, and start/stop densities."""
211
+
212
+ frame: int
213
+ genetic_code: int
214
+ counts: FrozenDict
215
+ frequencies: FrozenDict
216
+ codon_count: int
217
+ start_count: int
218
+ stop_count: int
219
+ start_density: float | None
220
+ stop_density: float | None
221
+ incomplete_base_count: int
222
+ ignored_ambiguity_codon_count: int
223
+ gap_interrupted_codon_count: int
224
+ phase_coordinate_system: str = "sequence-coordinate"
225
+ phase_unresolved_after_gap: bool = False
226
+ unresolved_downstream_base_count: int = 0
227
+
228
+
229
+ @dataclass(frozen=True)
230
+ class ComplexityResult(DescriptorResult):
231
+ """Linguistic vocabulary complexity with per-word-size audit."""
232
+
233
+ score: float
234
+ max_word_size: int
235
+ by_k: FrozenDict
236
+ observed_by_k: FrozenDict
237
+ possible_by_k: FrozenDict
238
+ observation_count: int
239
+ max_observations: int
240
+ formula: str
241
+
242
+
243
+ @dataclass(frozen=True)
244
+ class RepeatRun:
245
+ unit: str
246
+ unit_length: int
247
+ repeat_count: int
248
+ symbol_start: int
249
+ symbol_end: int
250
+
251
+
252
+ @dataclass(frozen=True)
253
+ class ExactRepeatResult(DescriptorResult):
254
+ """Union coverage and runs from an exact tandem-repeat scan."""
255
+
256
+ repeat_fraction: float
257
+ repeated_base_count: int
258
+ denominator: int
259
+ min_unit_length: int
260
+ max_unit_length: int
261
+ min_repeats: int
262
+ runs: tuple[RepeatRun, ...]
263
+ repeat_count_by_unit: FrozenDict
264
+ comparisons: int
265
+ max_comparisons: int
266
+
267
+
268
+ __all__ = [
269
+ "AllDescriptorsResult",
270
+ "CodonResult",
271
+ "ComplexityResult",
272
+ "CompositionResult",
273
+ "ContentResult",
274
+ "CpGResult",
275
+ "DescriptorResult",
276
+ "EntropyResult",
277
+ "ExactRepeatResult",
278
+ "HomopolymerResult",
279
+ "HomopolymerRun",
280
+ "KmerResult",
281
+ "LengthResult",
282
+ "RepeatRun",
283
+ "SkewResult",
284
+ "WindowDescriptorResult",
285
+ "WindowResult",
286
+ ]