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,381 @@
|
|
|
1
|
+
"""DNA adaptations of the MOSES Frag and SNN generative-set metrics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections import Counter
|
|
7
|
+
from collections.abc import Iterable, Mapping
|
|
8
|
+
from typing import TypeVar, cast
|
|
9
|
+
|
|
10
|
+
from dnakit.core import DNARecord
|
|
11
|
+
from dnakit.core._json import FrozenDict
|
|
12
|
+
from dnakit.descriptors import kmer_statistics
|
|
13
|
+
from dnakit.exceptions import ConfigurationError
|
|
14
|
+
from dnakit.fingerprints import hashed_kmer_fingerprint
|
|
15
|
+
|
|
16
|
+
from ._shared import (
|
|
17
|
+
EvaluationInput,
|
|
18
|
+
enforce_pair_limit,
|
|
19
|
+
materialize_input,
|
|
20
|
+
pair_count,
|
|
21
|
+
record_for,
|
|
22
|
+
report,
|
|
23
|
+
require_nonempty,
|
|
24
|
+
)
|
|
25
|
+
from .config import EvaluationLimits, FragmentSimilarityConfig, SNNConfig
|
|
26
|
+
from .results import EvaluationEntry, EvaluationReport
|
|
27
|
+
|
|
28
|
+
_T = TypeVar("_T")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _tracked(
|
|
32
|
+
items: tuple[_T, ...],
|
|
33
|
+
*,
|
|
34
|
+
enabled: bool,
|
|
35
|
+
description: str,
|
|
36
|
+
) -> Iterable[_T]:
|
|
37
|
+
if not enabled:
|
|
38
|
+
return items
|
|
39
|
+
from rich.progress import track
|
|
40
|
+
|
|
41
|
+
return cast(Iterable[_T], track(items, description=description, total=len(items)))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _materialize_collections(
|
|
45
|
+
generated: EvaluationInput,
|
|
46
|
+
reference: EvaluationInput,
|
|
47
|
+
*,
|
|
48
|
+
operation: str,
|
|
49
|
+
limits: EvaluationLimits,
|
|
50
|
+
) -> tuple[tuple[DNARecord, ...], tuple[DNARecord, ...]]:
|
|
51
|
+
generated_items = materialize_input(generated, limits=limits)
|
|
52
|
+
reference_items = materialize_input(reference, limits=limits)
|
|
53
|
+
require_nonempty(generated_items, operation)
|
|
54
|
+
require_nonempty(reference_items, operation)
|
|
55
|
+
total_symbols = sum(item.sequence.symbol_length for item in generated_items + reference_items)
|
|
56
|
+
if total_symbols > limits.max_total_symbols:
|
|
57
|
+
raise ConfigurationError(
|
|
58
|
+
f"Combined {operation} inputs exceed max_total_symbols.",
|
|
59
|
+
code="EVALUATION_SYMBOL_LIMIT",
|
|
60
|
+
context={
|
|
61
|
+
"total_symbols": total_symbols,
|
|
62
|
+
"max_total_symbols": limits.max_total_symbols,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
return (
|
|
66
|
+
tuple(record_for(item) for item in generated_items),
|
|
67
|
+
tuple(record_for(item) for item in reference_items),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _fragment_counts(
|
|
72
|
+
records: tuple[DNARecord, ...],
|
|
73
|
+
*,
|
|
74
|
+
config: FragmentSimilarityConfig,
|
|
75
|
+
description: str,
|
|
76
|
+
initial_observations: int,
|
|
77
|
+
) -> tuple[Counter[str], int, int]:
|
|
78
|
+
counts: Counter[str] = Counter()
|
|
79
|
+
observations = initial_observations
|
|
80
|
+
ignored_ambiguities = 0
|
|
81
|
+
for record in _tracked(
|
|
82
|
+
records,
|
|
83
|
+
enabled=config.show_progress,
|
|
84
|
+
description=description,
|
|
85
|
+
):
|
|
86
|
+
result = kmer_statistics(
|
|
87
|
+
record,
|
|
88
|
+
config.k,
|
|
89
|
+
canonical=config.canonical,
|
|
90
|
+
ambiguity_policy=config.ambiguity_policy,
|
|
91
|
+
cross_gaps=False,
|
|
92
|
+
)
|
|
93
|
+
observations += result.denominator
|
|
94
|
+
if observations > config.max_kmer_observations:
|
|
95
|
+
raise ConfigurationError(
|
|
96
|
+
"Fragment similarity exceeds max_kmer_observations.",
|
|
97
|
+
code="FRAGMENT_KMER_LIMIT",
|
|
98
|
+
context={
|
|
99
|
+
"observation_count": observations,
|
|
100
|
+
"max_kmer_observations": config.max_kmer_observations,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
counts.update(cast(Mapping[str, int], result.counts))
|
|
104
|
+
ignored_ambiguities += result.ignored_ambiguity_count
|
|
105
|
+
return counts, observations, ignored_ambiguities
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _cosine_counts(left: Counter[str], right: Counter[str]) -> tuple[float, float, float, float]:
|
|
109
|
+
left_squared = math.fsum(value * value for value in left.values())
|
|
110
|
+
right_squared = math.fsum(value * value for value in right.values())
|
|
111
|
+
if left_squared == 0 or right_squared == 0:
|
|
112
|
+
raise ConfigurationError(
|
|
113
|
+
"Fragment similarity requires at least one eligible k-mer in each collection.",
|
|
114
|
+
code="EMPTY_FRAGMENT_VECTOR",
|
|
115
|
+
context={"generated_empty": left_squared == 0, "reference_empty": right_squared == 0},
|
|
116
|
+
)
|
|
117
|
+
shared = set(left) & set(right)
|
|
118
|
+
dot = math.fsum(left[word] * right[word] for word in shared)
|
|
119
|
+
denominator = math.sqrt(left_squared * right_squared)
|
|
120
|
+
similarity = min(1.0, max(0.0, dot / denominator))
|
|
121
|
+
return similarity, dot, left_squared, right_squared
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def evaluate_fragment_similarity(
|
|
125
|
+
generated: EvaluationInput,
|
|
126
|
+
reference: EvaluationInput,
|
|
127
|
+
*,
|
|
128
|
+
config: FragmentSimilarityConfig | None = None,
|
|
129
|
+
) -> EvaluationReport:
|
|
130
|
+
"""Compare exact DNA k-mer count distributions with cosine similarity."""
|
|
131
|
+
|
|
132
|
+
resolved = FragmentSimilarityConfig() if config is None else config
|
|
133
|
+
if not isinstance(resolved, FragmentSimilarityConfig):
|
|
134
|
+
raise ConfigurationError(
|
|
135
|
+
"config must be FragmentSimilarityConfig or None.",
|
|
136
|
+
code="INVALID_FRAGMENT_SIMILARITY_CONFIG",
|
|
137
|
+
)
|
|
138
|
+
generated_records, reference_records = _materialize_collections(
|
|
139
|
+
generated,
|
|
140
|
+
reference,
|
|
141
|
+
operation="fragment similarity",
|
|
142
|
+
limits=resolved.limits,
|
|
143
|
+
)
|
|
144
|
+
generated_counts, generated_observations, generated_ignored = _fragment_counts(
|
|
145
|
+
generated_records,
|
|
146
|
+
config=resolved,
|
|
147
|
+
description="Frag generated",
|
|
148
|
+
initial_observations=0,
|
|
149
|
+
)
|
|
150
|
+
reference_counts, total_observations, reference_ignored = _fragment_counts(
|
|
151
|
+
reference_records,
|
|
152
|
+
config=resolved,
|
|
153
|
+
description="Frag reference",
|
|
154
|
+
initial_observations=generated_observations,
|
|
155
|
+
)
|
|
156
|
+
similarity, dot, generated_squared, reference_squared = _cosine_counts(
|
|
157
|
+
generated_counts,
|
|
158
|
+
reference_counts,
|
|
159
|
+
)
|
|
160
|
+
shared_fragments = len(set(generated_counts) & set(reference_counts))
|
|
161
|
+
union_fragments = len(set(generated_counts) | set(reference_counts))
|
|
162
|
+
return report(
|
|
163
|
+
name="fragment_similarity",
|
|
164
|
+
method="cosine-similarity-of-exact-dna-kmer-counts",
|
|
165
|
+
version="eval-frag-dna-v1",
|
|
166
|
+
parameters={
|
|
167
|
+
"formula": "sum_f(c_G(f)*c_R(f)) / sqrt(sum_f(c_G(f)^2)*sum_f(c_R(f)^2))",
|
|
168
|
+
"molecular_analog": "MOSES Frag over BRICS fragment count distributions",
|
|
169
|
+
"dna_fragment_definition": "overlapping fixed-length A/C/G/T k-mers",
|
|
170
|
+
"k": resolved.k,
|
|
171
|
+
"canonical": resolved.canonical,
|
|
172
|
+
"overlapping": True,
|
|
173
|
+
"ambiguity_policy": resolved.ambiguity_policy,
|
|
174
|
+
"gap_policy": "k-mers never cross explicit Gap objects",
|
|
175
|
+
"topology_policy": "linearized at stored origin; no circular-origin wrap",
|
|
176
|
+
"higher_is_better": True,
|
|
177
|
+
"bounds": (0.0, 1.0),
|
|
178
|
+
"max_kmer_observations": resolved.max_kmer_observations,
|
|
179
|
+
"show_progress": resolved.show_progress,
|
|
180
|
+
"limits": resolved.limits,
|
|
181
|
+
"inference": (
|
|
182
|
+
"DNA k-mer adaptation; not BRICS Frag and not an experimentally "
|
|
183
|
+
"validated DNA quality score"
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
metrics={
|
|
187
|
+
"score": similarity,
|
|
188
|
+
"frag": similarity,
|
|
189
|
+
"fragment_similarity": similarity,
|
|
190
|
+
"dot_product": dot,
|
|
191
|
+
"generated_squared_norm": generated_squared,
|
|
192
|
+
"reference_squared_norm": reference_squared,
|
|
193
|
+
"generated_count": len(generated_records),
|
|
194
|
+
"reference_count": len(reference_records),
|
|
195
|
+
"generated_fragment_observations": generated_observations,
|
|
196
|
+
"reference_fragment_observations": total_observations - generated_observations,
|
|
197
|
+
"generated_unique_fragments": len(generated_counts),
|
|
198
|
+
"reference_unique_fragments": len(reference_counts),
|
|
199
|
+
"shared_unique_fragments": shared_fragments,
|
|
200
|
+
"union_unique_fragments": union_fragments,
|
|
201
|
+
"generated_ignored_ambiguity_count": generated_ignored,
|
|
202
|
+
"reference_ignored_ambiguity_count": reference_ignored,
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _bit_fingerprints(
|
|
208
|
+
records: tuple[DNARecord, ...],
|
|
209
|
+
*,
|
|
210
|
+
config: SNNConfig,
|
|
211
|
+
description: str,
|
|
212
|
+
) -> tuple[tuple[frozenset[str], ...], tuple[int, ...], int]:
|
|
213
|
+
fingerprints: list[frozenset[str]] = []
|
|
214
|
+
observation_counts: list[int] = []
|
|
215
|
+
ignored_ambiguities = 0
|
|
216
|
+
for record in _tracked(
|
|
217
|
+
records,
|
|
218
|
+
enabled=config.show_progress,
|
|
219
|
+
description=description,
|
|
220
|
+
):
|
|
221
|
+
fingerprint = hashed_kmer_fingerprint(
|
|
222
|
+
record,
|
|
223
|
+
k=config.k,
|
|
224
|
+
n_bits=config.n_bits,
|
|
225
|
+
canonical=config.canonical,
|
|
226
|
+
seed=config.seed,
|
|
227
|
+
representation="sparse",
|
|
228
|
+
ambiguity_policy=config.ambiguity_policy,
|
|
229
|
+
cross_gaps=False,
|
|
230
|
+
)
|
|
231
|
+
fingerprints.append(frozenset(fingerprint.sparse_values()))
|
|
232
|
+
observation_counts.append(fingerprint.observation_count)
|
|
233
|
+
ignored_ambiguities += fingerprint.ignored_ambiguity_count
|
|
234
|
+
return tuple(fingerprints), tuple(observation_counts), ignored_ambiguities
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _binary_tanimoto(left: frozenset[str], right: frozenset[str]) -> float:
|
|
238
|
+
union_size = len(left | right)
|
|
239
|
+
if union_size == 0:
|
|
240
|
+
return 1.0
|
|
241
|
+
return len(left & right) / union_size
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def evaluate_snn(
|
|
245
|
+
generated: EvaluationInput,
|
|
246
|
+
reference: EvaluationInput,
|
|
247
|
+
*,
|
|
248
|
+
config: SNNConfig | None = None,
|
|
249
|
+
) -> EvaluationReport:
|
|
250
|
+
"""Average each generated sequence's nearest-reference Tanimoto similarity."""
|
|
251
|
+
|
|
252
|
+
resolved = SNNConfig() if config is None else config
|
|
253
|
+
if not isinstance(resolved, SNNConfig):
|
|
254
|
+
raise ConfigurationError(
|
|
255
|
+
"config must be SNNConfig or None.",
|
|
256
|
+
code="INVALID_SNN_CONFIG",
|
|
257
|
+
)
|
|
258
|
+
generated_records, reference_records = _materialize_collections(
|
|
259
|
+
generated,
|
|
260
|
+
reference,
|
|
261
|
+
operation="SNN",
|
|
262
|
+
limits=resolved.limits,
|
|
263
|
+
)
|
|
264
|
+
comparisons = pair_count(len(generated_records), len(reference_records))
|
|
265
|
+
enforce_pair_limit(comparisons, resolved.limits)
|
|
266
|
+
fingerprint_elements = (len(generated_records) + len(reference_records)) * resolved.n_bits
|
|
267
|
+
if fingerprint_elements > resolved.max_fingerprint_elements:
|
|
268
|
+
raise ConfigurationError(
|
|
269
|
+
"SNN exceeds max_fingerprint_elements.",
|
|
270
|
+
code="SNN_FINGERPRINT_LIMIT",
|
|
271
|
+
context={
|
|
272
|
+
"fingerprint_elements": fingerprint_elements,
|
|
273
|
+
"max_fingerprint_elements": resolved.max_fingerprint_elements,
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
generated_fingerprints, generated_observations, generated_ignored = _bit_fingerprints(
|
|
277
|
+
generated_records,
|
|
278
|
+
config=resolved,
|
|
279
|
+
description="SNN generated fingerprints",
|
|
280
|
+
)
|
|
281
|
+
reference_fingerprints, reference_observations, reference_ignored = _bit_fingerprints(
|
|
282
|
+
reference_records,
|
|
283
|
+
config=resolved,
|
|
284
|
+
description="SNN reference fingerprints",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
entries: list[EvaluationEntry] = []
|
|
288
|
+
nearest_values: list[float] = []
|
|
289
|
+
generated_pairs = tuple(zip(generated_records, generated_fingerprints, strict=True))
|
|
290
|
+
for generated_index, (record, fingerprint) in enumerate(
|
|
291
|
+
_tracked(
|
|
292
|
+
generated_pairs,
|
|
293
|
+
enabled=resolved.show_progress,
|
|
294
|
+
description="SNN nearest neighbors",
|
|
295
|
+
)
|
|
296
|
+
):
|
|
297
|
+
best_index = 0
|
|
298
|
+
best_similarity = -1.0
|
|
299
|
+
for reference_index, reference_fingerprint in enumerate(reference_fingerprints):
|
|
300
|
+
similarity = _binary_tanimoto(fingerprint, reference_fingerprint)
|
|
301
|
+
if similarity > best_similarity:
|
|
302
|
+
best_index = reference_index
|
|
303
|
+
best_similarity = similarity
|
|
304
|
+
nearest_values.append(best_similarity)
|
|
305
|
+
nearest_fingerprint = reference_fingerprints[best_index]
|
|
306
|
+
shared_bit_count = len(fingerprint & nearest_fingerprint)
|
|
307
|
+
union_bit_count = len(fingerprint | nearest_fingerprint)
|
|
308
|
+
entries.append(
|
|
309
|
+
EvaluationEntry(
|
|
310
|
+
record.id,
|
|
311
|
+
generated_index,
|
|
312
|
+
FrozenDict(
|
|
313
|
+
{
|
|
314
|
+
"nearest_reference_id": reference_records[best_index].id,
|
|
315
|
+
"nearest_reference_index": best_index,
|
|
316
|
+
"nearest_similarity": best_similarity,
|
|
317
|
+
"generated_set_bit_count": len(fingerprint),
|
|
318
|
+
"nearest_reference_set_bit_count": len(nearest_fingerprint),
|
|
319
|
+
"nearest_shared_bit_count": shared_bit_count,
|
|
320
|
+
"nearest_union_bit_count": union_bit_count,
|
|
321
|
+
}
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
snn = math.fsum(nearest_values) / len(nearest_values)
|
|
326
|
+
return report(
|
|
327
|
+
name="similarity_to_nearest_neighbor",
|
|
328
|
+
method="mean-nearest-binary-tanimoto-over-hashed-dna-kmer-fingerprints",
|
|
329
|
+
version="eval-snn-dna-v1",
|
|
330
|
+
parameters={
|
|
331
|
+
"formula": "mean_g max_r Tanimoto(fp(g), fp(r))",
|
|
332
|
+
"molecular_analog": (
|
|
333
|
+
"MOSES SNN over radius-2 Morgan fingerprints (1024 bits in the paper)"
|
|
334
|
+
),
|
|
335
|
+
"dna_fingerprint": "SHA-256 hashed binary k-mer fingerprint",
|
|
336
|
+
"k": resolved.k,
|
|
337
|
+
"n_bits": resolved.n_bits,
|
|
338
|
+
"canonical": resolved.canonical,
|
|
339
|
+
"overlapping": True,
|
|
340
|
+
"seed": resolved.seed,
|
|
341
|
+
"ambiguity_policy": resolved.ambiguity_policy,
|
|
342
|
+
"gap_policy": "fingerprint k-mers never cross explicit Gap objects",
|
|
343
|
+
"topology_policy": "linearized at stored origin; no circular-origin wrap",
|
|
344
|
+
"zero_vector_policy": "two empty fingerprints are identical; one empty is dissimilar",
|
|
345
|
+
"tie_break": "reference-input-index-ascending",
|
|
346
|
+
"higher_is_better": True,
|
|
347
|
+
"bounds": (0.0, 1.0),
|
|
348
|
+
"max_fingerprint_elements": resolved.max_fingerprint_elements,
|
|
349
|
+
"show_progress": resolved.show_progress,
|
|
350
|
+
"limits": resolved.limits,
|
|
351
|
+
"inference": (
|
|
352
|
+
"DNA k-mer fingerprint adaptation; not Morgan-fingerprint SNN and not an "
|
|
353
|
+
"experimentally validated DNA quality score"
|
|
354
|
+
),
|
|
355
|
+
},
|
|
356
|
+
metrics={
|
|
357
|
+
"score": snn,
|
|
358
|
+
"snn": snn,
|
|
359
|
+
"mean_nearest_similarity": snn,
|
|
360
|
+
"minimum_nearest_similarity": min(nearest_values),
|
|
361
|
+
"maximum_nearest_similarity": max(nearest_values),
|
|
362
|
+
"generated_count": len(generated_records),
|
|
363
|
+
"reference_count": len(reference_records),
|
|
364
|
+
"pairwise_comparison_count": comparisons,
|
|
365
|
+
"fingerprint_elements": fingerprint_elements,
|
|
366
|
+
"generated_zero_fingerprint_count": sum(
|
|
367
|
+
not fingerprint for fingerprint in generated_fingerprints
|
|
368
|
+
),
|
|
369
|
+
"reference_zero_fingerprint_count": sum(
|
|
370
|
+
not fingerprint for fingerprint in reference_fingerprints
|
|
371
|
+
),
|
|
372
|
+
"generated_kmer_observation_count": sum(generated_observations),
|
|
373
|
+
"reference_kmer_observation_count": sum(reference_observations),
|
|
374
|
+
"generated_ignored_ambiguity_count": generated_ignored,
|
|
375
|
+
"reference_ignored_ambiguity_count": reference_ignored,
|
|
376
|
+
},
|
|
377
|
+
entries=entries,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
__all__ = ["evaluate_fragment_similarity", "evaluate_snn"]
|