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,221 @@
|
|
|
1
|
+
"""DNA foundation-model representations followed by k-means clustering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import replace
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from dnakit.core import DNARecord, DNASet
|
|
11
|
+
from dnakit.exceptions import BackendUnavailableError, ConfigurationError, SequenceError
|
|
12
|
+
from dnakit.representations import RepresentationBackend, extract_representations
|
|
13
|
+
|
|
14
|
+
from .config import NeuralClusteringConfig
|
|
15
|
+
from .results import Cluster, NeuralClusteringResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _materialize(
|
|
19
|
+
records: Iterable[DNARecord],
|
|
20
|
+
*,
|
|
21
|
+
max_records: int,
|
|
22
|
+
) -> tuple[DNARecord, ...]:
|
|
23
|
+
materialized: list[DNARecord] = []
|
|
24
|
+
for index, record in enumerate(records):
|
|
25
|
+
if index >= max_records:
|
|
26
|
+
raise ConfigurationError(
|
|
27
|
+
"Neural clustering exceeded max_records.",
|
|
28
|
+
code="NEURAL_CLUSTER_RECORD_LIMIT",
|
|
29
|
+
context={"max_records": max_records},
|
|
30
|
+
)
|
|
31
|
+
if not isinstance(record, DNARecord):
|
|
32
|
+
raise SequenceError(
|
|
33
|
+
"Neural clustering accepts only DNARecord objects.",
|
|
34
|
+
code="INVALID_RECORD_SEQUENCE",
|
|
35
|
+
context={"index": index, "type": type(record).__name__},
|
|
36
|
+
)
|
|
37
|
+
materialized.append(record)
|
|
38
|
+
if not materialized:
|
|
39
|
+
raise ConfigurationError(
|
|
40
|
+
"At least one DNARecord is required for neural clustering.",
|
|
41
|
+
code="EMPTY_NEURAL_CLUSTER_INPUT",
|
|
42
|
+
)
|
|
43
|
+
return tuple(materialized)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _require_scientific_stack() -> tuple[Any, Any, Any, Any]:
|
|
47
|
+
try:
|
|
48
|
+
import numpy as np
|
|
49
|
+
|
|
50
|
+
cluster_module = importlib.import_module("sklearn.cluster")
|
|
51
|
+
decomposition_module = importlib.import_module("sklearn.decomposition")
|
|
52
|
+
metrics_module = importlib.import_module("sklearn.metrics")
|
|
53
|
+
except ImportError as exc:
|
|
54
|
+
raise BackendUnavailableError(
|
|
55
|
+
"Neural clustering requires NumPy and scikit-learn.",
|
|
56
|
+
code="MISSING_NEURAL_DEPENDENCY",
|
|
57
|
+
hint='Install the neural extra with: python -m pip install "dnakit[neural]"',
|
|
58
|
+
) from exc
|
|
59
|
+
return (
|
|
60
|
+
np,
|
|
61
|
+
cluster_module.KMeans,
|
|
62
|
+
decomposition_module.PCA,
|
|
63
|
+
metrics_module.silhouette_score,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _l2_normalize(matrix: Any, np: Any) -> Any:
|
|
68
|
+
norms = np.linalg.norm(matrix, axis=1, keepdims=True)
|
|
69
|
+
zero_rows = np.flatnonzero(norms[:, 0] == 0)
|
|
70
|
+
if len(zero_rows):
|
|
71
|
+
raise ConfigurationError(
|
|
72
|
+
"Cannot L2-normalize zero-valued model representations.",
|
|
73
|
+
code="ZERO_REPRESENTATION_VECTOR",
|
|
74
|
+
context={"row_indices": tuple(int(value) for value in zero_rows[:20])},
|
|
75
|
+
)
|
|
76
|
+
return matrix / norms
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def neural_cluster_sequences(
|
|
80
|
+
records: Iterable[DNARecord],
|
|
81
|
+
*,
|
|
82
|
+
config: NeuralClusteringConfig | None = None,
|
|
83
|
+
backend: RepresentationBackend | None = None,
|
|
84
|
+
) -> NeuralClusteringResult:
|
|
85
|
+
"""Extract model representations and group them with reproducible k-means."""
|
|
86
|
+
|
|
87
|
+
resolved = NeuralClusteringConfig() if config is None else config
|
|
88
|
+
if not isinstance(resolved, NeuralClusteringConfig):
|
|
89
|
+
raise ConfigurationError(
|
|
90
|
+
"config must be NeuralClusteringConfig or None.",
|
|
91
|
+
code="INVALID_NEURAL_CLUSTER_CONFIG",
|
|
92
|
+
)
|
|
93
|
+
materialized = _materialize(records, max_records=resolved.max_records)
|
|
94
|
+
if resolved.n_clusters > len(materialized):
|
|
95
|
+
raise ConfigurationError(
|
|
96
|
+
"n_clusters cannot exceed the number of input records.",
|
|
97
|
+
code="INVALID_NEURAL_CLUSTER_COUNT",
|
|
98
|
+
context={"n_clusters": resolved.n_clusters, "input_count": len(materialized)},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
representation_config = replace(
|
|
102
|
+
resolved.representation,
|
|
103
|
+
max_records=min(resolved.representation.max_records, resolved.max_records),
|
|
104
|
+
)
|
|
105
|
+
representation_result = extract_representations(
|
|
106
|
+
materialized,
|
|
107
|
+
config=representation_config,
|
|
108
|
+
backend=backend,
|
|
109
|
+
)
|
|
110
|
+
np, kmeans_class, pca_class, silhouette_function = _require_scientific_stack()
|
|
111
|
+
matrix = np.asarray(representation_result.representations, dtype=np.float64)
|
|
112
|
+
if resolved.normalize:
|
|
113
|
+
matrix = _l2_normalize(matrix, np)
|
|
114
|
+
|
|
115
|
+
explained_variance: tuple[float, ...] = ()
|
|
116
|
+
pca_components = resolved.pca_components
|
|
117
|
+
if pca_components is not None:
|
|
118
|
+
if len(materialized) < 2:
|
|
119
|
+
raise ConfigurationError(
|
|
120
|
+
"PCA requires at least two input records.",
|
|
121
|
+
code="INVALID_NEURAL_CLUSTER_PCA",
|
|
122
|
+
)
|
|
123
|
+
maximum = min(int(matrix.shape[0]), int(matrix.shape[1]))
|
|
124
|
+
if pca_components > maximum:
|
|
125
|
+
raise ConfigurationError(
|
|
126
|
+
"pca_components cannot exceed min(input_count, embedding_dimension).",
|
|
127
|
+
code="INVALID_NEURAL_CLUSTER_PCA",
|
|
128
|
+
context={"pca_components": pca_components, "maximum": maximum},
|
|
129
|
+
)
|
|
130
|
+
pca = pca_class(n_components=pca_components, svd_solver="full")
|
|
131
|
+
matrix = pca.fit_transform(matrix)
|
|
132
|
+
explained_variance = tuple(float(value) for value in pca.explained_variance_ratio_)
|
|
133
|
+
|
|
134
|
+
estimator = kmeans_class(
|
|
135
|
+
n_clusters=resolved.n_clusters,
|
|
136
|
+
init="k-means++",
|
|
137
|
+
n_init=resolved.n_init,
|
|
138
|
+
max_iter=resolved.max_iter,
|
|
139
|
+
tol=resolved.tolerance,
|
|
140
|
+
random_state=resolved.seed,
|
|
141
|
+
algorithm="lloyd",
|
|
142
|
+
)
|
|
143
|
+
raw_labels = estimator.fit_predict(matrix)
|
|
144
|
+
raw_groups = {
|
|
145
|
+
cluster_index: tuple(int(index) for index in np.flatnonzero(raw_labels == cluster_index))
|
|
146
|
+
for cluster_index in range(resolved.n_clusters)
|
|
147
|
+
}
|
|
148
|
+
if any(not members for members in raw_groups.values()):
|
|
149
|
+
raise ConfigurationError(
|
|
150
|
+
"k-means returned an empty cluster.",
|
|
151
|
+
code="EMPTY_NEURAL_CLUSTER",
|
|
152
|
+
)
|
|
153
|
+
old_cluster_order = tuple(
|
|
154
|
+
sorted(raw_groups, key=lambda cluster_index: raw_groups[cluster_index][0])
|
|
155
|
+
)
|
|
156
|
+
old_to_new = {old: new for new, old in enumerate(old_cluster_order)}
|
|
157
|
+
labels = tuple(old_to_new[int(label)] for label in raw_labels)
|
|
158
|
+
reordered_centers = estimator.cluster_centers_[list(old_cluster_order)]
|
|
159
|
+
|
|
160
|
+
clusters: list[Cluster] = []
|
|
161
|
+
representatives: list[DNARecord] = []
|
|
162
|
+
for cluster_index, old_cluster_index in enumerate(old_cluster_order):
|
|
163
|
+
members = raw_groups[old_cluster_index]
|
|
164
|
+
center = reordered_centers[cluster_index]
|
|
165
|
+
representative_index = min(
|
|
166
|
+
members,
|
|
167
|
+
key=lambda index: (float(np.linalg.norm(matrix[index] - center)), index),
|
|
168
|
+
)
|
|
169
|
+
representatives.append(materialized[representative_index])
|
|
170
|
+
clusters.append(
|
|
171
|
+
Cluster(
|
|
172
|
+
cluster_index,
|
|
173
|
+
members,
|
|
174
|
+
tuple(materialized[index].id for index in members),
|
|
175
|
+
representative_index,
|
|
176
|
+
materialized[representative_index].id,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
score: float | None = None
|
|
181
|
+
unique_labels = len(set(labels))
|
|
182
|
+
if 1 < unique_labels < len(materialized):
|
|
183
|
+
score_kwargs: dict[str, object] = {"metric": "euclidean"}
|
|
184
|
+
if len(materialized) > resolved.silhouette_sample_size:
|
|
185
|
+
score_kwargs.update(
|
|
186
|
+
{
|
|
187
|
+
"sample_size": resolved.silhouette_sample_size,
|
|
188
|
+
"random_state": resolved.seed,
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
score = float(silhouette_function(matrix, labels, **score_kwargs))
|
|
192
|
+
|
|
193
|
+
centers = tuple(
|
|
194
|
+
tuple(float(value) for value in center)
|
|
195
|
+
for center in np.asarray(reordered_centers, dtype=np.float64)
|
|
196
|
+
)
|
|
197
|
+
return NeuralClusteringResult(
|
|
198
|
+
tuple(clusters),
|
|
199
|
+
labels,
|
|
200
|
+
DNASet(representatives),
|
|
201
|
+
centers,
|
|
202
|
+
representation_result.model_name,
|
|
203
|
+
representation_result.checkpoint_path,
|
|
204
|
+
representation_result.pooling,
|
|
205
|
+
representation_result.embedding_dimension,
|
|
206
|
+
int(matrix.shape[1]),
|
|
207
|
+
resolved.n_clusters,
|
|
208
|
+
float(estimator.inertia_),
|
|
209
|
+
score,
|
|
210
|
+
resolved.normalize,
|
|
211
|
+
pca_components,
|
|
212
|
+
explained_variance,
|
|
213
|
+
resolved.seed,
|
|
214
|
+
resolved.n_init,
|
|
215
|
+
resolved.max_iter,
|
|
216
|
+
int(estimator.n_iter_),
|
|
217
|
+
len(materialized),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
__all__ = ["neural_cluster_sequences"]
|