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
dnakit/ops/mutation.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Specified and reproducible single-site mutation operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Literal, TypeAlias, cast
|
|
8
|
+
|
|
9
|
+
from dnakit.core import DNASequence
|
|
10
|
+
from dnakit.exceptions import ConfigurationError
|
|
11
|
+
from dnakit.ops._common import require_sequence
|
|
12
|
+
from dnakit.ops.edit import Edit, substitute
|
|
13
|
+
|
|
14
|
+
MutationMode: TypeAlias = Literal["specified", "random"]
|
|
15
|
+
RandomSource: TypeAlias = Literal["none", "seed", "rng"]
|
|
16
|
+
RandomState: TypeAlias = tuple[int, tuple[int, ...], float | None]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class MutationResult:
|
|
21
|
+
"""A new sequence plus a reproducible, single-site mutation audit."""
|
|
22
|
+
|
|
23
|
+
sequence: DNASequence
|
|
24
|
+
mode: MutationMode
|
|
25
|
+
edit: Edit
|
|
26
|
+
seed: int | None
|
|
27
|
+
random_source: RandomSource = "none"
|
|
28
|
+
rng_algorithm_version: int | None = None
|
|
29
|
+
rng_state_before: RandomState | None = None
|
|
30
|
+
rng_state_after: RandomState | None = None
|
|
31
|
+
allowed_bases: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def mutate(
|
|
35
|
+
sequence: DNASequence,
|
|
36
|
+
*,
|
|
37
|
+
position: int | None = None,
|
|
38
|
+
replacement: str | None = None,
|
|
39
|
+
seed: int | None = None,
|
|
40
|
+
rng: random.Random | None = None,
|
|
41
|
+
allowed_bases: str = "ACGT",
|
|
42
|
+
) -> MutationResult:
|
|
43
|
+
"""Apply one specified substitution or one seeded random SNV.
|
|
44
|
+
|
|
45
|
+
Supplying ``position`` and ``replacement`` selects specified mode. Omitting
|
|
46
|
+
both selects random mode and requires exactly one of ``seed`` or ``rng``.
|
|
47
|
+
Random mode chooses a nucleotide position (gaps are never selected), then a
|
|
48
|
+
different base from ``allowed_bases``. Combinatorial mutation enumeration
|
|
49
|
+
is intentionally outside the MVP.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
source = require_sequence(sequence)
|
|
53
|
+
specified = position is not None or replacement is not None
|
|
54
|
+
if specified:
|
|
55
|
+
if position is None or replacement is None:
|
|
56
|
+
raise ConfigurationError(
|
|
57
|
+
"Specified mutation requires both position and replacement.",
|
|
58
|
+
code="INCOMPLETE_SPECIFIED_MUTATION",
|
|
59
|
+
)
|
|
60
|
+
if seed is not None or rng is not None:
|
|
61
|
+
raise ConfigurationError(
|
|
62
|
+
"seed and rng are only valid for random mutation.",
|
|
63
|
+
code="UNEXPECTED_RANDOM_SOURCE",
|
|
64
|
+
)
|
|
65
|
+
if not isinstance(replacement, str) or len(replacement) != 1:
|
|
66
|
+
raise ConfigurationError(
|
|
67
|
+
"Specified mutation replacement must be one normalized nucleotide symbol.",
|
|
68
|
+
code="INVALID_MUTATION_REPLACEMENT",
|
|
69
|
+
context={"replacement": replacement},
|
|
70
|
+
)
|
|
71
|
+
result = substitute(source, position, position + 1, replacement)
|
|
72
|
+
if result.edits[0].removed_symbols == replacement:
|
|
73
|
+
raise ConfigurationError(
|
|
74
|
+
"Specified mutation replacement must differ from the original symbol.",
|
|
75
|
+
code="NO_OP_MUTATION",
|
|
76
|
+
context={"position": position, "symbol": replacement},
|
|
77
|
+
)
|
|
78
|
+
return MutationResult(result.sequence, "specified", result.edits[0], None)
|
|
79
|
+
|
|
80
|
+
if (seed is None) == (rng is None):
|
|
81
|
+
raise ConfigurationError(
|
|
82
|
+
"Random mutation requires exactly one of seed or rng.",
|
|
83
|
+
code="RANDOM_SOURCE_REQUIRED",
|
|
84
|
+
)
|
|
85
|
+
if seed is not None and (isinstance(seed, bool) or not isinstance(seed, int)):
|
|
86
|
+
raise ConfigurationError(
|
|
87
|
+
"seed must be an integer.",
|
|
88
|
+
code="INVALID_RANDOM_SEED",
|
|
89
|
+
context={"seed": seed},
|
|
90
|
+
)
|
|
91
|
+
if rng is not None and not isinstance(rng, random.Random):
|
|
92
|
+
raise ConfigurationError(
|
|
93
|
+
"rng must be random.Random.",
|
|
94
|
+
code="INVALID_RANDOM_GENERATOR",
|
|
95
|
+
context={"type": type(rng).__name__},
|
|
96
|
+
)
|
|
97
|
+
if (
|
|
98
|
+
not isinstance(allowed_bases, str)
|
|
99
|
+
or not allowed_bases
|
|
100
|
+
or len(set(allowed_bases)) != len(allowed_bases)
|
|
101
|
+
or any(base not in "ACGT" for base in allowed_bases)
|
|
102
|
+
):
|
|
103
|
+
raise ConfigurationError(
|
|
104
|
+
"allowed_bases must contain unique uppercase characters from ACGT.",
|
|
105
|
+
code="INVALID_ALLOWED_BASES",
|
|
106
|
+
context={"allowed_bases": allowed_bases},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
candidates: list[tuple[int, str]] = []
|
|
110
|
+
coordinate = 0
|
|
111
|
+
for part in source.parts:
|
|
112
|
+
if isinstance(part, str):
|
|
113
|
+
candidates.extend((coordinate + index, symbol) for index, symbol in enumerate(part))
|
|
114
|
+
coordinate += len(part)
|
|
115
|
+
else:
|
|
116
|
+
if part.length is None:
|
|
117
|
+
raise ConfigurationError(
|
|
118
|
+
"Random mutation requires known Gap lengths.",
|
|
119
|
+
code="UNKNOWN_RANDOM_MUTATION_COORDINATES",
|
|
120
|
+
)
|
|
121
|
+
coordinate += part.length
|
|
122
|
+
viable = [
|
|
123
|
+
(candidate_position, base)
|
|
124
|
+
for candidate_position, base in candidates
|
|
125
|
+
if any(replacement_base != base for replacement_base in allowed_bases)
|
|
126
|
+
]
|
|
127
|
+
if not viable:
|
|
128
|
+
raise ConfigurationError(
|
|
129
|
+
"No nucleotide position has an allowed alternative base.",
|
|
130
|
+
code="NO_RANDOM_MUTATION_CANDIDATE",
|
|
131
|
+
)
|
|
132
|
+
generator = random.Random(seed) if rng is None else rng
|
|
133
|
+
state_before = cast(RandomState, generator.getstate())
|
|
134
|
+
selected_position, original = generator.choice(viable)
|
|
135
|
+
choices = tuple(base for base in allowed_bases if base != original)
|
|
136
|
+
selected_replacement = generator.choice(choices)
|
|
137
|
+
result = substitute(source, selected_position, selected_position + 1, selected_replacement)
|
|
138
|
+
return MutationResult(
|
|
139
|
+
result.sequence,
|
|
140
|
+
"random",
|
|
141
|
+
result.edits[0],
|
|
142
|
+
seed,
|
|
143
|
+
"seed" if rng is None else "rng",
|
|
144
|
+
random.Random.VERSION,
|
|
145
|
+
state_before,
|
|
146
|
+
cast(RandomState, generator.getstate()),
|
|
147
|
+
allowed_bases,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = ["MutationResult", "RandomSource", "RandomState", "mutate"]
|