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,676 @@
|
|
|
1
|
+
"""Immutable structured results for native similarity operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal, TypeAlias, cast
|
|
9
|
+
|
|
10
|
+
from dnakit.core import Provenance, Strand
|
|
11
|
+
from dnakit.core._json import FrozenDict, to_json_compatible
|
|
12
|
+
from dnakit.exceptions import ConfigurationError
|
|
13
|
+
|
|
14
|
+
DistanceKind: TypeAlias = Literal["hamming", "levenshtein"]
|
|
15
|
+
EditOperation: TypeAlias = Literal["match", "substitute", "insert", "delete"]
|
|
16
|
+
ValueKind: TypeAlias = Literal["similarity", "distance"]
|
|
17
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _SerializableResult:
|
|
21
|
+
def to_dict(self) -> dict[str, Any]:
|
|
22
|
+
"""Return a plain JSON-compatible representation."""
|
|
23
|
+
|
|
24
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _non_negative_int(value: object, name: str) -> None:
|
|
28
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
29
|
+
raise ConfigurationError(f"{name} must be a non-negative integer.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _optional_identifier(value: str | None, name: str) -> None:
|
|
33
|
+
if value is not None and (not isinstance(value, str) or not value.strip()):
|
|
34
|
+
raise ConfigurationError(f"{name} must be a non-empty string or None.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class SequenceMatch(_SerializableResult):
|
|
39
|
+
"""One exact hit in target 0-based, half-open coordinates."""
|
|
40
|
+
|
|
41
|
+
target_index: int
|
|
42
|
+
target_id: str | None
|
|
43
|
+
start: int
|
|
44
|
+
end: int
|
|
45
|
+
strand: Strand
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
_non_negative_int(self.target_index, "target_index")
|
|
49
|
+
_optional_identifier(self.target_id, "target_id")
|
|
50
|
+
_non_negative_int(self.start, "start")
|
|
51
|
+
_non_negative_int(self.end, "end")
|
|
52
|
+
if self.end < self.start:
|
|
53
|
+
raise ConfigurationError("SequenceMatch end cannot be smaller than start.")
|
|
54
|
+
if not isinstance(self.strand, Strand):
|
|
55
|
+
raise ConfigurationError("SequenceMatch strand must be Strand.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ApproximateMatch(SequenceMatch):
|
|
60
|
+
"""One approximate substring hit with its weighted edit distance."""
|
|
61
|
+
|
|
62
|
+
distance: float
|
|
63
|
+
|
|
64
|
+
def __post_init__(self) -> None:
|
|
65
|
+
super().__post_init__()
|
|
66
|
+
if (
|
|
67
|
+
isinstance(self.distance, bool)
|
|
68
|
+
or not isinstance(self.distance, (int, float))
|
|
69
|
+
or not math.isfinite(self.distance)
|
|
70
|
+
or self.distance < 0
|
|
71
|
+
):
|
|
72
|
+
raise ConfigurationError("Approximate match distance must be non-negative and finite.")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class SearchResult(_SerializableResult):
|
|
77
|
+
"""Exact full-sequence or exact-subsequence search result."""
|
|
78
|
+
|
|
79
|
+
name: str
|
|
80
|
+
method: str
|
|
81
|
+
query_id: str | None
|
|
82
|
+
query_length: int
|
|
83
|
+
target_count: int
|
|
84
|
+
matches: tuple[SequenceMatch, ...]
|
|
85
|
+
overlapping: bool
|
|
86
|
+
reverse_complement: bool
|
|
87
|
+
merge_strands: bool
|
|
88
|
+
full_length: bool
|
|
89
|
+
max_targets: int
|
|
90
|
+
max_matches: int
|
|
91
|
+
iupac_matching: Literal["literal"] = "literal"
|
|
92
|
+
coordinate_system: Literal["0-based-half-open"] = "0-based-half-open"
|
|
93
|
+
circular_wrap: bool = False
|
|
94
|
+
|
|
95
|
+
def __post_init__(self) -> None:
|
|
96
|
+
for name in ("name", "method"):
|
|
97
|
+
value = getattr(self, name)
|
|
98
|
+
if not isinstance(value, str) or not value.strip():
|
|
99
|
+
raise ConfigurationError(f"SearchResult {name} must be non-empty.")
|
|
100
|
+
_optional_identifier(self.query_id, "query_id")
|
|
101
|
+
_non_negative_int(self.query_length, "query_length")
|
|
102
|
+
_non_negative_int(self.target_count, "target_count")
|
|
103
|
+
if any(not isinstance(match, SequenceMatch) for match in self.matches):
|
|
104
|
+
raise ConfigurationError("SearchResult matches must all be SequenceMatch objects.")
|
|
105
|
+
if any(match.target_index >= self.target_count for match in self.matches):
|
|
106
|
+
raise ConfigurationError("A search match target_index is outside target_count.")
|
|
107
|
+
for name in ("overlapping", "reverse_complement", "merge_strands", "full_length"):
|
|
108
|
+
if not isinstance(getattr(self, name), bool):
|
|
109
|
+
raise ConfigurationError(f"SearchResult {name} must be boolean.")
|
|
110
|
+
for name in ("max_targets", "max_matches"):
|
|
111
|
+
value = getattr(self, name)
|
|
112
|
+
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
|
|
113
|
+
raise ConfigurationError(f"SearchResult {name} must be a positive integer.")
|
|
114
|
+
if self.target_count > self.max_targets:
|
|
115
|
+
raise ConfigurationError("SearchResult target_count cannot exceed max_targets.")
|
|
116
|
+
if len(self.matches) > self.max_matches:
|
|
117
|
+
raise ConfigurationError("SearchResult matches cannot exceed max_matches.")
|
|
118
|
+
if self.iupac_matching != "literal":
|
|
119
|
+
raise ConfigurationError("Only literal IUPAC matching is implemented in the MVP.")
|
|
120
|
+
if self.coordinate_system != "0-based-half-open":
|
|
121
|
+
raise ConfigurationError("Internal search coordinates must be 0-based half-open.")
|
|
122
|
+
if self.circular_wrap:
|
|
123
|
+
raise ConfigurationError("Circular origin-wrapping search is not implemented in MVP.")
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def found(self) -> bool:
|
|
127
|
+
return bool(self.matches)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class ApproximateSearchResult(_SerializableResult):
|
|
132
|
+
"""Audited collection of bounded approximate substring matches."""
|
|
133
|
+
|
|
134
|
+
name: str
|
|
135
|
+
method: str
|
|
136
|
+
algorithm_version: str
|
|
137
|
+
query_id: str | None
|
|
138
|
+
query_length: int
|
|
139
|
+
target_count: int
|
|
140
|
+
matches: tuple[ApproximateMatch, ...]
|
|
141
|
+
max_distance: float
|
|
142
|
+
reverse_complement: bool
|
|
143
|
+
max_targets: int
|
|
144
|
+
max_matches: int
|
|
145
|
+
max_cells: int
|
|
146
|
+
dp_cells: int
|
|
147
|
+
parameters: FrozenDict
|
|
148
|
+
|
|
149
|
+
def __post_init__(self) -> None:
|
|
150
|
+
for name in ("name", "method", "algorithm_version"):
|
|
151
|
+
if not isinstance(getattr(self, name), str) or not getattr(self, name).strip():
|
|
152
|
+
raise ConfigurationError(f"ApproximateSearchResult {name} must be non-empty.")
|
|
153
|
+
_optional_identifier(self.query_id, "query_id")
|
|
154
|
+
for name in ("query_length", "target_count", "dp_cells"):
|
|
155
|
+
_non_negative_int(getattr(self, name), name)
|
|
156
|
+
for name in ("max_targets", "max_matches", "max_cells"):
|
|
157
|
+
value = getattr(self, name)
|
|
158
|
+
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
|
|
159
|
+
raise ConfigurationError(f"{name} must be a positive integer.")
|
|
160
|
+
if any(not isinstance(item, ApproximateMatch) for item in self.matches):
|
|
161
|
+
raise ConfigurationError("matches must contain ApproximateMatch objects.")
|
|
162
|
+
if any(item.target_index >= self.target_count for item in self.matches):
|
|
163
|
+
raise ConfigurationError("Approximate match target index is outside target_count.")
|
|
164
|
+
if any(item.distance > self.max_distance for item in self.matches):
|
|
165
|
+
raise ConfigurationError("Approximate match exceeds max_distance.")
|
|
166
|
+
if self.dp_cells > self.max_cells or len(self.matches) > self.max_matches:
|
|
167
|
+
raise ConfigurationError("Approximate result exceeds a declared resource limit.")
|
|
168
|
+
if not isinstance(self.reverse_complement, bool):
|
|
169
|
+
raise ConfigurationError("reverse_complement must be boolean.")
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def found(self) -> bool:
|
|
173
|
+
return bool(self.matches)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True)
|
|
177
|
+
class Mismatch(_SerializableResult):
|
|
178
|
+
"""One literal symbol mismatch at a shared sequence position."""
|
|
179
|
+
|
|
180
|
+
position: int
|
|
181
|
+
left_symbol: str
|
|
182
|
+
right_symbol: str
|
|
183
|
+
|
|
184
|
+
def __post_init__(self) -> None:
|
|
185
|
+
_non_negative_int(self.position, "position")
|
|
186
|
+
if len(self.left_symbol) != 1 or len(self.right_symbol) != 1:
|
|
187
|
+
raise ConfigurationError("Mismatch symbols must each contain one character.")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class EditStep(_SerializableResult):
|
|
192
|
+
"""One deterministic Levenshtein traceback step."""
|
|
193
|
+
|
|
194
|
+
operation: EditOperation
|
|
195
|
+
left_start: int
|
|
196
|
+
left_end: int
|
|
197
|
+
right_start: int
|
|
198
|
+
right_end: int
|
|
199
|
+
left_symbol: str | None
|
|
200
|
+
right_symbol: str | None
|
|
201
|
+
|
|
202
|
+
def __post_init__(self) -> None:
|
|
203
|
+
if self.operation not in ("match", "substitute", "insert", "delete"):
|
|
204
|
+
raise ConfigurationError("Unknown edit-path operation.")
|
|
205
|
+
for name in ("left_start", "left_end", "right_start", "right_end"):
|
|
206
|
+
_non_negative_int(getattr(self, name), name)
|
|
207
|
+
if self.left_end < self.left_start or self.right_end < self.right_start:
|
|
208
|
+
raise ConfigurationError("EditStep coordinates must be ordered.")
|
|
209
|
+
for name in ("left_symbol", "right_symbol"):
|
|
210
|
+
symbol = getattr(self, name)
|
|
211
|
+
if symbol is not None and (not isinstance(symbol, str) or len(symbol) != 1):
|
|
212
|
+
raise ConfigurationError(f"EditStep {name} must be one character or None.")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True)
|
|
216
|
+
class DistanceResult(_SerializableResult):
|
|
217
|
+
"""Hamming or weighted Levenshtein distance with optional details."""
|
|
218
|
+
|
|
219
|
+
name: str
|
|
220
|
+
method: DistanceKind
|
|
221
|
+
left_id: str | None
|
|
222
|
+
right_id: str | None
|
|
223
|
+
left_length: int
|
|
224
|
+
right_length: int
|
|
225
|
+
distance: float
|
|
226
|
+
mismatches: tuple[Mismatch, ...]
|
|
227
|
+
edit_path: tuple[EditStep, ...] | None
|
|
228
|
+
costs: FrozenDict
|
|
229
|
+
max_distance: float | None
|
|
230
|
+
exceeded_max_distance: bool
|
|
231
|
+
iupac_matching: Literal["literal"] = "literal"
|
|
232
|
+
max_cells: int | None = None
|
|
233
|
+
dp_cells: int | None = None
|
|
234
|
+
|
|
235
|
+
def __post_init__(self) -> None:
|
|
236
|
+
if not isinstance(self.name, str) or not self.name.strip():
|
|
237
|
+
raise ConfigurationError("DistanceResult name must be non-empty.")
|
|
238
|
+
if self.method not in ("hamming", "levenshtein"):
|
|
239
|
+
raise ConfigurationError("Unknown distance method.")
|
|
240
|
+
_optional_identifier(self.left_id, "left_id")
|
|
241
|
+
_optional_identifier(self.right_id, "right_id")
|
|
242
|
+
_non_negative_int(self.left_length, "left_length")
|
|
243
|
+
_non_negative_int(self.right_length, "right_length")
|
|
244
|
+
if (
|
|
245
|
+
isinstance(self.distance, bool)
|
|
246
|
+
or not isinstance(self.distance, (int, float))
|
|
247
|
+
or not math.isfinite(self.distance)
|
|
248
|
+
or self.distance < 0
|
|
249
|
+
):
|
|
250
|
+
raise ConfigurationError("distance must be finite and non-negative.")
|
|
251
|
+
if any(not isinstance(item, Mismatch) for item in self.mismatches):
|
|
252
|
+
raise ConfigurationError("mismatches must contain Mismatch objects.")
|
|
253
|
+
if self.edit_path is not None and any(
|
|
254
|
+
not isinstance(item, EditStep) for item in self.edit_path
|
|
255
|
+
):
|
|
256
|
+
raise ConfigurationError("edit_path must contain EditStep objects or be None.")
|
|
257
|
+
if self.max_distance is not None and (
|
|
258
|
+
isinstance(self.max_distance, bool)
|
|
259
|
+
or not isinstance(self.max_distance, (int, float))
|
|
260
|
+
or not math.isfinite(self.max_distance)
|
|
261
|
+
or self.max_distance < 0
|
|
262
|
+
):
|
|
263
|
+
raise ConfigurationError("max_distance must be finite and non-negative or None.")
|
|
264
|
+
if not isinstance(self.exceeded_max_distance, bool):
|
|
265
|
+
raise ConfigurationError("exceeded_max_distance must be boolean.")
|
|
266
|
+
if self.max_cells is not None and (
|
|
267
|
+
isinstance(self.max_cells, bool)
|
|
268
|
+
or not isinstance(self.max_cells, int)
|
|
269
|
+
or self.max_cells <= 0
|
|
270
|
+
):
|
|
271
|
+
raise ConfigurationError("max_cells must be a positive integer or None.")
|
|
272
|
+
if self.dp_cells is not None and (
|
|
273
|
+
isinstance(self.dp_cells, bool)
|
|
274
|
+
or not isinstance(self.dp_cells, int)
|
|
275
|
+
or self.dp_cells <= 0
|
|
276
|
+
):
|
|
277
|
+
raise ConfigurationError("dp_cells must be a positive integer or None.")
|
|
278
|
+
if (
|
|
279
|
+
self.max_cells is not None
|
|
280
|
+
and self.dp_cells is not None
|
|
281
|
+
and self.dp_cells > self.max_cells
|
|
282
|
+
):
|
|
283
|
+
raise ConfigurationError("dp_cells cannot exceed max_cells in a completed result.")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@dataclass(frozen=True)
|
|
287
|
+
class SimilarityResult(_SerializableResult):
|
|
288
|
+
"""Scalar sequence, k-mer, or fingerprint comparison."""
|
|
289
|
+
|
|
290
|
+
name: str
|
|
291
|
+
method: str
|
|
292
|
+
value: float
|
|
293
|
+
value_kind: ValueKind
|
|
294
|
+
left_id: str | None
|
|
295
|
+
right_id: str | None
|
|
296
|
+
left_dimension: int
|
|
297
|
+
right_dimension: int
|
|
298
|
+
parameters: FrozenDict
|
|
299
|
+
components: FrozenDict
|
|
300
|
+
zero_vector_policy: str | None
|
|
301
|
+
iupac_matching: Literal["literal"] | None
|
|
302
|
+
|
|
303
|
+
def __post_init__(self) -> None:
|
|
304
|
+
for name in ("name", "method"):
|
|
305
|
+
value = getattr(self, name)
|
|
306
|
+
if not isinstance(value, str) or not value.strip():
|
|
307
|
+
raise ConfigurationError(f"SimilarityResult {name} must be non-empty.")
|
|
308
|
+
if self.value_kind not in ("similarity", "distance"):
|
|
309
|
+
raise ConfigurationError("value_kind must be 'similarity' or 'distance'.")
|
|
310
|
+
if (
|
|
311
|
+
isinstance(self.value, bool)
|
|
312
|
+
or not isinstance(self.value, (int, float))
|
|
313
|
+
or not math.isfinite(self.value)
|
|
314
|
+
):
|
|
315
|
+
raise ConfigurationError("SimilarityResult value must be finite.")
|
|
316
|
+
if self.value_kind == "distance" and self.value < 0:
|
|
317
|
+
raise ConfigurationError("A distance value cannot be negative.")
|
|
318
|
+
if self.value_kind == "similarity" and not -1 <= self.value <= 1:
|
|
319
|
+
raise ConfigurationError("A similarity value must be in [-1, 1].")
|
|
320
|
+
_optional_identifier(self.left_id, "left_id")
|
|
321
|
+
_optional_identifier(self.right_id, "right_id")
|
|
322
|
+
_non_negative_int(self.left_dimension, "left_dimension")
|
|
323
|
+
_non_negative_int(self.right_dimension, "right_dimension")
|
|
324
|
+
if self.zero_vector_policy is not None and (
|
|
325
|
+
not isinstance(self.zero_vector_policy, str) or not self.zero_vector_policy.strip()
|
|
326
|
+
):
|
|
327
|
+
raise ConfigurationError("zero_vector_policy must be non-empty or None.")
|
|
328
|
+
if self.iupac_matching not in (None, "literal"):
|
|
329
|
+
raise ConfigurationError("Only literal IUPAC matching is implemented in the MVP.")
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass(frozen=True)
|
|
333
|
+
class SimilarityMatrixResult(_SerializableResult):
|
|
334
|
+
"""Bounded in-memory dense pairwise matrix."""
|
|
335
|
+
|
|
336
|
+
name: str
|
|
337
|
+
method: str
|
|
338
|
+
value_kind: ValueKind
|
|
339
|
+
labels: tuple[str, ...]
|
|
340
|
+
values: tuple[tuple[float, ...], ...]
|
|
341
|
+
symmetric: bool
|
|
342
|
+
max_items: int
|
|
343
|
+
parameters: FrozenDict
|
|
344
|
+
|
|
345
|
+
def __post_init__(self) -> None:
|
|
346
|
+
if not isinstance(self.name, str) or not self.name.strip():
|
|
347
|
+
raise ConfigurationError("SimilarityMatrixResult name must be non-empty.")
|
|
348
|
+
if not isinstance(self.method, str) or not self.method.strip():
|
|
349
|
+
raise ConfigurationError("SimilarityMatrixResult method must be non-empty.")
|
|
350
|
+
if self.value_kind not in ("similarity", "distance"):
|
|
351
|
+
raise ConfigurationError("Matrix value_kind is invalid.")
|
|
352
|
+
if any(not isinstance(label, str) or not label for label in self.labels):
|
|
353
|
+
raise ConfigurationError("Matrix labels must be non-empty strings.")
|
|
354
|
+
_non_negative_int(self.max_items, "max_items")
|
|
355
|
+
size = len(self.labels)
|
|
356
|
+
if len(self.values) != size or any(len(row) != size for row in self.values):
|
|
357
|
+
raise ConfigurationError("Matrix values must be square and match labels.")
|
|
358
|
+
if any(
|
|
359
|
+
isinstance(value, bool)
|
|
360
|
+
or not isinstance(value, (int, float))
|
|
361
|
+
or not math.isfinite(value)
|
|
362
|
+
or (self.value_kind == "distance" and value < 0)
|
|
363
|
+
or (self.value_kind == "similarity" and not -1 <= value <= 1)
|
|
364
|
+
for row in self.values
|
|
365
|
+
for value in row
|
|
366
|
+
):
|
|
367
|
+
raise ConfigurationError("Matrix contains an invalid value.")
|
|
368
|
+
if self.symmetric and any(
|
|
369
|
+
not math.isclose(self.values[i][j], self.values[j][i])
|
|
370
|
+
for i in range(size)
|
|
371
|
+
for j in range(i)
|
|
372
|
+
):
|
|
373
|
+
raise ConfigurationError("Matrix declared symmetric but values are asymmetric.")
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def item_count(self) -> int:
|
|
377
|
+
return len(self.labels)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _validate_dashing_common(
|
|
381
|
+
algorithm_version: str,
|
|
382
|
+
input_sha256: tuple[str, ...],
|
|
383
|
+
provenance: Provenance,
|
|
384
|
+
raw_output_path: str | None,
|
|
385
|
+
raw_output_sha256: str,
|
|
386
|
+
) -> None:
|
|
387
|
+
if not isinstance(algorithm_version, str) or not algorithm_version.strip():
|
|
388
|
+
raise ConfigurationError("Dashing algorithm_version must be non-empty.")
|
|
389
|
+
if not input_sha256 or any(not _SHA256.fullmatch(value) for value in input_sha256):
|
|
390
|
+
raise ConfigurationError("Dashing input_sha256 must contain lowercase SHA-256 digests.")
|
|
391
|
+
if not isinstance(provenance, Provenance):
|
|
392
|
+
raise ConfigurationError("Dashing provenance must be Provenance.")
|
|
393
|
+
_optional_identifier(raw_output_path, "raw_output_path")
|
|
394
|
+
if not _SHA256.fullmatch(raw_output_sha256):
|
|
395
|
+
raise ConfigurationError("Dashing raw_output_sha256 must be a lowercase SHA-256 digest.")
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@dataclass(frozen=True)
|
|
399
|
+
class DashingJaccardMatrixResult(_SerializableResult):
|
|
400
|
+
"""Validated symmetric Jaccard matrix emitted by a user-supplied Dashing CLI."""
|
|
401
|
+
|
|
402
|
+
name: str
|
|
403
|
+
method: Literal["exact-kmer-set", "hll-sketch"]
|
|
404
|
+
algorithm_version: str
|
|
405
|
+
labels: tuple[str, ...]
|
|
406
|
+
values: tuple[tuple[float, ...], ...]
|
|
407
|
+
input_sha256: tuple[str, ...]
|
|
408
|
+
parameters: FrozenDict
|
|
409
|
+
provenance: Provenance
|
|
410
|
+
raw_output_path: str | None
|
|
411
|
+
raw_output_sha256: str
|
|
412
|
+
|
|
413
|
+
def __post_init__(self) -> None:
|
|
414
|
+
if not isinstance(self.name, str) or not self.name.strip():
|
|
415
|
+
raise ConfigurationError("Dashing matrix name must be non-empty.")
|
|
416
|
+
if self.method not in {"exact-kmer-set", "hll-sketch"}:
|
|
417
|
+
raise ConfigurationError("Dashing matrix method is invalid.")
|
|
418
|
+
if len(self.labels) < 2 or any(
|
|
419
|
+
not isinstance(label, str) or not label for label in self.labels
|
|
420
|
+
):
|
|
421
|
+
raise ConfigurationError("Dashing matrix requires at least two non-empty labels.")
|
|
422
|
+
size = len(self.labels)
|
|
423
|
+
if len(self.values) != size or any(len(row) != size for row in self.values):
|
|
424
|
+
raise ConfigurationError("Dashing matrix values must be square and match labels.")
|
|
425
|
+
if len(self.input_sha256) != size:
|
|
426
|
+
raise ConfigurationError("Dashing matrix checksums must match labels.")
|
|
427
|
+
if any(
|
|
428
|
+
isinstance(value, bool)
|
|
429
|
+
or not isinstance(value, (int, float))
|
|
430
|
+
or not math.isfinite(value)
|
|
431
|
+
or not 0 <= value <= 1
|
|
432
|
+
for row in self.values
|
|
433
|
+
for value in row
|
|
434
|
+
):
|
|
435
|
+
raise ConfigurationError("Dashing matrix contains an invalid Jaccard value.")
|
|
436
|
+
if any(not math.isclose(self.values[index][index], 1.0) for index in range(size)):
|
|
437
|
+
raise ConfigurationError("Dashing Jaccard matrix diagonal must equal one.")
|
|
438
|
+
if any(
|
|
439
|
+
not math.isclose(self.values[left][right], self.values[right][left])
|
|
440
|
+
for left in range(size)
|
|
441
|
+
for right in range(left)
|
|
442
|
+
):
|
|
443
|
+
raise ConfigurationError("Dashing Jaccard matrix must be symmetric.")
|
|
444
|
+
_validate_dashing_common(
|
|
445
|
+
self.algorithm_version,
|
|
446
|
+
self.input_sha256,
|
|
447
|
+
self.provenance,
|
|
448
|
+
self.raw_output_path,
|
|
449
|
+
self.raw_output_sha256,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
@property
|
|
453
|
+
def item_count(self) -> int:
|
|
454
|
+
return len(self.labels)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@dataclass(frozen=True)
|
|
458
|
+
class DashingNeighborHit(_SerializableResult):
|
|
459
|
+
"""One deterministic neighbor selected from a validated Dashing matrix."""
|
|
460
|
+
|
|
461
|
+
label: str
|
|
462
|
+
index: int
|
|
463
|
+
jaccard: float
|
|
464
|
+
|
|
465
|
+
def __post_init__(self) -> None:
|
|
466
|
+
if not isinstance(self.label, str) or not self.label:
|
|
467
|
+
raise ConfigurationError("Dashing neighbor label must be non-empty.")
|
|
468
|
+
_non_negative_int(self.index, "index")
|
|
469
|
+
if (
|
|
470
|
+
isinstance(self.jaccard, bool)
|
|
471
|
+
or not isinstance(self.jaccard, (int, float))
|
|
472
|
+
or not math.isfinite(self.jaccard)
|
|
473
|
+
or not 0 <= self.jaccard <= 1
|
|
474
|
+
):
|
|
475
|
+
raise ConfigurationError("Dashing neighbor Jaccard must be finite in [0, 1].")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@dataclass(frozen=True)
|
|
479
|
+
class DashingNeighborRow(_SerializableResult):
|
|
480
|
+
"""Top-k neighbors for one matrix row."""
|
|
481
|
+
|
|
482
|
+
query_label: str
|
|
483
|
+
query_index: int
|
|
484
|
+
hits: tuple[DashingNeighborHit, ...]
|
|
485
|
+
|
|
486
|
+
def __post_init__(self) -> None:
|
|
487
|
+
if not isinstance(self.query_label, str) or not self.query_label:
|
|
488
|
+
raise ConfigurationError("Dashing query label must be non-empty.")
|
|
489
|
+
_non_negative_int(self.query_index, "query_index")
|
|
490
|
+
if any(not isinstance(hit, DashingNeighborHit) for hit in self.hits):
|
|
491
|
+
raise ConfigurationError("Dashing hits must contain DashingNeighborHit objects.")
|
|
492
|
+
if any(hit.index == self.query_index for hit in self.hits):
|
|
493
|
+
raise ConfigurationError("Dashing Top-k rows cannot contain the query itself.")
|
|
494
|
+
expected = tuple(sorted(self.hits, key=lambda hit: (-hit.jaccard, hit.index)))
|
|
495
|
+
if self.hits != expected:
|
|
496
|
+
raise ConfigurationError("Dashing Top-k hits must use deterministic score/index order.")
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@dataclass(frozen=True)
|
|
500
|
+
class DashingTopKResult(_SerializableResult):
|
|
501
|
+
"""Per-item Top-k neighbors derived from a validated Dashing Jaccard matrix."""
|
|
502
|
+
|
|
503
|
+
name: str
|
|
504
|
+
method: Literal["exact-kmer-set", "hll-sketch"]
|
|
505
|
+
algorithm_version: str
|
|
506
|
+
labels: tuple[str, ...]
|
|
507
|
+
rows: tuple[DashingNeighborRow, ...]
|
|
508
|
+
top_k: int
|
|
509
|
+
input_sha256: tuple[str, ...]
|
|
510
|
+
parameters: FrozenDict
|
|
511
|
+
provenance: Provenance
|
|
512
|
+
raw_output_path: str | None
|
|
513
|
+
raw_output_sha256: str
|
|
514
|
+
|
|
515
|
+
def __post_init__(self) -> None:
|
|
516
|
+
if not isinstance(self.name, str) or not self.name.strip():
|
|
517
|
+
raise ConfigurationError("Dashing Top-k name must be non-empty.")
|
|
518
|
+
if self.method not in {"exact-kmer-set", "hll-sketch"}:
|
|
519
|
+
raise ConfigurationError("Dashing Top-k method is invalid.")
|
|
520
|
+
size = len(self.labels)
|
|
521
|
+
if size < 2 or any(not isinstance(label, str) or not label for label in self.labels):
|
|
522
|
+
raise ConfigurationError("Dashing Top-k requires at least two non-empty labels.")
|
|
523
|
+
if (
|
|
524
|
+
isinstance(self.top_k, bool)
|
|
525
|
+
or not isinstance(self.top_k, int)
|
|
526
|
+
or not 1 <= self.top_k < size
|
|
527
|
+
):
|
|
528
|
+
raise ConfigurationError("Dashing top_k must be in [1, item_count - 1].")
|
|
529
|
+
if len(self.rows) != size or any(
|
|
530
|
+
not isinstance(row, DashingNeighborRow) for row in self.rows
|
|
531
|
+
):
|
|
532
|
+
raise ConfigurationError("Dashing Top-k rows must match labels.")
|
|
533
|
+
if tuple(row.query_index for row in self.rows) != tuple(range(size)):
|
|
534
|
+
raise ConfigurationError("Dashing Top-k rows must cover query indexes in order.")
|
|
535
|
+
if any(row.query_label != self.labels[row.query_index] for row in self.rows):
|
|
536
|
+
raise ConfigurationError("Dashing Top-k query labels do not match labels.")
|
|
537
|
+
if any(len(row.hits) != self.top_k for row in self.rows):
|
|
538
|
+
raise ConfigurationError("Every Dashing Top-k row must contain top_k hits.")
|
|
539
|
+
if any(
|
|
540
|
+
hit.index >= size or hit.label != self.labels[hit.index]
|
|
541
|
+
for row in self.rows
|
|
542
|
+
for hit in row.hits
|
|
543
|
+
):
|
|
544
|
+
raise ConfigurationError("A Dashing Top-k hit is outside the result labels.")
|
|
545
|
+
if len(self.input_sha256) != size:
|
|
546
|
+
raise ConfigurationError("Dashing Top-k checksums must match labels.")
|
|
547
|
+
_validate_dashing_common(
|
|
548
|
+
self.algorithm_version,
|
|
549
|
+
self.input_sha256,
|
|
550
|
+
self.provenance,
|
|
551
|
+
self.raw_output_path,
|
|
552
|
+
self.raw_output_sha256,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
@dataclass(frozen=True)
|
|
557
|
+
class SketchSimilarityResult(_SerializableResult):
|
|
558
|
+
"""Set-based comparison of two compatible sketches."""
|
|
559
|
+
|
|
560
|
+
name: str
|
|
561
|
+
method: str
|
|
562
|
+
value: float
|
|
563
|
+
metric: Literal["jaccard", "containment"]
|
|
564
|
+
left_id: str | None
|
|
565
|
+
right_id: str | None
|
|
566
|
+
left_hash_count: int
|
|
567
|
+
right_hash_count: int
|
|
568
|
+
shared_hash_count: int
|
|
569
|
+
union_hash_count: int
|
|
570
|
+
denominator: int
|
|
571
|
+
passed_min_shared_hashes: bool
|
|
572
|
+
parameters: FrozenDict
|
|
573
|
+
|
|
574
|
+
def __post_init__(self) -> None:
|
|
575
|
+
if not self.name or not self.method:
|
|
576
|
+
raise ConfigurationError("Sketch similarity name and method must be non-empty.")
|
|
577
|
+
if self.metric not in {"jaccard", "containment"}:
|
|
578
|
+
raise ConfigurationError("Sketch similarity metric is invalid.")
|
|
579
|
+
if (
|
|
580
|
+
isinstance(self.value, bool)
|
|
581
|
+
or not isinstance(self.value, (int, float))
|
|
582
|
+
or not math.isfinite(self.value)
|
|
583
|
+
or not 0 <= self.value <= 1
|
|
584
|
+
):
|
|
585
|
+
raise ConfigurationError("Sketch similarity value must be finite in [0, 1].")
|
|
586
|
+
_optional_identifier(self.left_id, "left_id")
|
|
587
|
+
_optional_identifier(self.right_id, "right_id")
|
|
588
|
+
for name in (
|
|
589
|
+
"left_hash_count",
|
|
590
|
+
"right_hash_count",
|
|
591
|
+
"shared_hash_count",
|
|
592
|
+
"union_hash_count",
|
|
593
|
+
"denominator",
|
|
594
|
+
):
|
|
595
|
+
_non_negative_int(getattr(self, name), name)
|
|
596
|
+
if self.shared_hash_count > min(self.left_hash_count, self.right_hash_count):
|
|
597
|
+
raise ConfigurationError("Shared sketch hashes exceed an input hash count.")
|
|
598
|
+
if self.union_hash_count < max(self.left_hash_count, self.right_hash_count):
|
|
599
|
+
raise ConfigurationError("Sketch union is smaller than an input hash count.")
|
|
600
|
+
if not isinstance(self.passed_min_shared_hashes, bool):
|
|
601
|
+
raise ConfigurationError("passed_min_shared_hashes must be boolean.")
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@dataclass(frozen=True)
|
|
605
|
+
class NearestNeighborHit(_SerializableResult):
|
|
606
|
+
record_id: str
|
|
607
|
+
index: int
|
|
608
|
+
similarity: float
|
|
609
|
+
shared_hash_count: int
|
|
610
|
+
|
|
611
|
+
def __post_init__(self) -> None:
|
|
612
|
+
if not isinstance(self.record_id, str) or not self.record_id.strip():
|
|
613
|
+
raise ConfigurationError("Nearest-neighbor ID must be non-empty.")
|
|
614
|
+
_non_negative_int(self.index, "index")
|
|
615
|
+
_non_negative_int(self.shared_hash_count, "shared_hash_count")
|
|
616
|
+
if (
|
|
617
|
+
isinstance(self.similarity, bool)
|
|
618
|
+
or not isinstance(self.similarity, (int, float))
|
|
619
|
+
or not math.isfinite(self.similarity)
|
|
620
|
+
or not 0 <= self.similarity <= 1
|
|
621
|
+
):
|
|
622
|
+
raise ConfigurationError("Nearest-neighbor similarity must be in [0, 1].")
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
@dataclass(frozen=True)
|
|
626
|
+
class NearestNeighborResult(_SerializableResult):
|
|
627
|
+
name: str
|
|
628
|
+
method: str
|
|
629
|
+
algorithm_version: str
|
|
630
|
+
query_id: str | None
|
|
631
|
+
index_size: int
|
|
632
|
+
hits: tuple[NearestNeighborHit, ...]
|
|
633
|
+
top_k: int
|
|
634
|
+
min_similarity: float
|
|
635
|
+
parameters: FrozenDict
|
|
636
|
+
|
|
637
|
+
def __post_init__(self) -> None:
|
|
638
|
+
for name in ("name", "method", "algorithm_version"):
|
|
639
|
+
if not isinstance(getattr(self, name), str) or not getattr(self, name).strip():
|
|
640
|
+
raise ConfigurationError(f"NearestNeighborResult {name} must be non-empty.")
|
|
641
|
+
_optional_identifier(self.query_id, "query_id")
|
|
642
|
+
_non_negative_int(self.index_size, "index_size")
|
|
643
|
+
if isinstance(self.top_k, bool) or not isinstance(self.top_k, int) or self.top_k <= 0:
|
|
644
|
+
raise ConfigurationError("top_k must be positive.")
|
|
645
|
+
if len(self.hits) > self.top_k or any(
|
|
646
|
+
not isinstance(item, NearestNeighborHit) for item in self.hits
|
|
647
|
+
):
|
|
648
|
+
raise ConfigurationError("Nearest-neighbor hits violate the declared top_k.")
|
|
649
|
+
if any(item.index >= self.index_size for item in self.hits):
|
|
650
|
+
raise ConfigurationError("Nearest-neighbor hit index is outside the index.")
|
|
651
|
+
if (
|
|
652
|
+
isinstance(self.min_similarity, bool)
|
|
653
|
+
or not isinstance(self.min_similarity, (int, float))
|
|
654
|
+
or not 0 <= self.min_similarity <= 1
|
|
655
|
+
):
|
|
656
|
+
raise ConfigurationError("min_similarity must be in [0, 1].")
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
__all__ = [
|
|
660
|
+
"ApproximateMatch",
|
|
661
|
+
"ApproximateSearchResult",
|
|
662
|
+
"DashingJaccardMatrixResult",
|
|
663
|
+
"DashingNeighborHit",
|
|
664
|
+
"DashingNeighborRow",
|
|
665
|
+
"DashingTopKResult",
|
|
666
|
+
"DistanceResult",
|
|
667
|
+
"EditStep",
|
|
668
|
+
"Mismatch",
|
|
669
|
+
"NearestNeighborHit",
|
|
670
|
+
"NearestNeighborResult",
|
|
671
|
+
"SearchResult",
|
|
672
|
+
"SequenceMatch",
|
|
673
|
+
"SimilarityMatrixResult",
|
|
674
|
+
"SimilarityResult",
|
|
675
|
+
"SketchSimilarityResult",
|
|
676
|
+
]
|