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.
Files changed (210) hide show
  1. dnakit/__init__.py +108 -0
  2. dnakit/_version.py +5 -0
  3. dnakit/alignment/__init__.py +6 -0
  4. dnakit/alignment/pairwise.py +512 -0
  5. dnakit/alignment/results.py +188 -0
  6. dnakit/backends/__init__.py +80 -0
  7. dnakit/backends/external.py +517 -0
  8. dnakit/backends/registry.py +207 -0
  9. dnakit/batch.py +452 -0
  10. dnakit/cache/__init__.py +5 -0
  11. dnakit/cache/store.py +424 -0
  12. dnakit/chunking.py +920 -0
  13. dnakit/cli/__init__.py +5 -0
  14. dnakit/cli/__main__.py +6 -0
  15. dnakit/cli/app.py +814 -0
  16. dnakit/cli/workflow.py +94 -0
  17. dnakit/config/__init__.py +5 -0
  18. dnakit/config/loader.py +381 -0
  19. dnakit/core/__init__.py +80 -0
  20. dnakit/core/_json.py +206 -0
  21. dnakit/core/backend_info.py +70 -0
  22. dnakit/core/collection.py +163 -0
  23. dnakit/core/coordinates.py +295 -0
  24. dnakit/core/enums.py +95 -0
  25. dnakit/core/facade.py +719 -0
  26. dnakit/core/feature.py +90 -0
  27. dnakit/core/gap.py +82 -0
  28. dnakit/core/issues.py +62 -0
  29. dnakit/core/provenance.py +305 -0
  30. dnakit/core/record.py +161 -0
  31. dnakit/core/results.py +134 -0
  32. dnakit/core/sequence.py +327 -0
  33. dnakit/datasets/__init__.py +100 -0
  34. dnakit/datasets/_advanced_shared.py +198 -0
  35. dnakit/datasets/_metadata.py +33 -0
  36. dnakit/datasets/advanced_deduplicate.py +183 -0
  37. dnakit/datasets/advanced_split.py +302 -0
  38. dnakit/datasets/clustering.py +286 -0
  39. dnakit/datasets/config.py +531 -0
  40. dnakit/datasets/deduplicate.py +303 -0
  41. dnakit/datasets/evaluation.py +244 -0
  42. dnakit/datasets/filter.py +135 -0
  43. dnakit/datasets/neural_clustering.py +221 -0
  44. dnakit/datasets/results.py +520 -0
  45. dnakit/datasets/split.py +450 -0
  46. dnakit/descriptors/__init__.py +102 -0
  47. dnakit/descriptors/_dinucleotide.py +345 -0
  48. dnakit/descriptors/_shared.py +180 -0
  49. dnakit/descriptors/all.py +917 -0
  50. dnakit/descriptors/basic.py +193 -0
  51. dnakit/descriptors/codon.py +195 -0
  52. dnakit/descriptors/complexity.py +201 -0
  53. dnakit/descriptors/entropy.py +101 -0
  54. dnakit/descriptors/homopolymer.py +105 -0
  55. dnakit/descriptors/kmer.py +88 -0
  56. dnakit/descriptors/results.py +286 -0
  57. dnakit/descriptors/schema.py +414 -0
  58. dnakit/descriptors/window.py +203 -0
  59. dnakit/download/__init__.py +54 -0
  60. dnakit/download/catalogs.py +166 -0
  61. dnakit/download/ena.py +131 -0
  62. dnakit/download/encode.py +103 -0
  63. dnakit/download/files.py +469 -0
  64. dnakit/download/indexes.py +233 -0
  65. dnakit/download/metadata.py +164 -0
  66. dnakit/download/models.py +227 -0
  67. dnakit/download/ncbi.py +333 -0
  68. dnakit/download/sequence.py +133 -0
  69. dnakit/download/ucsc.py +99 -0
  70. dnakit/evaluation/__init__.py +81 -0
  71. dnakit/evaluation/_shared.py +288 -0
  72. dnakit/evaluation/collection.py +270 -0
  73. dnakit/evaluation/config.py +591 -0
  74. dnakit/evaluation/distribution.py +287 -0
  75. dnakit/evaluation/frechet.py +223 -0
  76. dnakit/evaluation/generative.py +381 -0
  77. dnakit/evaluation/reference.py +409 -0
  78. dnakit/evaluation/results.py +137 -0
  79. dnakit/evaluation/scorecard.py +211 -0
  80. dnakit/evaluation/sequence.py +403 -0
  81. dnakit/evaluation/synthesis.py +249 -0
  82. dnakit/exceptions.py +189 -0
  83. dnakit/fingerprints/__init__.py +109 -0
  84. dnakit/fingerprints/_shared.py +116 -0
  85. dnakit/fingerprints/advanced.py +829 -0
  86. dnakit/fingerprints/bit.py +229 -0
  87. dnakit/fingerprints/encoding.py +351 -0
  88. dnakit/fingerprints/kmer.py +170 -0
  89. dnakit/fingerprints/results.py +531 -0
  90. dnakit/fingerprints/sketch.py +246 -0
  91. dnakit/io/__init__.py +112 -0
  92. dnakit/io/_advanced_common.py +105 -0
  93. dnakit/io/_formats.py +994 -0
  94. dnakit/io/annotations.py +1179 -0
  95. dnakit/io/api.py +759 -0
  96. dnakit/io/config.py +315 -0
  97. dnakit/io/genbank.py +523 -0
  98. dnakit/io/indexed.py +1146 -0
  99. dnakit/io/metadata.py +452 -0
  100. dnakit/io/parquet.py +56 -0
  101. dnakit/io/results.py +131 -0
  102. dnakit/io/source.py +138 -0
  103. dnakit/io/tables.py +1237 -0
  104. dnakit/molbio/__init__.py +101 -0
  105. dnakit/molbio/_shared.py +266 -0
  106. dnakit/molbio/assembly.py +279 -0
  107. dnakit/molbio/crispr.py +181 -0
  108. dnakit/molbio/optimization.py +897 -0
  109. dnakit/molbio/primer3_design.py +667 -0
  110. dnakit/molbio/primers.py +899 -0
  111. dnakit/molbio/restriction.py +573 -0
  112. dnakit/molbio/results.py +583 -0
  113. dnakit/ops/__init__.py +588 -0
  114. dnakit/ops/_common.py +201 -0
  115. dnakit/ops/circular.py +193 -0
  116. dnakit/ops/concat.py +204 -0
  117. dnakit/ops/direction.py +27 -0
  118. dnakit/ops/edit.py +443 -0
  119. dnakit/ops/evolution.py +991 -0
  120. dnakit/ops/mutation.py +151 -0
  121. dnakit/ops/records.py +1012 -0
  122. dnakit/ops/translation.py +257 -0
  123. dnakit/patterns/__init__.py +81 -0
  124. dnakit/patterns/_shared.py +282 -0
  125. dnakit/patterns/coding.py +360 -0
  126. dnakit/patterns/crispr.py +337 -0
  127. dnakit/patterns/motif.py +721 -0
  128. dnakit/patterns/regions.py +341 -0
  129. dnakit/patterns/repeats.py +480 -0
  130. dnakit/patterns/restriction.py +281 -0
  131. dnakit/patterns/results.py +411 -0
  132. dnakit/py.typed +1 -0
  133. dnakit/references/__init__.py +27 -0
  134. dnakit/references/models.py +155 -0
  135. dnakit/references/ncbi.py +785 -0
  136. dnakit/representations/__init__.py +29 -0
  137. dnakit/representations/backends.py +928 -0
  138. dnakit/representations/checkpoints.py +356 -0
  139. dnakit/representations/extraction.py +220 -0
  140. dnakit/representations/models.py +395 -0
  141. dnakit/representations/results.py +79 -0
  142. dnakit/search/__init__.py +132 -0
  143. dnakit/search/_http.py +271 -0
  144. dnakit/search/_shared.py +108 -0
  145. dnakit/search/blast.py +516 -0
  146. dnakit/search/ena.py +245 -0
  147. dnakit/search/encode.py +102 -0
  148. dnakit/search/ensembl.py +874 -0
  149. dnakit/search/models.py +159 -0
  150. dnakit/search/ncbi.py +756 -0
  151. dnakit/search/ucsc.py +388 -0
  152. dnakit/secondary_structure/__init__.py +38 -0
  153. dnakit/secondary_structure/dotbracket.py +410 -0
  154. dnakit/secondary_structure/nupack.py +589 -0
  155. dnakit/secondary_structure/results.py +142 -0
  156. dnakit/similarity/__init__.py +125 -0
  157. dnakit/similarity/_shared.py +152 -0
  158. dnakit/similarity/approximate.py +258 -0
  159. dnakit/similarity/compare.py +162 -0
  160. dnakit/similarity/dashing.py +936 -0
  161. dnakit/similarity/distance.py +279 -0
  162. dnakit/similarity/index.py +365 -0
  163. dnakit/similarity/matrix.py +254 -0
  164. dnakit/similarity/results.py +676 -0
  165. dnakit/similarity/search.py +286 -0
  166. dnakit/similarity/sketch.py +96 -0
  167. dnakit/similarity/vector.py +662 -0
  168. dnakit/standardize/__init__.py +77 -0
  169. dnakit/standardize/_shared.py +100 -0
  170. dnakit/standardize/config.py +411 -0
  171. dnakit/standardize/gaps.py +1060 -0
  172. dnakit/standardize/normalize.py +565 -0
  173. dnakit/standardize/results.py +247 -0
  174. dnakit/standardize/validate.py +487 -0
  175. dnakit/structure3d/__init__.py +47 -0
  176. dnakit/structure3d/_shared.py +47 -0
  177. dnakit/structure3d/geometry.py +773 -0
  178. dnakit/structure3d/pdb.py +253 -0
  179. dnakit/structure3d/results.py +233 -0
  180. dnakit/structure3d/threedna.py +174 -0
  181. dnakit/thermodynamics/__init__.py +112 -0
  182. dnakit/thermodynamics/_shared.py +92 -0
  183. dnakit/thermodynamics/backends.py +653 -0
  184. dnakit/thermodynamics/calculations.py +837 -0
  185. dnakit/thermodynamics/config.py +175 -0
  186. dnakit/thermodynamics/equilibrium.py +340 -0
  187. dnakit/thermodynamics/optics.py +385 -0
  188. dnakit/thermodynamics/parameters.py +79 -0
  189. dnakit/thermodynamics/results.py +445 -0
  190. dnakit/visualization/__init__.py +43 -0
  191. dnakit/visualization/_svg.py +185 -0
  192. dnakit/visualization/advanced.py +314 -0
  193. dnakit/visualization/config.py +255 -0
  194. dnakit/visualization/export.py +349 -0
  195. dnakit/visualization/matrix.py +279 -0
  196. dnakit/visualization/report.py +431 -0
  197. dnakit/visualization/results.py +236 -0
  198. dnakit/visualization/sequence.py +459 -0
  199. dnakit/workflows/__init__.py +45 -0
  200. dnakit/workflows/manifest.py +365 -0
  201. dnakit/workflows/runner.py +1548 -0
  202. dnakit/workflows/schema.py +898 -0
  203. dnakit-0.1.0.dev0.dist-info/METADATA +126 -0
  204. dnakit-0.1.0.dev0.dist-info/RECORD +210 -0
  205. dnakit-0.1.0.dev0.dist-info/WHEEL +5 -0
  206. dnakit-0.1.0.dev0.dist-info/entry_points.txt +2 -0
  207. dnakit-0.1.0.dev0.dist-info/licenses/DISCLAIMER.md +26 -0
  208. dnakit-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
  209. dnakit-0.1.0.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
  210. dnakit-0.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,928 @@
1
+ """Lazy optional backends for extracting sequence-level DNA representations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import importlib.util
7
+ import json
8
+ import math
9
+ import sys
10
+ from collections.abc import Iterable, Sequence
11
+ from contextlib import suppress
12
+ from pathlib import Path
13
+ from typing import Any, Protocol
14
+
15
+ from dnakit.exceptions import BackendExecutionError, BackendUnavailableError, ConfigurationError
16
+
17
+ from .models import DNAEmbeddingModel, RepresentationConfig
18
+
19
+
20
+ class RepresentationBackend(Protocol):
21
+ """Backend contract used by :func:`extract_representations`."""
22
+
23
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
24
+ """Return one finite numeric vector per input sequence."""
25
+
26
+
27
+ def _missing_dependency(spec: DNAEmbeddingModel, package: str) -> BackendUnavailableError:
28
+ requirement = spec.required_package or package
29
+ return BackendUnavailableError(
30
+ f"The {spec.display_name} representation backend is not installed.",
31
+ code="MISSING_NEURAL_DEPENDENCY",
32
+ context={"model": spec.name, "dependency": requirement},
33
+ hint=f"Install the required backend: {requirement}",
34
+ )
35
+
36
+
37
+ def _require_module(name: str, spec: DNAEmbeddingModel) -> Any:
38
+ try:
39
+ return importlib.import_module(name)
40
+ except ImportError as exc:
41
+ raise _missing_dependency(spec, name) from exc
42
+
43
+
44
+ def _progress(values: Sequence[str], *, enabled: bool, description: str) -> Iterable[str]:
45
+ if not enabled:
46
+ return values
47
+ from rich.progress import track
48
+
49
+ return track(values, description=description)
50
+
51
+
52
+ def _prepend_source_path(value: str | None) -> None:
53
+ if value is None:
54
+ return
55
+ root = Path(value).expanduser().resolve()
56
+ for candidate in (root, root / "src"):
57
+ if not candidate.is_dir():
58
+ continue
59
+ candidate_text = str(candidate)
60
+ if candidate_text not in sys.path:
61
+ sys.path.insert(0, candidate_text)
62
+
63
+
64
+ def _resolve_torch_device(torch: Any, requested: str) -> Any:
65
+ normalized = requested.strip().lower()
66
+ if normalized == "auto":
67
+ if bool(torch.cuda.is_available()):
68
+ normalized = "cuda"
69
+ elif hasattr(torch.backends, "mps") and bool(torch.backends.mps.is_available()):
70
+ normalized = "mps"
71
+ else:
72
+ normalized = "cpu"
73
+ try:
74
+ device = torch.device(normalized)
75
+ except (RuntimeError, TypeError) as exc:
76
+ raise ConfigurationError(
77
+ "Invalid PyTorch device.",
78
+ code="INVALID_REPRESENTATION_DEVICE",
79
+ context={"device": requested},
80
+ ) from exc
81
+ if device.type == "cuda" and not bool(torch.cuda.is_available()):
82
+ raise ConfigurationError(
83
+ "Requested CUDA device is unavailable.",
84
+ code="INVALID_REPRESENTATION_DEVICE",
85
+ context={"device": requested},
86
+ )
87
+ if device.type == "mps" and (
88
+ not hasattr(torch.backends, "mps") or not bool(torch.backends.mps.is_available())
89
+ ):
90
+ raise ConfigurationError(
91
+ "Requested MPS device is unavailable.",
92
+ code="INVALID_REPRESENTATION_DEVICE",
93
+ context={"device": requested},
94
+ )
95
+ return device
96
+
97
+
98
+ def _torch_dtype(torch: Any, value: str) -> Any | None:
99
+ if value == "auto":
100
+ return None
101
+ return {
102
+ "float32": torch.float32,
103
+ "float16": torch.float16,
104
+ "bfloat16": torch.bfloat16,
105
+ }[value]
106
+
107
+
108
+ def _resolve_hidden_states(outputs: Any, torch: Any) -> Any:
109
+ candidate = getattr(outputs, "last_hidden_state", None)
110
+ if candidate is not None:
111
+ return candidate
112
+ hidden_states = getattr(outputs, "hidden_states", None)
113
+ if hidden_states:
114
+ return hidden_states[-1]
115
+ if isinstance(outputs, tuple) and outputs and isinstance(outputs[0], torch.Tensor):
116
+ return outputs[0]
117
+ raise BackendExecutionError(
118
+ "Model output does not contain hidden representations.",
119
+ code="MISSING_MODEL_HIDDEN_STATES",
120
+ )
121
+
122
+
123
+ def _pool_hidden_states(
124
+ hidden_states: Any,
125
+ *,
126
+ torch: Any,
127
+ pooling: str,
128
+ attention_mask: Any | None = None,
129
+ special_tokens_mask: Any | None = None,
130
+ ) -> Any:
131
+ if hidden_states.ndim != 3:
132
+ raise BackendExecutionError(
133
+ "Expected hidden states with shape (batch, tokens, dimensions).",
134
+ code="INVALID_MODEL_HIDDEN_STATES",
135
+ context={"shape": tuple(int(value) for value in hidden_states.shape)},
136
+ )
137
+ if attention_mask is None:
138
+ mask = torch.ones(
139
+ hidden_states.shape[:2],
140
+ device=hidden_states.device,
141
+ dtype=torch.bool,
142
+ )
143
+ else:
144
+ mask = attention_mask.to(hidden_states.device).bool()
145
+ if special_tokens_mask is not None:
146
+ without_special = mask & (~special_tokens_mask.to(hidden_states.device).bool())
147
+ if without_special.any(dim=1).all():
148
+ mask = without_special
149
+ if pooling == "cls":
150
+ return hidden_states[:, 0, :]
151
+ if pooling == "last":
152
+ positions = torch.arange(hidden_states.shape[1], device=hidden_states.device)[None, :]
153
+ indices = positions.masked_fill(~mask, -1).max(dim=1).values.clamp_min(0)
154
+ gather_index = indices[:, None, None].expand(-1, 1, hidden_states.shape[-1])
155
+ return hidden_states.gather(1, gather_index).squeeze(1)
156
+ weights = mask.to(hidden_states.dtype).unsqueeze(-1)
157
+ if pooling == "mean":
158
+ return (hidden_states * weights).sum(dim=1) / weights.sum(dim=1).clamp_min(1.0)
159
+ if pooling == "max":
160
+ minimum = torch.finfo(hidden_states.dtype).min
161
+ return hidden_states.masked_fill(~mask.unsqueeze(-1), minimum).max(dim=1).values
162
+ raise AssertionError(f"Unsupported pooling method: {pooling}")
163
+
164
+
165
+ def _module_from_file(module_name: str, file_path: Path) -> Any:
166
+ module_spec = importlib.util.spec_from_file_location(module_name, str(file_path))
167
+ if module_spec is None or module_spec.loader is None:
168
+ raise ImportError(f"Could not construct module {module_name} from {file_path}.")
169
+ module = importlib.util.module_from_spec(module_spec)
170
+ sys.modules[module_name] = module
171
+ module_spec.loader.exec_module(module)
172
+ return module
173
+
174
+
175
+ def _unwrap_base_model(model: Any, torch: Any) -> Any:
176
+ for name in ("esm", "bert", "roberta", "transformer", "base_model", "model"):
177
+ candidate = getattr(model, name, None)
178
+ if isinstance(candidate, torch.nn.Module):
179
+ return candidate
180
+ return model
181
+
182
+
183
+ def _disable_fast_paths(model: Any, spec: DNAEmbeddingModel) -> None:
184
+ if spec.name == "dnabert2":
185
+ for module in tuple(sys.modules.values()):
186
+ module_file = getattr(module, "__file__", "") or ""
187
+ if spec.cache_name not in module_file and "DNABERT-2-117M" not in module_file:
188
+ continue
189
+ if hasattr(module, "flash_attn_qkvpacked_func"):
190
+ module.__dict__["flash_attn_qkvpacked_func"] = None
191
+ if spec.name == "caduceus":
192
+ for module in model.modules():
193
+ if hasattr(module, "use_fast_path"):
194
+ with suppress(AttributeError, RuntimeError):
195
+ module.use_fast_path = False
196
+
197
+
198
+ class _TransformersBackend:
199
+ def __init__(
200
+ self,
201
+ spec: DNAEmbeddingModel,
202
+ checkpoint: Path,
203
+ config: RepresentationConfig,
204
+ ) -> None:
205
+ self.spec = spec
206
+ self.checkpoint = checkpoint
207
+ self.config = config
208
+ self.torch = _require_module("torch", spec)
209
+ transformers = _require_module("transformers", spec)
210
+ self.device = _resolve_torch_device(self.torch, config.device)
211
+ self.tokenizer = self._load_tokenizer(transformers)
212
+ self.model = self._load_model(transformers)
213
+ requested_dtype = _torch_dtype(self.torch, config.dtype)
214
+ if requested_dtype is not None:
215
+ self.model = self.model.to(dtype=requested_dtype)
216
+ self.model = self.model.to(self.device)
217
+ self.model.eval()
218
+ self.window_length = self._window_length()
219
+
220
+ def _load_tokenizer(self, transformers: Any) -> Any:
221
+ if self.spec.name == "caduceus":
222
+ module = _module_from_file(
223
+ f"_dnakit_caduceus_tokenizer_{abs(hash(self.checkpoint))}",
224
+ self.checkpoint / "tokenization_caduceus.py",
225
+ )
226
+ tokenizer_class = module.CaduceusTokenizer
227
+ return tokenizer_class(
228
+ model_max_length=int(self.config.max_length or self.spec.chunk_length),
229
+ padding_side="right",
230
+ )
231
+ tokenizer = transformers.AutoTokenizer.from_pretrained(
232
+ str(self.checkpoint),
233
+ trust_remote_code=self.spec.trust_remote_code,
234
+ local_files_only=True,
235
+ )
236
+ tokenizer.padding_side = "right"
237
+ if getattr(tokenizer, "pad_token_id", None) is None:
238
+ fallback = (
239
+ getattr(tokenizer, "eos_token", None)
240
+ or getattr(tokenizer, "sep_token", None)
241
+ or getattr(tokenizer, "cls_token", None)
242
+ or getattr(tokenizer, "unk_token", None)
243
+ )
244
+ if fallback is None:
245
+ raise BackendExecutionError(
246
+ "The selected tokenizer has no usable padding token.",
247
+ code="MODEL_LOAD_FAILED",
248
+ context={"model": self.spec.name},
249
+ )
250
+ tokenizer.pad_token = fallback
251
+ return tokenizer
252
+
253
+ def _load_model(self, transformers: Any) -> Any:
254
+ errors: list[str] = []
255
+ model_config = None
256
+ if self.spec.name == "caduceus":
257
+ try:
258
+ model_config = transformers.AutoConfig.from_pretrained(
259
+ str(self.checkpoint),
260
+ trust_remote_code=True,
261
+ local_files_only=True,
262
+ )
263
+ model_config.fused_add_norm = False
264
+ except Exception as exc: # third-party configuration errors are reported together.
265
+ errors.append(f"AutoConfig: {type(exc).__name__}: {exc}")
266
+ kwargs = {
267
+ "trust_remote_code": self.spec.trust_remote_code,
268
+ "local_files_only": True,
269
+ }
270
+ if model_config is not None:
271
+ kwargs["config"] = model_config
272
+ for loader_name in ("AutoModel", "AutoModelForMaskedLM"):
273
+ loader = getattr(transformers, loader_name)
274
+ try:
275
+ model = loader.from_pretrained(str(self.checkpoint), **kwargs)
276
+ if loader_name == "AutoModelForMaskedLM":
277
+ model = _unwrap_base_model(model, self.torch)
278
+ _disable_fast_paths(model, self.spec)
279
+ return model
280
+ except Exception as exc: # preserve both official loader diagnostics.
281
+ errors.append(f"{loader_name}: {type(exc).__name__}: {exc}")
282
+ raise BackendExecutionError(
283
+ "Could not load the Transformers checkpoint.",
284
+ code="MODEL_LOAD_FAILED",
285
+ context={"model": self.spec.name, "errors": tuple(errors)},
286
+ )
287
+
288
+ def _window_length(self) -> int:
289
+ configured = int(self.config.max_length or self.spec.chunk_length)
290
+ model_max = getattr(self.tokenizer, "model_max_length", configured)
291
+ try:
292
+ model_max_int = int(model_max)
293
+ except (TypeError, ValueError, OverflowError):
294
+ model_max_int = configured
295
+ if not 0 < model_max_int <= 10_000_000:
296
+ model_max_int = configured
297
+ special_count = 0
298
+ with suppress(AttributeError, TypeError, ValueError):
299
+ special_count = int(self.tokenizer.num_special_tokens_to_add(pair=False))
300
+ return max(1, min(configured, model_max_int) - special_count)
301
+
302
+ def _special_token_count(self) -> int:
303
+ method = getattr(self.tokenizer, "num_special_tokens_to_add", None)
304
+ if method is None:
305
+ return 0
306
+ try:
307
+ return int(method(pair=False))
308
+ except (TypeError, ValueError):
309
+ return 0
310
+
311
+ def _chunks(self, sequence: str) -> tuple[list[Any], str]:
312
+ if self.spec.chunk_unit == "bp":
313
+ bp_chunks: list[Any] = [
314
+ sequence[start : start + self.window_length]
315
+ for start in range(0, len(sequence), self.window_length)
316
+ ]
317
+ return bp_chunks, "bp"
318
+ encoded = self.tokenizer(
319
+ sequence,
320
+ add_special_tokens=False,
321
+ return_attention_mask=False,
322
+ return_token_type_ids=False,
323
+ )
324
+ token_ids = list(encoded["input_ids"])
325
+ if not token_ids:
326
+ raise BackendExecutionError(
327
+ "The selected tokenizer produced no tokens for a non-empty sequence.",
328
+ code="EMPTY_MODEL_TOKENIZATION",
329
+ context={"model": self.spec.name},
330
+ )
331
+ token_chunks: list[Any] = [
332
+ token_ids[start : start + self.window_length]
333
+ for start in range(0, len(token_ids), self.window_length)
334
+ ]
335
+ return token_chunks, "token"
336
+
337
+ def _tokenize(self, chunks: list[Any], kind: str) -> dict[str, Any]:
338
+ if kind == "bp":
339
+ kwargs = {
340
+ "add_special_tokens": True,
341
+ "padding": True,
342
+ "truncation": True,
343
+ "max_length": self.window_length + self._special_token_count(),
344
+ "return_attention_mask": True,
345
+ "return_special_tokens_mask": True,
346
+ "return_tensors": "pt",
347
+ }
348
+ try:
349
+ batch = self.tokenizer(chunks, **kwargs)
350
+ except (TypeError, NotImplementedError):
351
+ kwargs.pop("return_special_tokens_mask")
352
+ batch = self.tokenizer(chunks, **kwargs)
353
+ else:
354
+ prepared = [
355
+ self.tokenizer.prepare_for_model(
356
+ token_ids,
357
+ add_special_tokens=True,
358
+ return_attention_mask=True,
359
+ return_token_type_ids=False,
360
+ truncation=True,
361
+ max_length=self.window_length + self._special_token_count(),
362
+ )
363
+ for token_ids in chunks
364
+ ]
365
+ batch = self.tokenizer.pad(prepared, padding=True, return_tensors="pt")
366
+ values = dict(batch)
367
+ values.pop("token_type_ids", None)
368
+ if "special_tokens_mask" not in values:
369
+ special_ids = tuple(getattr(self.tokenizer, "all_special_ids", ()) or ())
370
+ special_mask = self.torch.zeros_like(values["input_ids"], dtype=self.torch.long)
371
+ for token_id in special_ids:
372
+ special_mask |= values["input_ids"] == int(token_id)
373
+ values["special_tokens_mask"] = special_mask.long()
374
+ return values
375
+
376
+ def _encode_chunk_batch(self, chunks: list[Any], kind: str) -> Any:
377
+ batch = self._tokenize(chunks, kind)
378
+ special_tokens_mask = batch.pop("special_tokens_mask", None)
379
+ inputs = {
380
+ key: value.to(self.device)
381
+ for key, value in batch.items()
382
+ if isinstance(value, self.torch.Tensor)
383
+ }
384
+ with self.torch.inference_mode():
385
+ try:
386
+ outputs = self.model(**inputs)
387
+ except TypeError as exc:
388
+ if "attention_mask" not in str(exc):
389
+ raise
390
+ outputs = self.model(input_ids=inputs["input_ids"])
391
+ hidden_states = _resolve_hidden_states(outputs, self.torch)
392
+ return _pool_hidden_states(
393
+ hidden_states,
394
+ torch=self.torch,
395
+ pooling=self.config.pooling,
396
+ attention_mask=inputs.get("attention_mask"),
397
+ special_tokens_mask=(
398
+ None if special_tokens_mask is None else special_tokens_mask.to(self.device)
399
+ ),
400
+ )
401
+
402
+ def _encode_sequence(self, sequence: str) -> Any:
403
+ chunks, kind = self._chunks(sequence)
404
+ vectors: list[Any] = []
405
+ for start in range(0, len(chunks), self.config.batch_size):
406
+ vectors.append(
407
+ self._encode_chunk_batch(chunks[start : start + self.config.batch_size], kind)
408
+ )
409
+ return self.torch.cat(vectors, dim=0).mean(dim=0)
410
+
411
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
412
+ vectors = [
413
+ self._encode_sequence(sequence)
414
+ for sequence in _progress(
415
+ sequences,
416
+ enabled=show_progress,
417
+ description=f"Extracting {self.spec.display_name} rep",
418
+ )
419
+ ]
420
+ return self.torch.stack(vectors, dim=0).detach().float().cpu().numpy()
421
+
422
+
423
+ class _EnformerBackend:
424
+ def __init__(
425
+ self,
426
+ spec: DNAEmbeddingModel,
427
+ checkpoint: Path,
428
+ config: RepresentationConfig,
429
+ ) -> None:
430
+ self.spec = spec
431
+ self.config = config
432
+ self.torch = _require_module("torch", spec)
433
+ enformer_pytorch = _require_module("enformer_pytorch", spec)
434
+ self.device = _resolve_torch_device(self.torch, config.device)
435
+ try:
436
+ self.model = enformer_pytorch.from_pretrained(
437
+ str(checkpoint),
438
+ )
439
+ except Exception as exc:
440
+ raise BackendExecutionError(
441
+ "Could not load the Enformer checkpoint.",
442
+ code="MODEL_LOAD_FAILED",
443
+ context={"model": spec.name},
444
+ ) from exc
445
+ # ``enformer_pytorch.from_pretrained`` applies this correction only when
446
+ # it receives the remote repository ID. DNAKit intentionally loads from
447
+ # the resolved local directory, so enable the same official setting here.
448
+ for module in self.model.modules():
449
+ if hasattr(module, "use_tf_gamma"):
450
+ module.use_tf_gamma = True
451
+ dtype = _torch_dtype(self.torch, config.dtype)
452
+ if dtype is not None:
453
+ self.model = self.model.to(dtype=dtype)
454
+ self.model = self.model.to(self.device).eval()
455
+ self.chunk_length = int(config.max_length or spec.chunk_length)
456
+ lookup = self.torch.zeros(256, 4, dtype=self.torch.float32, device=self.device)
457
+ for base, vector in {
458
+ "A": (1.0, 0.0, 0.0, 0.0),
459
+ "C": (0.0, 1.0, 0.0, 0.0),
460
+ "G": (0.0, 0.0, 1.0, 0.0),
461
+ "T": (0.0, 0.0, 0.0, 1.0),
462
+ "N": (0.0, 0.0, 0.0, 0.0),
463
+ }.items():
464
+ lookup[ord(base)] = self.torch.tensor(vector, device=self.device)
465
+ self.lookup = lookup
466
+
467
+ def _encode_sequence(self, sequence: str) -> Any:
468
+ raw_chunks = [
469
+ sequence[start : start + self.chunk_length]
470
+ for start in range(0, len(sequence), self.chunk_length)
471
+ ]
472
+ chunks = [chunk + "N" * (self.chunk_length - len(chunk)) for chunk in raw_chunks]
473
+ vectors: list[Any] = []
474
+ for start in range(0, len(chunks), self.config.batch_size):
475
+ batch_chunks = chunks[start : start + self.config.batch_size]
476
+ encoded = self.torch.tensor(
477
+ [list(chunk.encode("ascii")) for chunk in batch_chunks],
478
+ dtype=self.torch.long,
479
+ device=self.device,
480
+ )
481
+ one_hot = self.lookup[encoded]
482
+ with self.torch.inference_mode():
483
+ hidden = self.model(one_hot, return_only_embeddings=True)
484
+ vectors.append(
485
+ _pool_hidden_states(
486
+ hidden,
487
+ torch=self.torch,
488
+ pooling=self.config.pooling,
489
+ )
490
+ )
491
+ return self.torch.cat(vectors, dim=0).mean(dim=0)
492
+
493
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
494
+ vectors = [
495
+ self._encode_sequence(sequence)
496
+ for sequence in _progress(
497
+ sequences,
498
+ enabled=show_progress,
499
+ description="Extracting Enformer rep",
500
+ )
501
+ ]
502
+ return self.torch.stack(vectors).detach().float().cpu().numpy()
503
+
504
+
505
+ def _clean_janusdna_config(config: dict[str, Any]) -> dict[str, Any]:
506
+ cleaned = dict(config)
507
+ cleaned.pop("_target_", None)
508
+ for key in (
509
+ "bidirectional",
510
+ "bidirectional_weight_tie",
511
+ "layer_fusion",
512
+ "final_attention",
513
+ "mid_single_direction_attention",
514
+ "bidirectional_attn_tie",
515
+ "gradient_checkpointing",
516
+ "output_router_logits",
517
+ ):
518
+ value = cleaned.get(key)
519
+ if isinstance(value, str):
520
+ cleaned[key] = value.lower().strip(", ") == "true"
521
+ cleaned.update(
522
+ {
523
+ "gradient_checkpointing": False,
524
+ "output_router_logits": False,
525
+ "use_cache": False,
526
+ "use_mamba_kernels": False,
527
+ "attn_implementation": "eager",
528
+ "final_attention_class": "eager",
529
+ }
530
+ )
531
+ return cleaned
532
+
533
+
534
+ class _JanusDNABackend:
535
+ def __init__(
536
+ self,
537
+ spec: DNAEmbeddingModel,
538
+ checkpoint: Path,
539
+ config: RepresentationConfig,
540
+ ) -> None:
541
+ self.spec = spec
542
+ self.config = config
543
+ source_path = None if config.model_source_path is None else str(config.model_source_path)
544
+ _prepend_source_path(source_path)
545
+ self.torch = _require_module("torch", spec)
546
+ try:
547
+ configuration_module = importlib.import_module("janusdna.configuration_janusdna")
548
+ modeling_module = importlib.import_module("janusdna.modeling_janusdna")
549
+ except ImportError as exc:
550
+ raise BackendUnavailableError(
551
+ "JanusDNA source code is required to load its official checkpoint.",
552
+ code="MISSING_NEURAL_DEPENDENCY",
553
+ hint=(
554
+ "Clone https://github.com/Qihao-Duan/JanusDNA and pass "
555
+ "model_source_path in RepresentationConfig."
556
+ ),
557
+ ) from exc
558
+ payload = json.loads((checkpoint / "model_config.json").read_text(encoding="utf-8"))
559
+ model_config = configuration_module.JanusDNAConfig(
560
+ **_clean_janusdna_config(dict(payload["config"]))
561
+ )
562
+ model_config._attn_implementation = "eager"
563
+ model_config.final_attention_class = "eager"
564
+ causal_model = modeling_module.JanusDNAForCausalLM(model_config)
565
+ state = self.torch.load(checkpoint / "checkpoints" / "last.ckpt", map_location="cpu")
566
+ state_dict = dict(state["state_dict"])
567
+ self.torch.nn.modules.utils.consume_prefix_in_state_dict_if_present(state_dict, "model.")
568
+ for key in tuple(state_dict):
569
+ if ".self_attn.o_projs.0." in key:
570
+ state_dict[key.replace(".self_attn.o_projs.0.", ".self_attn.o_proj.")] = (
571
+ state_dict.pop(key)
572
+ )
573
+ elif "torchmetrics" in key:
574
+ state_dict.pop(key)
575
+ missing, unexpected = causal_model.load_state_dict(state_dict, strict=False)
576
+ missing = [key for key in missing if not key.startswith("lm_head.")]
577
+ if missing or unexpected:
578
+ raise BackendExecutionError(
579
+ "JanusDNA checkpoint did not match the official model architecture.",
580
+ code="MODEL_LOAD_FAILED",
581
+ context={"missing": tuple(missing[:10]), "unexpected": tuple(unexpected[:10])},
582
+ )
583
+ self.device = _resolve_torch_device(self.torch, config.device)
584
+ self.model = causal_model.model
585
+ dtype = _torch_dtype(self.torch, config.dtype)
586
+ if dtype is not None:
587
+ self.model = self.model.to(dtype=dtype)
588
+ self.model = self.model.to(self.device).eval()
589
+ self.chunk_length = int(config.max_length or spec.chunk_length)
590
+ self.base_ids = {"A": 7, "C": 8, "G": 9, "T": 10, "N": 11}
591
+
592
+ def _encode_sequence(self, sequence: str) -> Any:
593
+ chunks = [
594
+ sequence[start : start + self.chunk_length]
595
+ for start in range(0, len(sequence), self.chunk_length)
596
+ ]
597
+ vectors: list[Any] = []
598
+ for start in range(0, len(chunks), self.config.batch_size):
599
+ batch_chunks = chunks[start : start + self.config.batch_size]
600
+ max_size = max(len(chunk) for chunk in batch_chunks) + 2
601
+ input_ids = self.torch.full(
602
+ (len(batch_chunks), max_size),
603
+ 4,
604
+ dtype=self.torch.long,
605
+ device=self.device,
606
+ )
607
+ attention_mask = self.torch.zeros_like(input_ids)
608
+ special_mask = self.torch.ones_like(input_ids)
609
+ for row, chunk in enumerate(batch_chunks):
610
+ ids = [0, *(self.base_ids[base] for base in chunk), 1]
611
+ input_ids[row, : len(ids)] = self.torch.tensor(ids, device=self.device)
612
+ attention_mask[row, : len(ids)] = 1
613
+ special_mask[row, 1 : len(ids) - 1] = 0
614
+ with self.torch.inference_mode():
615
+ outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
616
+ hidden = _resolve_hidden_states(outputs, self.torch)
617
+ vectors.append(
618
+ _pool_hidden_states(
619
+ hidden,
620
+ torch=self.torch,
621
+ pooling=self.config.pooling,
622
+ attention_mask=attention_mask,
623
+ special_tokens_mask=special_mask,
624
+ )
625
+ )
626
+ return self.torch.cat(vectors).mean(dim=0)
627
+
628
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
629
+ vectors = [
630
+ self._encode_sequence(sequence)
631
+ for sequence in _progress(
632
+ sequences,
633
+ enabled=show_progress,
634
+ description="Extracting JanusDNA rep",
635
+ )
636
+ ]
637
+ return self.torch.stack(vectors).detach().float().cpu().numpy()
638
+
639
+
640
+ class _Evo2Backend:
641
+ def __init__(
642
+ self,
643
+ spec: DNAEmbeddingModel,
644
+ checkpoint: Path,
645
+ config: RepresentationConfig,
646
+ ) -> None:
647
+ self.spec = spec
648
+ self.config = config
649
+ _prepend_source_path(
650
+ None if config.model_source_path is None else str(config.model_source_path)
651
+ )
652
+ self.torch = _require_module("torch", spec)
653
+ evo2_module = _require_module("evo2", spec)
654
+ checkpoint_file = checkpoint / "evo2_7b.pt"
655
+ try:
656
+ self.wrapper = evo2_module.Evo2(
657
+ model_name="evo2_7b",
658
+ local_path=str(checkpoint_file),
659
+ )
660
+ except Exception as exc:
661
+ raise BackendExecutionError(
662
+ "Could not load the Evo 2 checkpoint.",
663
+ code="MODEL_LOAD_FAILED",
664
+ ) from exc
665
+ self.model = self.wrapper.model.eval()
666
+ self.device = _resolve_torch_device(self.torch, config.device)
667
+ dtype = _torch_dtype(self.torch, config.dtype)
668
+ if dtype is not None:
669
+ self.model = self.model.to(dtype=dtype)
670
+ self.model = self.model.to(self.device)
671
+ self.chunk_length = int(config.max_length or spec.chunk_length)
672
+ if spec.evo2_layer is None:
673
+ raise AssertionError("Evo 2 registry entry is missing an embedding layer.")
674
+ self.layer = spec.evo2_layer
675
+
676
+ def _encode_sequence(self, sequence: str) -> Any:
677
+ chunks = [
678
+ sequence[start : start + self.chunk_length]
679
+ for start in range(0, len(sequence), self.chunk_length)
680
+ ]
681
+ vectors: list[Any] = []
682
+ for chunk in chunks:
683
+ input_ids = self.torch.tensor(
684
+ self.wrapper.tokenizer.tokenize(chunk),
685
+ dtype=self.torch.int,
686
+ device=self.device,
687
+ ).unsqueeze(0)
688
+ with self.torch.inference_mode():
689
+ _, embeddings = self.wrapper(
690
+ input_ids,
691
+ return_embeddings=True,
692
+ layer_names=[self.layer],
693
+ )
694
+ vectors.append(
695
+ _pool_hidden_states(
696
+ embeddings[self.layer],
697
+ torch=self.torch,
698
+ pooling=self.config.pooling,
699
+ ).squeeze(0)
700
+ )
701
+ return self.torch.stack(vectors).mean(dim=0)
702
+
703
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
704
+ vectors = [
705
+ self._encode_sequence(sequence)
706
+ for sequence in _progress(
707
+ sequences,
708
+ enabled=show_progress,
709
+ description="Extracting Evo 2 rep",
710
+ )
711
+ ]
712
+ return self.torch.stack(vectors).detach().float().cpu().numpy()
713
+
714
+
715
+ def _build_alphagenome_apply_fn(module: Any, metadata: Any, *, force_float32: bool) -> Any:
716
+ if not force_float32:
717
+ _, apply_fn, _ = module.create_model(metadata)
718
+ return apply_fn
719
+ hk = module.hk
720
+ jmp = module.jmp
721
+ research_model = module.model
722
+ policy = jmp.get_policy("params=float32,compute=float32,output=float32")
723
+
724
+ @hk.transform_with_state # type: ignore[untyped-decorator]
725
+ def forward(dna_sequence: Any, organism_index: Any) -> Any:
726
+ with hk.mixed_precision.push_policy(research_model.AlphaGenome, policy):
727
+ return research_model.AlphaGenome(
728
+ metadata,
729
+ num_splice_sites=research_model.DEFAULT_NUM_SPLICE_SITES,
730
+ splice_site_threshold=research_model.DEFAULT_SPLICE_SITE_THRESHOLD,
731
+ )(dna_sequence, organism_index)
732
+
733
+ def apply(params: Any, state: Any, dna_sequence: Any, organism_index: Any) -> Any:
734
+ (predictions, _), _ = forward.apply(
735
+ params,
736
+ state,
737
+ None,
738
+ dna_sequence,
739
+ organism_index,
740
+ )
741
+ return predictions
742
+
743
+ return apply
744
+
745
+
746
+ def _cast_jax_float32(tree: Any, *, jax: Any, jnp: Any) -> Any:
747
+ def cast(value: Any) -> Any:
748
+ dtype = getattr(value, "dtype", None)
749
+ if dtype is None:
750
+ return value
751
+ try:
752
+ if jnp.issubdtype(dtype, jnp.floating):
753
+ return value.astype(jnp.float32)
754
+ except TypeError:
755
+ pass
756
+ return value
757
+
758
+ return jax.tree_util.tree_map(cast, tree)
759
+
760
+
761
+ class _AlphaGenomeBackend:
762
+ def __init__(
763
+ self,
764
+ spec: DNAEmbeddingModel,
765
+ checkpoint: Path,
766
+ config: RepresentationConfig,
767
+ ) -> None:
768
+ self.spec = spec
769
+ self.config = config
770
+ _prepend_source_path(
771
+ None if config.model_source_path is None else str(config.model_source_path)
772
+ )
773
+ try:
774
+ jax = importlib.import_module("jax")
775
+ jnp = importlib.import_module("jax.numpy")
776
+ ocp = importlib.import_module("orbax.checkpoint")
777
+ api_dna_model = importlib.import_module("alphagenome.models.dna_model")
778
+ dna_model = importlib.import_module("alphagenome_research.model.dna_model")
779
+ metadata_lib = importlib.import_module("alphagenome_research.model.metadata.metadata")
780
+ except ImportError as exc:
781
+ raise _missing_dependency(spec, "alphagenome_research") from exc
782
+ self.jax = jax
783
+ self.jnp = jnp
784
+ self.api_dna_model = api_dna_model
785
+ devices = list(jax.devices())
786
+ requested = config.device.strip().lower()
787
+ if requested == "auto":
788
+ self.device = next(
789
+ (device for device in devices if device.platform == "gpu"),
790
+ devices[0],
791
+ )
792
+ else:
793
+ platform, separator, index_text = requested.partition(":")
794
+ if platform == "cuda":
795
+ platform = "gpu"
796
+ matching = [device for device in devices if device.platform == platform]
797
+ try:
798
+ device_index = 0 if not separator else int(index_text)
799
+ except ValueError as exc:
800
+ raise ConfigurationError(
801
+ "Invalid JAX device index.",
802
+ code="INVALID_REPRESENTATION_DEVICE",
803
+ context={"device": config.device},
804
+ ) from exc
805
+ if not matching or not 0 <= device_index < len(matching):
806
+ raise ConfigurationError(
807
+ "Requested JAX device is unavailable.",
808
+ code="INVALID_REPRESENTATION_DEVICE",
809
+ context={"device": config.device},
810
+ )
811
+ self.device = matching[device_index]
812
+ if config.dtype not in {"auto", "float32"}:
813
+ raise ConfigurationError(
814
+ "AlphaGenome controls mixed precision in its official JAX implementation.",
815
+ code="INVALID_REPRESENTATION_DTYPE",
816
+ hint='Use dtype="auto" or dtype="float32" for AlphaGenome.',
817
+ )
818
+ metadata = {organism: metadata_lib.load(organism) for organism in api_dna_model.Organism}
819
+ params, state = ocp.StandardCheckpointer().restore(
820
+ str(checkpoint),
821
+ target=None,
822
+ strict=False,
823
+ )
824
+ if self.device.platform != "gpu":
825
+ params = _cast_jax_float32(params, jax=jax, jnp=jnp)
826
+ state = _cast_jax_float32(state, jax=jax, jnp=jnp)
827
+ self.params = jax.device_put(params, self.device)
828
+ self.state = jax.device_put(state, self.device)
829
+ apply_fn = _build_alphagenome_apply_fn(
830
+ dna_model,
831
+ metadata,
832
+ force_float32=self.device.platform != "gpu",
833
+ )
834
+ self.apply_fn = jax.jit(apply_fn, device=self.device)
835
+ self.chunk_length = int(config.max_length or spec.chunk_length)
836
+ self.minimum_length = 2_048
837
+ self.alignment = 2_048
838
+
839
+ def _pad(self, sequence: str) -> str:
840
+ size = max(len(sequence), self.minimum_length)
841
+ size = int(math.ceil(size / self.alignment) * self.alignment)
842
+ size = min(size, self.chunk_length)
843
+ return sequence[:size] + "N" * max(0, size - len(sequence))
844
+
845
+ @staticmethod
846
+ def _one_hot(sequence: str) -> Any:
847
+ import numpy as np
848
+
849
+ lookup = {
850
+ "A": (1.0, 0.0, 0.0, 0.0),
851
+ "C": (0.0, 1.0, 0.0, 0.0),
852
+ "G": (0.0, 0.0, 1.0, 0.0),
853
+ "T": (0.0, 0.0, 0.0, 1.0),
854
+ "N": (0.0, 0.0, 0.0, 0.0),
855
+ }
856
+ return np.asarray([lookup[base] for base in sequence], dtype=np.float32)[None, ...]
857
+
858
+ def _pool(self, embeddings: Any) -> Any:
859
+ import numpy as np
860
+
861
+ values = np.asarray(embeddings, dtype=np.float32)
862
+ if self.config.pooling == "mean":
863
+ return values.mean(axis=1).squeeze(0)
864
+ if self.config.pooling == "max":
865
+ return values.max(axis=1).squeeze(0)
866
+ if self.config.pooling == "cls":
867
+ return values[:, 0, :].squeeze(0)
868
+ return values[:, -1, :].squeeze(0)
869
+
870
+ def _encode_sequence(self, sequence: str) -> Any:
871
+ import numpy as np
872
+
873
+ chunks = [
874
+ sequence[start : start + self.chunk_length]
875
+ for start in range(0, len(sequence), self.chunk_length)
876
+ ]
877
+ vectors = []
878
+ for chunk in chunks:
879
+ padded = self._pad(chunk)
880
+ dna = self.jax.device_put(self._one_hot(padded), self.device)
881
+ organism = self.jax.device_put(
882
+ self.jnp.asarray(
883
+ [self.api_dna_model.Organism.HOMO_SAPIENS.value],
884
+ dtype=self.jnp.int32,
885
+ ),
886
+ self.device,
887
+ )
888
+ predictions = self.apply_fn(self.params, self.state, dna, organism)
889
+ vectors.append(self._pool(predictions["embeddings_1bp"]))
890
+ return np.stack(vectors).mean(axis=0)
891
+
892
+ def extract(self, sequences: Sequence[str], *, show_progress: bool) -> Any:
893
+ import numpy as np
894
+
895
+ return np.stack(
896
+ [
897
+ self._encode_sequence(sequence)
898
+ for sequence in _progress(
899
+ sequences,
900
+ enabled=show_progress,
901
+ description="Extracting AlphaGenome rep",
902
+ )
903
+ ]
904
+ ).astype(np.float32, copy=False)
905
+
906
+
907
+ def create_representation_backend(
908
+ spec: DNAEmbeddingModel,
909
+ checkpoint: str | Path,
910
+ config: RepresentationConfig,
911
+ ) -> RepresentationBackend:
912
+ """Create one lazy backend for a locally resolved official checkpoint."""
913
+
914
+ path = Path(checkpoint).expanduser().resolve()
915
+ if spec.loader == "transformers":
916
+ return _TransformersBackend(spec, path, config)
917
+ if spec.loader == "enformer":
918
+ return _EnformerBackend(spec, path, config)
919
+ if spec.loader == "janusdna":
920
+ return _JanusDNABackend(spec, path, config)
921
+ if spec.loader == "evo2":
922
+ return _Evo2Backend(spec, path, config)
923
+ if spec.loader == "alphagenome":
924
+ return _AlphaGenomeBackend(spec, path, config)
925
+ raise AssertionError(f"Unsupported model loader: {spec.loader}")
926
+
927
+
928
+ __all__ = ["RepresentationBackend", "create_representation_backend"]