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.
- dnakit/__init__.py +108 -0
- dnakit/_version.py +5 -0
- dnakit/alignment/__init__.py +6 -0
- dnakit/alignment/pairwise.py +512 -0
- dnakit/alignment/results.py +188 -0
- dnakit/backends/__init__.py +80 -0
- dnakit/backends/external.py +517 -0
- dnakit/backends/registry.py +207 -0
- dnakit/batch.py +452 -0
- dnakit/cache/__init__.py +5 -0
- dnakit/cache/store.py +424 -0
- dnakit/chunking.py +920 -0
- dnakit/cli/__init__.py +5 -0
- dnakit/cli/__main__.py +6 -0
- dnakit/cli/app.py +814 -0
- dnakit/cli/workflow.py +94 -0
- dnakit/config/__init__.py +5 -0
- dnakit/config/loader.py +381 -0
- dnakit/core/__init__.py +80 -0
- dnakit/core/_json.py +206 -0
- dnakit/core/backend_info.py +70 -0
- dnakit/core/collection.py +163 -0
- dnakit/core/coordinates.py +295 -0
- dnakit/core/enums.py +95 -0
- dnakit/core/facade.py +719 -0
- dnakit/core/feature.py +90 -0
- dnakit/core/gap.py +82 -0
- dnakit/core/issues.py +62 -0
- dnakit/core/provenance.py +305 -0
- dnakit/core/record.py +161 -0
- dnakit/core/results.py +134 -0
- dnakit/core/sequence.py +327 -0
- dnakit/datasets/__init__.py +100 -0
- dnakit/datasets/_advanced_shared.py +198 -0
- dnakit/datasets/_metadata.py +33 -0
- dnakit/datasets/advanced_deduplicate.py +183 -0
- dnakit/datasets/advanced_split.py +302 -0
- dnakit/datasets/clustering.py +286 -0
- dnakit/datasets/config.py +531 -0
- dnakit/datasets/deduplicate.py +303 -0
- dnakit/datasets/evaluation.py +244 -0
- dnakit/datasets/filter.py +135 -0
- dnakit/datasets/neural_clustering.py +221 -0
- dnakit/datasets/results.py +520 -0
- dnakit/datasets/split.py +450 -0
- dnakit/descriptors/__init__.py +102 -0
- dnakit/descriptors/_dinucleotide.py +345 -0
- dnakit/descriptors/_shared.py +180 -0
- dnakit/descriptors/all.py +917 -0
- dnakit/descriptors/basic.py +193 -0
- dnakit/descriptors/codon.py +195 -0
- dnakit/descriptors/complexity.py +201 -0
- dnakit/descriptors/entropy.py +101 -0
- dnakit/descriptors/homopolymer.py +105 -0
- dnakit/descriptors/kmer.py +88 -0
- dnakit/descriptors/results.py +286 -0
- dnakit/descriptors/schema.py +414 -0
- dnakit/descriptors/window.py +203 -0
- dnakit/download/__init__.py +54 -0
- dnakit/download/catalogs.py +166 -0
- dnakit/download/ena.py +131 -0
- dnakit/download/encode.py +103 -0
- dnakit/download/files.py +469 -0
- dnakit/download/indexes.py +233 -0
- dnakit/download/metadata.py +164 -0
- dnakit/download/models.py +227 -0
- dnakit/download/ncbi.py +333 -0
- dnakit/download/sequence.py +133 -0
- dnakit/download/ucsc.py +99 -0
- dnakit/evaluation/__init__.py +81 -0
- dnakit/evaluation/_shared.py +288 -0
- dnakit/evaluation/collection.py +270 -0
- dnakit/evaluation/config.py +591 -0
- dnakit/evaluation/distribution.py +287 -0
- dnakit/evaluation/frechet.py +223 -0
- dnakit/evaluation/generative.py +381 -0
- dnakit/evaluation/reference.py +409 -0
- dnakit/evaluation/results.py +137 -0
- dnakit/evaluation/scorecard.py +211 -0
- dnakit/evaluation/sequence.py +403 -0
- dnakit/evaluation/synthesis.py +249 -0
- dnakit/exceptions.py +189 -0
- dnakit/fingerprints/__init__.py +109 -0
- dnakit/fingerprints/_shared.py +116 -0
- dnakit/fingerprints/advanced.py +829 -0
- dnakit/fingerprints/bit.py +229 -0
- dnakit/fingerprints/encoding.py +351 -0
- dnakit/fingerprints/kmer.py +170 -0
- dnakit/fingerprints/results.py +531 -0
- dnakit/fingerprints/sketch.py +246 -0
- dnakit/io/__init__.py +112 -0
- dnakit/io/_advanced_common.py +105 -0
- dnakit/io/_formats.py +994 -0
- dnakit/io/annotations.py +1179 -0
- dnakit/io/api.py +759 -0
- dnakit/io/config.py +315 -0
- dnakit/io/genbank.py +523 -0
- dnakit/io/indexed.py +1146 -0
- dnakit/io/metadata.py +452 -0
- dnakit/io/parquet.py +56 -0
- dnakit/io/results.py +131 -0
- dnakit/io/source.py +138 -0
- dnakit/io/tables.py +1237 -0
- dnakit/molbio/__init__.py +101 -0
- dnakit/molbio/_shared.py +266 -0
- dnakit/molbio/assembly.py +279 -0
- dnakit/molbio/crispr.py +181 -0
- dnakit/molbio/optimization.py +897 -0
- dnakit/molbio/primer3_design.py +667 -0
- dnakit/molbio/primers.py +899 -0
- dnakit/molbio/restriction.py +573 -0
- dnakit/molbio/results.py +583 -0
- dnakit/ops/__init__.py +588 -0
- dnakit/ops/_common.py +201 -0
- dnakit/ops/circular.py +193 -0
- dnakit/ops/concat.py +204 -0
- dnakit/ops/direction.py +27 -0
- dnakit/ops/edit.py +443 -0
- dnakit/ops/evolution.py +991 -0
- dnakit/ops/mutation.py +151 -0
- dnakit/ops/records.py +1012 -0
- dnakit/ops/translation.py +257 -0
- dnakit/patterns/__init__.py +81 -0
- dnakit/patterns/_shared.py +282 -0
- dnakit/patterns/coding.py +360 -0
- dnakit/patterns/crispr.py +337 -0
- dnakit/patterns/motif.py +721 -0
- dnakit/patterns/regions.py +341 -0
- dnakit/patterns/repeats.py +480 -0
- dnakit/patterns/restriction.py +281 -0
- dnakit/patterns/results.py +411 -0
- dnakit/py.typed +1 -0
- dnakit/references/__init__.py +27 -0
- dnakit/references/models.py +155 -0
- dnakit/references/ncbi.py +785 -0
- dnakit/representations/__init__.py +29 -0
- dnakit/representations/backends.py +928 -0
- dnakit/representations/checkpoints.py +356 -0
- dnakit/representations/extraction.py +220 -0
- dnakit/representations/models.py +395 -0
- dnakit/representations/results.py +79 -0
- dnakit/search/__init__.py +132 -0
- dnakit/search/_http.py +271 -0
- dnakit/search/_shared.py +108 -0
- dnakit/search/blast.py +516 -0
- dnakit/search/ena.py +245 -0
- dnakit/search/encode.py +102 -0
- dnakit/search/ensembl.py +874 -0
- dnakit/search/models.py +159 -0
- dnakit/search/ncbi.py +756 -0
- dnakit/search/ucsc.py +388 -0
- dnakit/secondary_structure/__init__.py +38 -0
- dnakit/secondary_structure/dotbracket.py +410 -0
- dnakit/secondary_structure/nupack.py +589 -0
- dnakit/secondary_structure/results.py +142 -0
- dnakit/similarity/__init__.py +125 -0
- dnakit/similarity/_shared.py +152 -0
- dnakit/similarity/approximate.py +258 -0
- dnakit/similarity/compare.py +162 -0
- dnakit/similarity/dashing.py +936 -0
- dnakit/similarity/distance.py +279 -0
- dnakit/similarity/index.py +365 -0
- dnakit/similarity/matrix.py +254 -0
- dnakit/similarity/results.py +676 -0
- dnakit/similarity/search.py +286 -0
- dnakit/similarity/sketch.py +96 -0
- dnakit/similarity/vector.py +662 -0
- dnakit/standardize/__init__.py +77 -0
- dnakit/standardize/_shared.py +100 -0
- dnakit/standardize/config.py +411 -0
- dnakit/standardize/gaps.py +1060 -0
- dnakit/standardize/normalize.py +565 -0
- dnakit/standardize/results.py +247 -0
- dnakit/standardize/validate.py +487 -0
- dnakit/structure3d/__init__.py +47 -0
- dnakit/structure3d/_shared.py +47 -0
- dnakit/structure3d/geometry.py +773 -0
- dnakit/structure3d/pdb.py +253 -0
- dnakit/structure3d/results.py +233 -0
- dnakit/structure3d/threedna.py +174 -0
- dnakit/thermodynamics/__init__.py +112 -0
- dnakit/thermodynamics/_shared.py +92 -0
- dnakit/thermodynamics/backends.py +653 -0
- dnakit/thermodynamics/calculations.py +837 -0
- dnakit/thermodynamics/config.py +175 -0
- dnakit/thermodynamics/equilibrium.py +340 -0
- dnakit/thermodynamics/optics.py +385 -0
- dnakit/thermodynamics/parameters.py +79 -0
- dnakit/thermodynamics/results.py +445 -0
- dnakit/visualization/__init__.py +43 -0
- dnakit/visualization/_svg.py +185 -0
- dnakit/visualization/advanced.py +314 -0
- dnakit/visualization/config.py +255 -0
- dnakit/visualization/export.py +349 -0
- dnakit/visualization/matrix.py +279 -0
- dnakit/visualization/report.py +431 -0
- dnakit/visualization/results.py +236 -0
- dnakit/visualization/sequence.py +459 -0
- dnakit/workflows/__init__.py +45 -0
- dnakit/workflows/manifest.py +365 -0
- dnakit/workflows/runner.py +1548 -0
- dnakit/workflows/schema.py +898 -0
- dnakit-0.1.0.dev0.dist-info/METADATA +126 -0
- dnakit-0.1.0.dev0.dist-info/RECORD +210 -0
- dnakit-0.1.0.dev0.dist-info/WHEEL +5 -0
- dnakit-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- dnakit-0.1.0.dev0.dist-info/licenses/DISCLAIMER.md +26 -0
- dnakit-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
- dnakit-0.1.0.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
- 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
|
+
]
|