bioforge 2.3.0__py3-none-win_amd64.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.
@@ -0,0 +1 @@
1
+ # engine package — motor C de alto rendimiento
@@ -0,0 +1,543 @@
1
+ """
2
+ engine/_loader.py — Carga el motor C y expone funciones Python.
3
+
4
+ Si el DLL no está compilado, C_AVAILABLE = False y los módulos
5
+ usan el fallback NumPy automáticamente.
6
+ """
7
+
8
+ import ctypes
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+
14
+ _ENGINE_DIR = Path(__file__).parent
15
+ _DLL_PATH = _ENGINE_DIR / ("engine.dll" if sys.platform == "win32" else "engine.so")
16
+
17
+ # ── Tipos ctypes frecuentes ────────────────────────────────────────────────────
18
+ _U8P = ctypes.POINTER(ctypes.c_uint8)
19
+ _I32P = ctypes.POINTER(ctypes.c_int32)
20
+ _I32 = ctypes.c_int32
21
+ _I64 = ctypes.c_int64
22
+ _CHARP = ctypes.c_char_p
23
+
24
+ # ── Carga del DLL ──────────────────────────────────────────────────────────────
25
+ _lib: ctypes.CDLL | None = None
26
+ C_AVAILABLE: bool = False
27
+
28
+ def _load() -> bool:
29
+ global _lib, C_AVAILABLE
30
+ if not _DLL_PATH.exists():
31
+ return False
32
+ try:
33
+ _lib = ctypes.CDLL(str(_DLL_PATH))
34
+ _setup_signatures()
35
+ C_AVAILABLE = True
36
+ return True
37
+ except Exception:
38
+ return False
39
+
40
+
41
+ def _setup_signatures() -> None:
42
+ assert _lib is not None
43
+
44
+ # ── bio_getitem5 ───────────────────────────────────────────────────────────
45
+ _lib.bio_getitem5.restype = ctypes.c_uint8
46
+ _lib.bio_getitem5.argtypes = [_U8P, _I32]
47
+
48
+ # ── bio_pack5 ─────────────────────────────────────────────────────────────
49
+ _lib.bio_pack5.restype = None
50
+ _lib.bio_pack5.argtypes = [_U8P, _I32, _U8P]
51
+
52
+ # ── bio_unpack5 ───────────────────────────────────────────────────────────
53
+ _lib.bio_unpack5.restype = None
54
+ _lib.bio_unpack5.argtypes = [_U8P, _I32, _U8P]
55
+
56
+ # ── bio_find_atg ──────────────────────────────────────────────────────────
57
+ _lib.bio_find_atg.restype = _I32
58
+ _lib.bio_find_atg.argtypes = [_U8P, _I32]
59
+
60
+ # ── bio_translate ─────────────────────────────────────────────────────────
61
+ _lib.bio_translate.restype = None
62
+ _lib.bio_translate.argtypes = [_U8P, _U8P, _I32, _U8P]
63
+
64
+ # ── nw_global / nw_semiglobal (misma firma) ────────────────────────────────
65
+ _nw_args = [
66
+ _U8P, _I32, # codes_a, m
67
+ _U8P, _I32, # codes_b, n
68
+ _CHARP, # decode (32 bytes)
69
+ _I32, _I32, _I32, # match, mismatch, gap
70
+ _CHARP, _CHARP, # out_a, out_b
71
+ _I32P, _I32P, _I32P, _I32P, # score, matches, mismatches, gaps
72
+ ]
73
+ _lib.nw_global.restype = _I32
74
+ _lib.nw_global.argtypes = _nw_args
75
+ _lib.nw_semiglobal.restype = _I32
76
+ _lib.nw_semiglobal.argtypes = _nw_args
77
+
78
+ # ── sw_align (Smith-Waterman, misma firma que nw) ──────────────────────────
79
+ _lib.sw_align.restype = _I32
80
+ _lib.sw_align.argtypes = _nw_args
81
+
82
+ # ── nw_banded / nw_banded_semiglobal (band extra) ─────────────────────────
83
+ _nw_banded_args = [
84
+ _U8P, _I32, # codes_a, m
85
+ _U8P, _I32, # codes_b, n
86
+ _CHARP, # decode
87
+ _I32, _I32, _I32, _I32, # match, mismatch, gap, band
88
+ _CHARP, _CHARP, # out_a, out_b
89
+ _I32P, _I32P, _I32P, _I32P,
90
+ ]
91
+ _lib.nw_banded.restype = _I32
92
+ _lib.nw_banded.argtypes = _nw_banded_args
93
+ _lib.nw_banded_semiglobal.restype = _I32
94
+ _lib.nw_banded_semiglobal.argtypes = _nw_banded_args
95
+
96
+
97
+ _load()
98
+
99
+ # ── Verificar si el motor tiene las funciones del parser (requiere recompilación) ─
100
+ C_PARSER_AVAILABLE: bool = False
101
+
102
+ def _check_parser() -> None:
103
+ global C_PARSER_AVAILABLE
104
+ if not C_AVAILABLE or _lib is None:
105
+ return
106
+ try:
107
+ # in_dll fuerza la resolución del símbolo en el DLL ahora mismo.
108
+ # Si el DLL es antiguo (sin bio_parser_open), lanza OSError aquí
109
+ # en vez de colapsar más tarde al llamar la función.
110
+ ctypes.c_void_p.in_dll(_lib, "bio_parser_open")
111
+ _lib.bio_parser_open.restype = ctypes.c_void_p
112
+ _lib.bio_parser_open.argtypes = [ctypes.c_char_p]
113
+
114
+ _lib.bio_parser_next.restype = _I32
115
+ _lib.bio_parser_next.argtypes = [
116
+ ctypes.c_void_p, # handle
117
+ ctypes.c_char_p, _I32, # hdr, hdr_max
118
+ _U8P, _I32, _I32P, # codes, codes_max, n_out
119
+ _I32, # force_type (-1 auto | 0 nuc | 1 prot)
120
+ _I32P, # type_out
121
+ _U8P, _I32P, # qual, qual_out (NULL para FASTA)
122
+ ]
123
+
124
+ _lib.bio_parser_close.restype = None
125
+ _lib.bio_parser_close.argtypes = [ctypes.c_void_p]
126
+
127
+ C_PARSER_AVAILABLE = True
128
+ except (AttributeError, OSError):
129
+ pass
130
+
131
+
132
+ C_BATCH_AVAILABLE: bool = False
133
+
134
+ def _check_batch() -> None:
135
+ """El parser por lotes es opcional: DLLs antiguos solo tienen next()."""
136
+ global C_BATCH_AVAILABLE
137
+ if not C_PARSER_AVAILABLE or _lib is None:
138
+ return
139
+ try:
140
+ ctypes.c_void_p.in_dll(_lib, "bio_parser_next_batch")
141
+ _lib.bio_parser_next_batch.restype = _I32
142
+ _lib.bio_parser_next_batch.argtypes = [
143
+ ctypes.c_void_p, _I32, _I32, # handle, max_records, force_type
144
+ ctypes.c_char_p, _I32, _I32P, # hdr_buf, hdr_buf_max, hdr_off
145
+ _U8P, _I32, _I32P, # pack_buf, pack_buf_max, pack_off
146
+ _I32P, _I32P, # n_syms, types
147
+ _U8P, _I32, _I32P, # qual_buf, qual_buf_max, qual_off
148
+ ]
149
+ C_BATCH_AVAILABLE = True
150
+ except (AttributeError, OSError):
151
+ pass
152
+
153
+
154
+ C_PARALLEL_AVAILABLE: bool = False
155
+
156
+ def _check_parallel() -> None:
157
+ """Parser paralelo en memoria (OpenMP). Opcional."""
158
+ global C_PARALLEL_AVAILABLE
159
+ if not C_BATCH_AVAILABLE or _lib is None:
160
+ return
161
+ try:
162
+ ctypes.c_void_p.in_dll(_lib, "bio_parse_mem_parallel")
163
+ _lib.bio_parse_mem_parallel.restype = _I32
164
+ _lib.bio_parse_mem_parallel.argtypes = [
165
+ _U8P, _I64, _I32, _I32, _I32, # data, len, fmt, n_threads, force_type
166
+ ctypes.c_char_p, _I32, _I32P, # hdr_buf, hdr_buf_max, hdr_off
167
+ _U8P, _I64, _I32P, # pack_buf, pack_buf_max, pack_off
168
+ _I32P, _I32P, # n_syms, types
169
+ _U8P, _I64, _I32P, # qual_buf, qual_buf_max, qual_off
170
+ _I32, # max_records
171
+ ]
172
+ C_PARALLEL_AVAILABLE = True
173
+ except (AttributeError, OSError):
174
+ pass
175
+
176
+
177
+ C_LIBDEFLATE_AVAILABLE: bool = False
178
+
179
+ def _check_libdeflate() -> None:
180
+ """Descompresor gzip rápido (libdeflate). Opcional."""
181
+ global C_LIBDEFLATE_AVAILABLE
182
+ if not C_AVAILABLE or _lib is None:
183
+ return
184
+ try:
185
+ ctypes.c_void_p.in_dll(_lib, "bio_has_libdeflate")
186
+ _lib.bio_has_libdeflate.restype = ctypes.c_int
187
+ _lib.bio_has_libdeflate.argtypes = []
188
+ _lib.bio_gzip_decompress.restype = _I64
189
+ _lib.bio_gzip_decompress.argtypes = [_U8P, _I64, _U8P, _I64]
190
+ _lib.bio_is_bgzf.restype = ctypes.c_int
191
+ _lib.bio_is_bgzf.argtypes = [_U8P, _I64]
192
+ _lib.bio_bgzf_usize.restype = _I64
193
+ _lib.bio_bgzf_usize.argtypes = [_U8P, _I64]
194
+ _lib.bio_bgzf_decompress_parallel.restype = _I64
195
+ _lib.bio_bgzf_decompress_parallel.argtypes = [_U8P, _I64, _U8P, _I64, _I32]
196
+ _lib.bio_bgzf_compress.restype = _I64
197
+ _lib.bio_bgzf_compress.argtypes = [_U8P, _I64, _U8P, _I64, _I32, _I32]
198
+ C_LIBDEFLATE_AVAILABLE = bool(_lib.bio_has_libdeflate())
199
+ except (AttributeError, OSError):
200
+ pass
201
+
202
+
203
+ _check_parser()
204
+ _check_batch()
205
+ _check_parallel()
206
+ _check_libdeflate()
207
+
208
+
209
+ # ── Wrappers Python ────────────────────────────────────────────────────────────
210
+
211
+ def c_getitem5(packed: np.ndarray, i: int) -> int:
212
+ return int(_lib.bio_getitem5(
213
+ packed.ctypes.data_as(_U8P),
214
+ _I32(i),
215
+ ))
216
+
217
+
218
+ def c_pack5(codes: np.ndarray) -> np.ndarray:
219
+ n = len(codes)
220
+ out_len = (n * 5 + 7) // 8 + 1 # +1 para lecturas seguras
221
+ out = np.zeros(out_len, dtype=np.uint8)
222
+ _lib.bio_pack5(
223
+ codes.ctypes.data_as(_U8P),
224
+ _I32(n),
225
+ out.ctypes.data_as(_U8P),
226
+ )
227
+ return out[:out_len - 1] # recortar el byte extra
228
+
229
+
230
+ def c_unpack5(packed: np.ndarray, n: int) -> np.ndarray:
231
+ # bio_unpack5 es seguro en los límites → no hace falta copiar un byte extra.
232
+ # ascontiguousarray no copia si ya es uint8 contiguo (el caso normal).
233
+ safe = np.ascontiguousarray(packed, dtype=np.uint8)
234
+ out = np.empty(n, dtype=np.uint8)
235
+ _lib.bio_unpack5(
236
+ safe.ctypes.data_as(_U8P),
237
+ _I32(n),
238
+ out.ctypes.data_as(_U8P),
239
+ )
240
+ return out
241
+
242
+
243
+ def c_find_atg(codes: np.ndarray) -> int:
244
+ """Devuelve el indice del primer ATG en codes, o -1 si no existe."""
245
+ safe = np.ascontiguousarray(codes, dtype=np.uint8)
246
+ return int(_lib.bio_find_atg(safe.ctypes.data_as(_U8P), _I32(len(safe))))
247
+
248
+
249
+ def c_translate(codon_lut: np.ndarray, nuc_codes: np.ndarray, n_codons: int) -> np.ndarray:
250
+ """Traduce n_codons codones usando el LUT; devuelve array uint8 de AAs."""
251
+ lut = np.ascontiguousarray(codon_lut, dtype=np.uint8)
252
+ safe = np.ascontiguousarray(nuc_codes[:n_codons * 3], dtype=np.uint8)
253
+ out = np.empty(n_codons, dtype=np.uint8)
254
+ _lib.bio_translate(
255
+ lut.ctypes.data_as(_U8P),
256
+ safe.ctypes.data_as(_U8P),
257
+ _I32(n_codons),
258
+ out.ctypes.data_as(_U8P),
259
+ )
260
+ return out
261
+
262
+
263
+ def c_sw_align(
264
+ codes_a: np.ndarray,
265
+ codes_b: np.ndarray,
266
+ decode_bytes: bytes,
267
+ match: int, mismatch: int, gap: int,
268
+ ) -> tuple[str, str, int, int, int, int]:
269
+ """Smith-Waterman local alignment en C."""
270
+ m, n = len(codes_a), len(codes_b)
271
+ buf_size = m + n + 2
272
+ out_a = ctypes.create_string_buffer(buf_size)
273
+ out_b = ctypes.create_string_buffer(buf_size)
274
+ score = _I32(0); nm = _I32(0); nmi = _I32(0); ng = _I32(0)
275
+ ca = np.ascontiguousarray(codes_a, dtype=np.uint8)
276
+ cb = np.ascontiguousarray(codes_b, dtype=np.uint8)
277
+ aln_len = _lib.sw_align(
278
+ ca.ctypes.data_as(_U8P), _I32(m),
279
+ cb.ctypes.data_as(_U8P), _I32(n),
280
+ decode_bytes,
281
+ _I32(match), _I32(mismatch), _I32(gap),
282
+ out_a, out_b,
283
+ ctypes.byref(score), ctypes.byref(nm),
284
+ ctypes.byref(nmi), ctypes.byref(ng),
285
+ )
286
+ if aln_len < 0:
287
+ raise MemoryError("Motor C: fallo de memoria en SW")
288
+ return (
289
+ out_a.value.decode("ascii"), out_b.value.decode("ascii"),
290
+ score.value, nm.value, nmi.value, ng.value,
291
+ )
292
+
293
+
294
+ def c_nw_banded(
295
+ codes_a: np.ndarray,
296
+ codes_b: np.ndarray,
297
+ decode_bytes: bytes,
298
+ match: int, mismatch: int, gap: int,
299
+ band: int, mode: str,
300
+ ) -> tuple[str, str, int, int, int, int]:
301
+ """Banded NW en C. Memoria O(m*band)."""
302
+ m, n = len(codes_a), len(codes_b)
303
+ buf_size = m + n + 2
304
+ out_a = ctypes.create_string_buffer(buf_size)
305
+ out_b = ctypes.create_string_buffer(buf_size)
306
+ score = _I32(0); nm = _I32(0); nmi = _I32(0); ng = _I32(0)
307
+ ca = np.ascontiguousarray(codes_a, dtype=np.uint8)
308
+ cb = np.ascontiguousarray(codes_b, dtype=np.uint8)
309
+ fn = _lib.nw_banded_semiglobal if mode == "semi-global" else _lib.nw_banded
310
+ aln_len = fn(
311
+ ca.ctypes.data_as(_U8P), _I32(m),
312
+ cb.ctypes.data_as(_U8P), _I32(n),
313
+ decode_bytes,
314
+ _I32(match), _I32(mismatch), _I32(gap), _I32(band),
315
+ out_a, out_b,
316
+ ctypes.byref(score), ctypes.byref(nm),
317
+ ctypes.byref(nmi), ctypes.byref(ng),
318
+ )
319
+ if aln_len < 0:
320
+ raise MemoryError("Motor C: fallo de memoria en NW banded")
321
+ return (
322
+ out_a.value.decode("ascii"), out_b.value.decode("ascii"),
323
+ score.value, nm.value, nmi.value, ng.value,
324
+ )
325
+
326
+
327
+ def c_parser_open(path: str) -> int:
328
+ """Abre un archivo FASTA/FASTQ y devuelve un handle opaco (c_void_p)."""
329
+ raw = path.encode("utf-8") if isinstance(path, str) else path
330
+ return _lib.bio_parser_open(raw) # devuelve c_void_p (int en Python)
331
+
332
+
333
+ def c_parser_next(
334
+ handle: int,
335
+ hdr_buf: "ctypes.Array",
336
+ codes_buf: np.ndarray,
337
+ force_type: int,
338
+ ) -> "tuple[int, int, int]":
339
+ """Lee el siguiente registro FASTA.
340
+ Retorna (ret, n_symbols, seq_type): ret 1=OK 0=EOF -1=error -2=overflow."""
341
+ n_out = _I32(0)
342
+ type_out = _I32(0)
343
+ ret = _lib.bio_parser_next(
344
+ ctypes.c_void_p(handle),
345
+ hdr_buf, _I32(len(hdr_buf)),
346
+ codes_buf.ctypes.data_as(_U8P), _I32(len(codes_buf)),
347
+ ctypes.byref(n_out),
348
+ _I32(force_type),
349
+ ctypes.byref(type_out),
350
+ None, None, # sin calidades — FASTA
351
+ )
352
+ return ret, n_out.value, type_out.value
353
+
354
+
355
+ def c_parser_next_fastq(
356
+ handle: int,
357
+ hdr_buf: "ctypes.Array",
358
+ codes_buf: np.ndarray,
359
+ qual_buf: np.ndarray,
360
+ ) -> "tuple[int, int, int]":
361
+ """Lee el siguiente registro FASTQ.
362
+ Retorna (ret, n_symbols, n_qual): ret 1=OK 0=EOF -1=error -2=overflow."""
363
+ n_out = _I32(0)
364
+ type_out = _I32(0)
365
+ q_out = _I32(0)
366
+ ret = _lib.bio_parser_next(
367
+ ctypes.c_void_p(handle),
368
+ hdr_buf, _I32(len(hdr_buf)),
369
+ codes_buf.ctypes.data_as(_U8P), _I32(len(codes_buf)),
370
+ ctypes.byref(n_out),
371
+ _I32(0), # FASTQ siempre nucleótido
372
+ ctypes.byref(type_out),
373
+ qual_buf.ctypes.data_as(_U8P), ctypes.byref(q_out),
374
+ )
375
+ return ret, n_out.value, q_out.value
376
+
377
+
378
+ def c_parser_next_batch(
379
+ handle: int,
380
+ max_records: int,
381
+ force_type: int,
382
+ hdr_buf: "ctypes.Array",
383
+ hdr_off: np.ndarray,
384
+ pack_buf: np.ndarray,
385
+ pack_off: np.ndarray,
386
+ n_syms: np.ndarray,
387
+ types: np.ndarray,
388
+ qual_buf: "np.ndarray | None" = None,
389
+ qual_off: "np.ndarray | None" = None,
390
+ ) -> int:
391
+ """Parsea hasta ``max_records`` registros en una sola llamada.
392
+
393
+ Empaqueta cada secuencia a 5-bit dentro de C. Rellena los arrays de salida
394
+ (que el llamante reutiliza entre lotes). Retorna el nº de registros
395
+ parseados (>=0), 0 = EOF, o negativo en error (-2 = registro demasiado
396
+ grande para el buffer).
397
+ """
398
+ q_ptr = qual_buf.ctypes.data_as(_U8P) if qual_buf is not None else None
399
+ q_max = _I32(len(qual_buf)) if qual_buf is not None else _I32(0)
400
+ q_off_ptr = qual_off.ctypes.data_as(_I32P) if qual_off is not None else None
401
+ return _lib.bio_parser_next_batch(
402
+ ctypes.c_void_p(handle), _I32(max_records), _I32(force_type),
403
+ hdr_buf, _I32(len(hdr_buf)), hdr_off.ctypes.data_as(_I32P),
404
+ pack_buf.ctypes.data_as(_U8P), _I32(len(pack_buf)),
405
+ pack_off.ctypes.data_as(_I32P),
406
+ n_syms.ctypes.data_as(_I32P), types.ctypes.data_as(_I32P),
407
+ q_ptr, q_max, q_off_ptr,
408
+ )
409
+
410
+
411
+ def c_parse_mem_parallel(
412
+ data: np.ndarray,
413
+ fmt: int,
414
+ n_threads: int,
415
+ force_type: int,
416
+ hdr_buf: "ctypes.Array",
417
+ hdr_off: np.ndarray,
418
+ pack_buf: np.ndarray,
419
+ pack_off: np.ndarray,
420
+ n_syms: np.ndarray,
421
+ types: np.ndarray,
422
+ qual_buf: "np.ndarray | None",
423
+ qual_off: "np.ndarray | None",
424
+ max_records: int,
425
+ ) -> int:
426
+ """Parsea un bloque de memoria (solo registros completos) en paralelo.
427
+
428
+ ``data`` es un array uint8 contiguo. Rellena los buffers de salida (que el
429
+ llamante reutiliza). Devuelve nº de registros (>=0), o negativo en error.
430
+ """
431
+ q_ptr = qual_buf.ctypes.data_as(_U8P) if qual_buf is not None else None
432
+ q_max = _I64(len(qual_buf)) if qual_buf is not None else _I64(0)
433
+ q_off = qual_off.ctypes.data_as(_I32P) if qual_off is not None else None
434
+ return _lib.bio_parse_mem_parallel(
435
+ data.ctypes.data_as(_U8P), _I64(len(data)),
436
+ _I32(fmt), _I32(n_threads), _I32(force_type),
437
+ hdr_buf, _I32(len(hdr_buf)), hdr_off.ctypes.data_as(_I32P),
438
+ pack_buf.ctypes.data_as(_U8P), _I64(len(pack_buf)),
439
+ pack_off.ctypes.data_as(_I32P),
440
+ n_syms.ctypes.data_as(_I32P), types.ctypes.data_as(_I32P),
441
+ q_ptr, q_max, q_off,
442
+ _I32(max_records),
443
+ )
444
+
445
+
446
+ def c_gzip_decompress(cbuf: np.ndarray, obuf: np.ndarray) -> int:
447
+ """Descomprime gzip ``cbuf`` (uint8) en ``obuf`` (uint8) con libdeflate.
448
+
449
+ Devuelve nº de bytes descomprimidos, o -1 si no caben o hay error.
450
+ """
451
+ return int(_lib.bio_gzip_decompress(
452
+ cbuf.ctypes.data_as(_U8P), _I64(len(cbuf)),
453
+ obuf.ctypes.data_as(_U8P), _I64(len(obuf)),
454
+ ))
455
+
456
+
457
+ def c_is_bgzf(cbuf: np.ndarray) -> bool:
458
+ """True si el buffer comprimido tiene formato BGZF (gzip por bloques)."""
459
+ return bool(_lib.bio_is_bgzf(cbuf.ctypes.data_as(_U8P), _I64(len(cbuf))))
460
+
461
+
462
+ def c_bgzf_usize(cbuf: np.ndarray) -> int:
463
+ """Tamaño total descomprimido de un BGZF, o -1 si malformado."""
464
+ return int(_lib.bio_bgzf_usize(cbuf.ctypes.data_as(_U8P), _I64(len(cbuf))))
465
+
466
+
467
+ def c_bgzf_decompress_parallel(cbuf: np.ndarray, obuf: np.ndarray,
468
+ n_threads: int) -> int:
469
+ """Descomprime un BGZF en paralelo (bloques independientes).
470
+
471
+ Devuelve nº de bytes descomprimidos, o -1 en error.
472
+ """
473
+ return int(_lib.bio_bgzf_decompress_parallel(
474
+ cbuf.ctypes.data_as(_U8P), _I64(len(cbuf)),
475
+ obuf.ctypes.data_as(_U8P), _I64(len(obuf)), _I32(n_threads),
476
+ ))
477
+
478
+
479
+ def c_bgzf_compress(inbuf: np.ndarray, obuf: np.ndarray,
480
+ level: int, n_threads: int) -> int:
481
+ """Comprime ``inbuf`` a BGZF en paralelo. Devuelve bytes comprimidos o -1."""
482
+ return int(_lib.bio_bgzf_compress(
483
+ inbuf.ctypes.data_as(_U8P), _I64(len(inbuf)),
484
+ obuf.ctypes.data_as(_U8P), _I64(len(obuf)),
485
+ _I32(level), _I32(n_threads),
486
+ ))
487
+
488
+
489
+ def c_parser_close(handle: int) -> None:
490
+ """Libera el handle del parser y cierra el archivo."""
491
+ _lib.bio_parser_close(ctypes.c_void_p(handle))
492
+
493
+
494
+ def c_nw_align(
495
+ codes_a: np.ndarray,
496
+ codes_b: np.ndarray,
497
+ decode_bytes: bytes,
498
+ match: int, mismatch: int, gap: int,
499
+ mode: str,
500
+ ) -> tuple[str, str, int, int, int, int]:
501
+ """
502
+ Alineamiento NW completo en C.
503
+ Devuelve (aligned_a, aligned_b, score, n_matches, n_mismatches, n_gaps).
504
+ """
505
+ m, n = len(codes_a), len(codes_b)
506
+ buf_size = m + n + 2
507
+
508
+ out_a = ctypes.create_string_buffer(buf_size)
509
+ out_b = ctypes.create_string_buffer(buf_size)
510
+ score = _I32(0)
511
+ nm = _I32(0)
512
+ nmi = _I32(0)
513
+ ng = _I32(0)
514
+
515
+ # Asegurar que los arrays son C-contiguos uint8
516
+ ca = np.ascontiguousarray(codes_a, dtype=np.uint8)
517
+ cb = np.ascontiguousarray(codes_b, dtype=np.uint8)
518
+
519
+ fn = _lib.nw_semiglobal if mode == "semi-global" else _lib.nw_global
520
+
521
+ aln_len = fn(
522
+ ca.ctypes.data_as(_U8P), _I32(m),
523
+ cb.ctypes.data_as(_U8P), _I32(n),
524
+ decode_bytes,
525
+ _I32(match), _I32(mismatch), _I32(gap),
526
+ out_a, out_b,
527
+ ctypes.byref(score),
528
+ ctypes.byref(nm),
529
+ ctypes.byref(nmi),
530
+ ctypes.byref(ng),
531
+ )
532
+
533
+ if aln_len < 0:
534
+ raise MemoryError("Motor C: fallo de asignación de memoria en NW")
535
+
536
+ return (
537
+ out_a.value.decode("ascii"),
538
+ out_b.value.decode("ascii"),
539
+ score.value,
540
+ nm.value,
541
+ nmi.value,
542
+ ng.value,
543
+ )