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,520 @@
|
|
|
1
|
+
"""Immutable, JSON-compatible summaries of dataset operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Literal, cast
|
|
8
|
+
|
|
9
|
+
from dnakit.core import DNASet
|
|
10
|
+
from dnakit.core._json import FrozenDict, JSONValue, to_json_compatible
|
|
11
|
+
from dnakit.exceptions import ConfigurationError
|
|
12
|
+
|
|
13
|
+
DedupAction = Literal["deduplicated", "kept_all", "dropped"]
|
|
14
|
+
Orientation = Literal["forward", "reverse_complement"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class DedupGroup:
|
|
19
|
+
"""One equivalence class and its chosen handling."""
|
|
20
|
+
|
|
21
|
+
group_index: int
|
|
22
|
+
representative_id: str
|
|
23
|
+
member_ids: tuple[str, ...]
|
|
24
|
+
orientations: tuple[Orientation, ...]
|
|
25
|
+
conflict: bool = False
|
|
26
|
+
conflict_values: tuple[JSONValue, ...] = ()
|
|
27
|
+
missing_conflict_value_count: int = 0
|
|
28
|
+
action: DedupAction = "deduplicated"
|
|
29
|
+
rotation_offsets: tuple[int | None, ...] = ()
|
|
30
|
+
rotation_offset_definition: str | None = None
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
if (
|
|
34
|
+
isinstance(self.group_index, bool)
|
|
35
|
+
or not isinstance(self.group_index, int)
|
|
36
|
+
or self.group_index < 0
|
|
37
|
+
):
|
|
38
|
+
raise ConfigurationError("DedupGroup group_index must be non-negative.")
|
|
39
|
+
if not self.representative_id or not self.member_ids:
|
|
40
|
+
raise ConfigurationError("DedupGroup IDs must be non-empty.")
|
|
41
|
+
if len(self.member_ids) != len(self.orientations):
|
|
42
|
+
raise ConfigurationError("DedupGroup orientations must align with member_ids.")
|
|
43
|
+
if self.representative_id not in self.member_ids:
|
|
44
|
+
raise ConfigurationError("DedupGroup representative must be a member.")
|
|
45
|
+
if self.action not in {"deduplicated", "kept_all", "dropped"}:
|
|
46
|
+
raise ConfigurationError("DedupGroup action is invalid.")
|
|
47
|
+
if self.rotation_offsets and len(self.rotation_offsets) != len(self.member_ids):
|
|
48
|
+
raise ConfigurationError("DedupGroup rotation offsets must align with member_ids.")
|
|
49
|
+
if any(
|
|
50
|
+
value is not None
|
|
51
|
+
and (isinstance(value, bool) or not isinstance(value, int) or value < 0)
|
|
52
|
+
for value in self.rotation_offsets
|
|
53
|
+
):
|
|
54
|
+
raise ConfigurationError("DedupGroup rotation offsets must be non-negative or None.")
|
|
55
|
+
if (
|
|
56
|
+
isinstance(self.missing_conflict_value_count, bool)
|
|
57
|
+
or not isinstance(self.missing_conflict_value_count, int)
|
|
58
|
+
or self.missing_conflict_value_count < 0
|
|
59
|
+
):
|
|
60
|
+
raise ConfigurationError("Missing conflict value count must be non-negative.")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True, slots=True)
|
|
64
|
+
class DeduplicationResult:
|
|
65
|
+
"""Non-redundant records, equivalence groups, and conflict audit."""
|
|
66
|
+
|
|
67
|
+
records: DNASet
|
|
68
|
+
groups: tuple[DedupGroup, ...]
|
|
69
|
+
equivalence: str
|
|
70
|
+
representative_policy: str
|
|
71
|
+
conflict_field: str | None
|
|
72
|
+
conflict_policy: str
|
|
73
|
+
input_count: int
|
|
74
|
+
output_count: int
|
|
75
|
+
duplicate_count: int
|
|
76
|
+
conflicted_group_count: int
|
|
77
|
+
removed_count: int
|
|
78
|
+
merge_metadata: bool = False
|
|
79
|
+
|
|
80
|
+
def __post_init__(self) -> None:
|
|
81
|
+
if self.output_count != len(self.records):
|
|
82
|
+
raise ConfigurationError("Deduplication output_count does not match records.")
|
|
83
|
+
if self.input_count < 0 or self.output_count < 0 or self.output_count > self.input_count:
|
|
84
|
+
raise ConfigurationError("Deduplication counts are inconsistent.")
|
|
85
|
+
if self.removed_count != self.input_count - self.output_count:
|
|
86
|
+
raise ConfigurationError("Deduplication removed_count is inconsistent.")
|
|
87
|
+
if self.duplicate_count != self.input_count - len(self.groups):
|
|
88
|
+
raise ConfigurationError("Deduplication duplicate_count is inconsistent.")
|
|
89
|
+
if not isinstance(self.merge_metadata, bool):
|
|
90
|
+
raise ConfigurationError("Deduplication merge_metadata must be a boolean.")
|
|
91
|
+
|
|
92
|
+
def to_dict(self) -> dict[str, Any]:
|
|
93
|
+
"""Return an audit payload without duplicating full sequences."""
|
|
94
|
+
|
|
95
|
+
payload = {
|
|
96
|
+
"equivalence": self.equivalence,
|
|
97
|
+
"representative_policy": self.representative_policy,
|
|
98
|
+
"conflict_field": self.conflict_field,
|
|
99
|
+
"conflict_policy": self.conflict_policy,
|
|
100
|
+
"merge_metadata": self.merge_metadata,
|
|
101
|
+
"input_count": self.input_count,
|
|
102
|
+
"output_count": self.output_count,
|
|
103
|
+
"duplicate_count": self.duplicate_count,
|
|
104
|
+
"conflicted_group_count": self.conflicted_group_count,
|
|
105
|
+
"removed_count": self.removed_count,
|
|
106
|
+
"output_ids": self.records.ids,
|
|
107
|
+
"groups": self.groups,
|
|
108
|
+
}
|
|
109
|
+
return cast(dict[str, Any], to_json_compatible(payload))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class SplitAssignment:
|
|
114
|
+
"""Assignment of one original record position to one named split."""
|
|
115
|
+
|
|
116
|
+
input_index: int
|
|
117
|
+
record_id: str
|
|
118
|
+
split: str
|
|
119
|
+
|
|
120
|
+
def __post_init__(self) -> None:
|
|
121
|
+
if (
|
|
122
|
+
isinstance(self.input_index, bool)
|
|
123
|
+
or not isinstance(self.input_index, int)
|
|
124
|
+
or self.input_index < 0
|
|
125
|
+
):
|
|
126
|
+
raise ConfigurationError("SplitAssignment input_index must be non-negative.")
|
|
127
|
+
if not self.record_id or not self.split:
|
|
128
|
+
raise ConfigurationError("SplitAssignment names must be non-empty.")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class SplitSubset:
|
|
133
|
+
"""One named materialized dataset subset."""
|
|
134
|
+
|
|
135
|
+
name: str
|
|
136
|
+
records: DNASet
|
|
137
|
+
|
|
138
|
+
def __post_init__(self) -> None:
|
|
139
|
+
if not isinstance(self.name, str) or not self.name.strip():
|
|
140
|
+
raise ConfigurationError("SplitSubset name must be non-empty.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True, slots=True)
|
|
144
|
+
class SplitResult:
|
|
145
|
+
"""Materialized subsets plus deterministic assignment metadata."""
|
|
146
|
+
|
|
147
|
+
subsets: tuple[SplitSubset, ...]
|
|
148
|
+
assignments: tuple[SplitAssignment, ...]
|
|
149
|
+
method: str
|
|
150
|
+
ratios: FrozenDict
|
|
151
|
+
counts: FrozenDict
|
|
152
|
+
seed: int
|
|
153
|
+
preserve_order: bool
|
|
154
|
+
metadata_key: str | None = None
|
|
155
|
+
component_count: int | None = None
|
|
156
|
+
pairwise_comparison_count: int = 0
|
|
157
|
+
similarity_method: str | None = None
|
|
158
|
+
similarity_threshold: float | None = None
|
|
159
|
+
shuffle: bool = True
|
|
160
|
+
similarity_k: int | None = None
|
|
161
|
+
max_pairwise_records: int | None = None
|
|
162
|
+
assignment_strategy: str | None = None
|
|
163
|
+
|
|
164
|
+
def __post_init__(self) -> None:
|
|
165
|
+
names = tuple(subset.name for subset in self.subsets)
|
|
166
|
+
if len(names) != len(set(names)) or set(names) != set(self.ratios):
|
|
167
|
+
raise ConfigurationError("SplitResult subset names must match ratio names.")
|
|
168
|
+
if set(self.counts) != set(names):
|
|
169
|
+
raise ConfigurationError("SplitResult count names must match subset names.")
|
|
170
|
+
if any(self.counts[name] != len(self.get(name)) for name in names):
|
|
171
|
+
raise ConfigurationError("SplitResult counts do not match materialized subsets.")
|
|
172
|
+
if sum(cast(int, self.counts[name]) for name in names) != len(self.assignments):
|
|
173
|
+
raise ConfigurationError("SplitResult assignments do not match subset counts.")
|
|
174
|
+
if not isinstance(self.shuffle, bool):
|
|
175
|
+
raise ConfigurationError("SplitResult shuffle must be a boolean.")
|
|
176
|
+
for field_name in ("similarity_k", "max_pairwise_records"):
|
|
177
|
+
value = getattr(self, field_name)
|
|
178
|
+
if value is not None and (
|
|
179
|
+
isinstance(value, bool) or not isinstance(value, int) or value <= 0
|
|
180
|
+
):
|
|
181
|
+
raise ConfigurationError(f"SplitResult {field_name} must be positive or None.")
|
|
182
|
+
if self.assignment_strategy is not None and (
|
|
183
|
+
not isinstance(self.assignment_strategy, str) or not self.assignment_strategy.strip()
|
|
184
|
+
):
|
|
185
|
+
raise ConfigurationError("SplitResult assignment_strategy must be non-empty or None.")
|
|
186
|
+
|
|
187
|
+
def get(self, name: str) -> DNASet:
|
|
188
|
+
"""Return one subset by name."""
|
|
189
|
+
|
|
190
|
+
for subset in self.subsets:
|
|
191
|
+
if subset.name == name:
|
|
192
|
+
return subset.records
|
|
193
|
+
raise KeyError(name)
|
|
194
|
+
|
|
195
|
+
def to_dict(self) -> dict[str, Any]:
|
|
196
|
+
"""Return an audit payload containing IDs rather than full sequences."""
|
|
197
|
+
|
|
198
|
+
payload = {
|
|
199
|
+
"method": self.method,
|
|
200
|
+
"ratios": self.ratios,
|
|
201
|
+
"counts": self.counts,
|
|
202
|
+
"seed": self.seed,
|
|
203
|
+
"shuffle": self.shuffle,
|
|
204
|
+
"preserve_order": self.preserve_order,
|
|
205
|
+
"metadata_key": self.metadata_key,
|
|
206
|
+
"component_count": self.component_count,
|
|
207
|
+
"pairwise_comparison_count": self.pairwise_comparison_count,
|
|
208
|
+
"similarity_method": self.similarity_method,
|
|
209
|
+
"similarity_threshold": self.similarity_threshold,
|
|
210
|
+
"similarity_k": self.similarity_k,
|
|
211
|
+
"max_pairwise_records": self.max_pairwise_records,
|
|
212
|
+
"assignment_strategy": self.assignment_strategy,
|
|
213
|
+
"subsets": {subset.name: subset.records.ids for subset in self.subsets},
|
|
214
|
+
"assignments": self.assignments,
|
|
215
|
+
}
|
|
216
|
+
return cast(dict[str, Any], to_json_compatible(payload))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass(frozen=True, slots=True)
|
|
220
|
+
class IUPACDuplicateGroup:
|
|
221
|
+
"""Complete-link group whose member symbols are pairwise compatible."""
|
|
222
|
+
|
|
223
|
+
group_index: int
|
|
224
|
+
representative_id: str
|
|
225
|
+
member_ids: tuple[str, ...]
|
|
226
|
+
relation: Literal["singleton", "identical", "compatible"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True, slots=True)
|
|
230
|
+
class IUPACPairRelation:
|
|
231
|
+
"""Auditable relation for one exhaustively compared input pair."""
|
|
232
|
+
|
|
233
|
+
left_index: int
|
|
234
|
+
right_index: int
|
|
235
|
+
left_id: str
|
|
236
|
+
right_id: str
|
|
237
|
+
relation: Literal["identical", "compatible", "conflict"]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True, slots=True)
|
|
241
|
+
class IUPACDeduplicationResult:
|
|
242
|
+
records: DNASet
|
|
243
|
+
groups: tuple[IUPACDuplicateGroup, ...]
|
|
244
|
+
pair_relations: tuple[IUPACPairRelation, ...]
|
|
245
|
+
representative_policy: str
|
|
246
|
+
compatibility_rule: str
|
|
247
|
+
grouping_strategy: str
|
|
248
|
+
input_count: int
|
|
249
|
+
output_count: int
|
|
250
|
+
pairwise_comparison_count: int
|
|
251
|
+
identical_pair_count: int
|
|
252
|
+
compatible_pair_count: int
|
|
253
|
+
conflict_pair_count: int
|
|
254
|
+
max_records: int
|
|
255
|
+
max_pairwise_comparisons: int
|
|
256
|
+
|
|
257
|
+
def to_dict(self) -> dict[str, Any]:
|
|
258
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass(frozen=True, slots=True)
|
|
262
|
+
class Cluster:
|
|
263
|
+
cluster_index: int
|
|
264
|
+
member_indices: tuple[int, ...]
|
|
265
|
+
member_ids: tuple[str, ...]
|
|
266
|
+
representative_index: int
|
|
267
|
+
representative_id: str
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass(frozen=True, slots=True)
|
|
271
|
+
class ClusteringResult:
|
|
272
|
+
clusters: tuple[Cluster, ...]
|
|
273
|
+
labels: tuple[int, ...]
|
|
274
|
+
representatives: DNASet
|
|
275
|
+
method: str
|
|
276
|
+
threshold: float
|
|
277
|
+
k: int
|
|
278
|
+
canonical: bool
|
|
279
|
+
representative_policy: str
|
|
280
|
+
clustering_strategy: str
|
|
281
|
+
seed: int
|
|
282
|
+
seed_used: bool
|
|
283
|
+
pairwise_comparison_count: int
|
|
284
|
+
max_records: int
|
|
285
|
+
max_pairwise_comparisons: int
|
|
286
|
+
max_alignment_cells: int
|
|
287
|
+
|
|
288
|
+
def to_dict(self) -> dict[str, Any]:
|
|
289
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@dataclass(frozen=True, slots=True)
|
|
293
|
+
class NeuralClusteringResult:
|
|
294
|
+
"""K-means groups obtained from sequence-level DNA model representations."""
|
|
295
|
+
|
|
296
|
+
clusters: tuple[Cluster, ...]
|
|
297
|
+
labels: tuple[int, ...]
|
|
298
|
+
representatives: DNASet
|
|
299
|
+
centers: tuple[tuple[float, ...], ...]
|
|
300
|
+
model_name: str
|
|
301
|
+
checkpoint_path: str | None
|
|
302
|
+
pooling: str
|
|
303
|
+
embedding_dimension: int
|
|
304
|
+
clustering_dimension: int
|
|
305
|
+
n_clusters: int
|
|
306
|
+
inertia: float
|
|
307
|
+
silhouette_score: float | None
|
|
308
|
+
normalize: bool
|
|
309
|
+
pca_components: int | None
|
|
310
|
+
pca_explained_variance_ratio: tuple[float, ...]
|
|
311
|
+
seed: int
|
|
312
|
+
n_init: int
|
|
313
|
+
max_iter: int
|
|
314
|
+
iteration_count: int
|
|
315
|
+
input_count: int
|
|
316
|
+
clustering_strategy: str = "l2-normalize-optional-pca-kmeans++-lloyd"
|
|
317
|
+
|
|
318
|
+
def __post_init__(self) -> None:
|
|
319
|
+
if len(self.labels) != self.input_count:
|
|
320
|
+
raise ConfigurationError("Neural clustering labels do not match input_count.")
|
|
321
|
+
if len(self.clusters) != self.n_clusters or len(self.centers) != self.n_clusters:
|
|
322
|
+
raise ConfigurationError("Neural clustering cluster counts are inconsistent.")
|
|
323
|
+
if len(self.representatives) != self.n_clusters:
|
|
324
|
+
raise ConfigurationError("Neural clustering representatives are inconsistent.")
|
|
325
|
+
if any(len(center) != self.clustering_dimension for center in self.centers):
|
|
326
|
+
raise ConfigurationError("Neural clustering center dimensions are inconsistent.")
|
|
327
|
+
numeric = [self.inertia, *(value for center in self.centers for value in center)]
|
|
328
|
+
if self.silhouette_score is not None:
|
|
329
|
+
numeric.append(self.silhouette_score)
|
|
330
|
+
numeric.extend(self.pca_explained_variance_ratio)
|
|
331
|
+
if any(not math.isfinite(float(value)) for value in numeric):
|
|
332
|
+
raise ConfigurationError("Neural clustering metrics must be finite.")
|
|
333
|
+
|
|
334
|
+
def to_dict(self) -> dict[str, Any]:
|
|
335
|
+
"""Return JSON-compatible labels, centers, scores, and provenance."""
|
|
336
|
+
|
|
337
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@dataclass(frozen=True, slots=True)
|
|
341
|
+
class LinkageStep:
|
|
342
|
+
step_index: int
|
|
343
|
+
left_node: int
|
|
344
|
+
right_node: int
|
|
345
|
+
new_node: int
|
|
346
|
+
distance: float
|
|
347
|
+
member_count: int
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@dataclass(frozen=True, slots=True)
|
|
351
|
+
class HierarchicalClusteringResult:
|
|
352
|
+
record_ids: tuple[str, ...]
|
|
353
|
+
linkage: tuple[LinkageStep, ...]
|
|
354
|
+
method: str
|
|
355
|
+
linkage_method: str
|
|
356
|
+
distance_definition: str
|
|
357
|
+
k: int
|
|
358
|
+
canonical: bool
|
|
359
|
+
pairwise_comparison_count: int
|
|
360
|
+
linkage_distance_update_count: int
|
|
361
|
+
max_records: int
|
|
362
|
+
max_pairwise_comparisons: int
|
|
363
|
+
max_alignment_cells: int
|
|
364
|
+
|
|
365
|
+
def to_dict(self) -> dict[str, Any]:
|
|
366
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@dataclass(frozen=True, slots=True)
|
|
370
|
+
class RepresentativeSelectionResult:
|
|
371
|
+
representatives: DNASet
|
|
372
|
+
representative_ids: tuple[str, ...]
|
|
373
|
+
policy: str
|
|
374
|
+
cluster_count: int
|
|
375
|
+
medoid_metric: str | None
|
|
376
|
+
pairwise_comparison_count: int
|
|
377
|
+
|
|
378
|
+
def to_dict(self) -> dict[str, Any]:
|
|
379
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass(frozen=True, slots=True)
|
|
383
|
+
class TemporalSplitResult:
|
|
384
|
+
subsets: tuple[SplitSubset, ...]
|
|
385
|
+
assignments: tuple[SplitAssignment, ...]
|
|
386
|
+
counts: FrozenDict
|
|
387
|
+
ratios: FrozenDict
|
|
388
|
+
metadata_key: str
|
|
389
|
+
cutoffs: tuple[str, ...]
|
|
390
|
+
strategy: str
|
|
391
|
+
preserve_order: bool
|
|
392
|
+
timezone_policy: str
|
|
393
|
+
max_records: int
|
|
394
|
+
|
|
395
|
+
def get(self, name: str) -> DNASet:
|
|
396
|
+
for subset in self.subsets:
|
|
397
|
+
if subset.name == name:
|
|
398
|
+
return subset.records
|
|
399
|
+
raise KeyError(name)
|
|
400
|
+
|
|
401
|
+
def to_dict(self) -> dict[str, Any]:
|
|
402
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass(frozen=True, slots=True)
|
|
406
|
+
class JointSplitResult:
|
|
407
|
+
subsets: tuple[SplitSubset, ...]
|
|
408
|
+
assignments: tuple[SplitAssignment, ...]
|
|
409
|
+
counts: FrozenDict
|
|
410
|
+
target_counts: FrozenDict
|
|
411
|
+
achieved_ratios: FrozenDict
|
|
412
|
+
group_keys: tuple[str, ...]
|
|
413
|
+
label_key: str | None
|
|
414
|
+
similarity_threshold: float | None
|
|
415
|
+
similarity_k: int
|
|
416
|
+
similarity_canonical: bool
|
|
417
|
+
seed: int
|
|
418
|
+
strategy: str
|
|
419
|
+
feasible: bool
|
|
420
|
+
relaxed: bool
|
|
421
|
+
relaxations: tuple[str, ...]
|
|
422
|
+
max_ratio_deviation: float
|
|
423
|
+
ratio_tolerance: float
|
|
424
|
+
atomic_unit_count: int
|
|
425
|
+
pairwise_comparison_count: int
|
|
426
|
+
max_records: int
|
|
427
|
+
max_pairwise_comparisons: int
|
|
428
|
+
infeasible_policy: str
|
|
429
|
+
|
|
430
|
+
def get(self, name: str) -> DNASet:
|
|
431
|
+
for subset in self.subsets:
|
|
432
|
+
if subset.name == name:
|
|
433
|
+
return subset.records
|
|
434
|
+
raise KeyError(name)
|
|
435
|
+
|
|
436
|
+
def to_dict(self) -> dict[str, Any]:
|
|
437
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@dataclass(frozen=True, slots=True)
|
|
441
|
+
class LeakageEvent:
|
|
442
|
+
left_split: str
|
|
443
|
+
right_split: str
|
|
444
|
+
left_id: str
|
|
445
|
+
right_id: str
|
|
446
|
+
left_index: int
|
|
447
|
+
right_index: int
|
|
448
|
+
similarity: float
|
|
449
|
+
exact: bool
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass(frozen=True, slots=True)
|
|
453
|
+
class LeakageReport:
|
|
454
|
+
events: tuple[LeakageEvent, ...]
|
|
455
|
+
method: str
|
|
456
|
+
threshold: float
|
|
457
|
+
strictness: str
|
|
458
|
+
exact_event_count: int
|
|
459
|
+
high_similarity_event_count: int
|
|
460
|
+
cross_pair_count: int
|
|
461
|
+
max_records: int
|
|
462
|
+
max_cross_pairs: int
|
|
463
|
+
max_events: int
|
|
464
|
+
truncated: bool
|
|
465
|
+
k: int
|
|
466
|
+
canonical: bool
|
|
467
|
+
max_alignment_cells: int
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def has_leakage(self) -> bool:
|
|
471
|
+
return bool(self.events)
|
|
472
|
+
|
|
473
|
+
def to_dict(self) -> dict[str, Any]:
|
|
474
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@dataclass(frozen=True, slots=True)
|
|
478
|
+
class SplitQualityResult:
|
|
479
|
+
total_count: int
|
|
480
|
+
counts: FrozenDict
|
|
481
|
+
target_ratios: FrozenDict
|
|
482
|
+
achieved_ratios: FrozenDict
|
|
483
|
+
ratio_deviations: FrozenDict
|
|
484
|
+
max_ratio_deviation: float
|
|
485
|
+
label_key: str | None
|
|
486
|
+
label_total_variation_by_split: FrozenDict
|
|
487
|
+
group_keys: tuple[str, ...]
|
|
488
|
+
group_leak_count: int
|
|
489
|
+
leakage_event_count: int | None
|
|
490
|
+
quality_score: float
|
|
491
|
+
score_definition: str
|
|
492
|
+
parameters: FrozenDict
|
|
493
|
+
|
|
494
|
+
def to_dict(self) -> dict[str, Any]:
|
|
495
|
+
return cast(dict[str, Any], to_json_compatible(self))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
__all__ = [
|
|
499
|
+
"Cluster",
|
|
500
|
+
"ClusteringResult",
|
|
501
|
+
"DedupAction",
|
|
502
|
+
"DedupGroup",
|
|
503
|
+
"DeduplicationResult",
|
|
504
|
+
"HierarchicalClusteringResult",
|
|
505
|
+
"IUPACDeduplicationResult",
|
|
506
|
+
"IUPACDuplicateGroup",
|
|
507
|
+
"IUPACPairRelation",
|
|
508
|
+
"JointSplitResult",
|
|
509
|
+
"LeakageEvent",
|
|
510
|
+
"LeakageReport",
|
|
511
|
+
"LinkageStep",
|
|
512
|
+
"NeuralClusteringResult",
|
|
513
|
+
"Orientation",
|
|
514
|
+
"RepresentativeSelectionResult",
|
|
515
|
+
"SplitAssignment",
|
|
516
|
+
"SplitQualityResult",
|
|
517
|
+
"SplitResult",
|
|
518
|
+
"SplitSubset",
|
|
519
|
+
"TemporalSplitResult",
|
|
520
|
+
]
|