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.
- bioforge-2.3.0.data/purelib/bioforge/__init__.py +74 -0
- bioforge-2.3.0.data/purelib/bioforge/aligner.py +953 -0
- bioforge-2.3.0.data/purelib/bioforge/analyze.py +385 -0
- bioforge-2.3.0.data/purelib/bioforge/bgzf.py +119 -0
- bioforge-2.3.0.data/purelib/bioforge/biocore.py +2092 -0
- bioforge-2.3.0.data/purelib/bioforge/engine/__init__.py +1 -0
- bioforge-2.3.0.data/purelib/bioforge/engine/_loader.py +543 -0
- bioforge-2.3.0.data/purelib/bioforge/engine/build.py +231 -0
- bioforge-2.3.0.data/purelib/bioforge/engine/engine.c +1538 -0
- bioforge-2.3.0.data/purelib/bioforge/engine/engine.dll +0 -0
- bioforge-2.3.0.data/purelib/bioforge/qcreport.py +298 -0
- bioforge-2.3.0.data/purelib/bioforge/smart_translator.py +601 -0
- bioforge-2.3.0.dist-info/METADATA +736 -0
- bioforge-2.3.0.dist-info/RECORD +18 -0
- bioforge-2.3.0.dist-info/WHEEL +5 -0
- bioforge-2.3.0.dist-info/entry_points.txt +4 -0
- bioforge-2.3.0.dist-info/licenses/LICENSE +280 -0
- bioforge-2.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1538 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* engine/engine.c — Motor C de alto rendimiento
|
|
3
|
+
*
|
|
4
|
+
* Incluye:
|
|
5
|
+
* - Empaquetado/desempaquetado 5-bit (compatible con NumPy packbits)
|
|
6
|
+
* - Alineamiento Needleman-Wunsch (global + semi-global)
|
|
7
|
+
*
|
|
8
|
+
* Compilar (Windows/MinGW):
|
|
9
|
+
* gcc -O3 -march=native -fopenmp -shared -o engine.dll engine.c
|
|
10
|
+
* Compilar (Linux/Mac):
|
|
11
|
+
* gcc -O3 -march=native -fopenmp -shared -fPIC -o engine.so engine.c
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#ifdef _WIN32
|
|
15
|
+
#define EXPORT __declspec(dllexport)
|
|
16
|
+
#else
|
|
17
|
+
#define EXPORT __attribute__((visibility("default")))
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#include <stdio.h>
|
|
21
|
+
#include <stdlib.h>
|
|
22
|
+
#include <string.h>
|
|
23
|
+
#include <stdint.h>
|
|
24
|
+
|
|
25
|
+
/* ── Lectura de archivos: gzip transparente si se compila con -DBIO_USE_ZLIB ──
|
|
26
|
+
zlib gzopen/gzread leen tanto archivos planos como .gz (autodetección del
|
|
27
|
+
magic gzip), así que un único código sirve para ambos formatos. Si zlib no
|
|
28
|
+
está disponible, se compila sin -DBIO_USE_ZLIB y se usa stdio normal. */
|
|
29
|
+
#ifdef BIO_USE_ZLIB
|
|
30
|
+
#include <zlib.h>
|
|
31
|
+
typedef gzFile BIO_FILE;
|
|
32
|
+
#define BIO_OPEN(path) gzopen((path), "rb")
|
|
33
|
+
#define BIO_READ(f, b, n) gzread((f), (b), (unsigned)(n))
|
|
34
|
+
#define BIO_CLOSE(f) gzclose(f)
|
|
35
|
+
#else
|
|
36
|
+
typedef FILE* BIO_FILE;
|
|
37
|
+
#define BIO_OPEN(path) fopen((path), "rb")
|
|
38
|
+
#define BIO_READ(f, b, n) ((int)fread((b), 1, (size_t)(n), (f)))
|
|
39
|
+
#define BIO_CLOSE(f) fclose(f)
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
/* OpenMP para el parser paralelo en memoria. Sin -fopenmp, se compila en serie. */
|
|
43
|
+
#ifdef _OPENMP
|
|
44
|
+
#include <omp.h>
|
|
45
|
+
#else
|
|
46
|
+
static int omp_get_max_threads(void) { return 1; }
|
|
47
|
+
static int omp_get_thread_num(void) { return 0; }
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
/* libdeflate: descompresión gzip ~2x más rápida que zlib (SIMD). Opcional. */
|
|
51
|
+
#ifdef BIO_USE_LIBDEFLATE
|
|
52
|
+
#include <libdeflate.h>
|
|
53
|
+
EXPORT int bio_has_libdeflate(void) { return 1; }
|
|
54
|
+
/* Descomprime gzip cbuf[0..clen) en obuf (cap ocap). Soporta multi-miembro.
|
|
55
|
+
Devuelve nº de bytes descomprimidos, o -1 si no caben / error. */
|
|
56
|
+
EXPORT int64_t bio_gzip_decompress(const uint8_t* cbuf, int64_t clen,
|
|
57
|
+
uint8_t* obuf, int64_t ocap) {
|
|
58
|
+
struct libdeflate_decompressor* d = libdeflate_alloc_decompressor();
|
|
59
|
+
if (!d) return -1;
|
|
60
|
+
int64_t in_pos = 0, out_pos = 0;
|
|
61
|
+
int ok = 1;
|
|
62
|
+
while (in_pos < clen) {
|
|
63
|
+
size_t ain = 0, aout = 0;
|
|
64
|
+
enum libdeflate_result r = libdeflate_gzip_decompress_ex(
|
|
65
|
+
d, cbuf + in_pos, (size_t)(clen - in_pos),
|
|
66
|
+
obuf + out_pos, (size_t)(ocap - out_pos), &ain, &aout);
|
|
67
|
+
if (r != LIBDEFLATE_SUCCESS) { ok = 0; break; }
|
|
68
|
+
in_pos += (int64_t)ain;
|
|
69
|
+
out_pos += (int64_t)aout;
|
|
70
|
+
if (ain == 0) break; /* sin avance: evitar bucle infinito */
|
|
71
|
+
}
|
|
72
|
+
libdeflate_free_decompressor(d);
|
|
73
|
+
return ok ? out_pos : -1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── BGZF: gzip por bloques independientes → descompresión PARALELA ──────
|
|
77
|
+
Un BGZF es un .gz válido (cada bloque es un miembro gzip con un subcampo
|
|
78
|
+
extra 'BC' que da BSIZE). Los bloques se descomprimen en paralelo. */
|
|
79
|
+
|
|
80
|
+
/* ¿Es BGZF? (primer bloque con subcampo extra 'BC'). */
|
|
81
|
+
EXPORT int bio_is_bgzf(const uint8_t* c, int64_t n) {
|
|
82
|
+
if (n < 18) return 0;
|
|
83
|
+
if (c[0] != 0x1f || c[1] != 0x8b || c[2] != 8 || !(c[3] & 4)) return 0;
|
|
84
|
+
int xlen = c[10] | (c[11] << 8);
|
|
85
|
+
int64_t p = 12, e = 12 + (int64_t)xlen;
|
|
86
|
+
if (e > n) return 0;
|
|
87
|
+
while (p + 4 <= e) {
|
|
88
|
+
int slen = c[p + 2] | (c[p + 3] << 8);
|
|
89
|
+
if (c[p] == 66 && c[p + 1] == 67) return 1; /* 'B','C' */
|
|
90
|
+
p += 4 + slen;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Tamaño del bloque BGZF en 'o' (BSIZE+1), o -1 si malformado. */
|
|
96
|
+
static int64_t _bgzf_block_size(const uint8_t* c, int64_t clen, int64_t o) {
|
|
97
|
+
if (o + 18 > clen || c[o] != 0x1f || c[o + 1] != 0x8b) return -1;
|
|
98
|
+
int xlen = c[o + 10] | (c[o + 11] << 8);
|
|
99
|
+
int64_t p = o + 12, e = o + 12 + xlen;
|
|
100
|
+
while (p + 4 <= e) {
|
|
101
|
+
int slen = c[p + 2] | (c[p + 3] << 8);
|
|
102
|
+
if (c[p] == 66 && c[p + 1] == 67)
|
|
103
|
+
return (int64_t)(c[p + 4] | (c[p + 5] << 8)) + 1;
|
|
104
|
+
p += 4 + slen;
|
|
105
|
+
}
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Tamaño total descomprimido de un BGZF (suma de ISIZE), o -1 si malformado. */
|
|
110
|
+
EXPORT int64_t bio_bgzf_usize(const uint8_t* c, int64_t clen) {
|
|
111
|
+
int64_t o = 0, utot = 0;
|
|
112
|
+
while (o < clen) {
|
|
113
|
+
int64_t bs = _bgzf_block_size(c, clen, o);
|
|
114
|
+
if (bs < 0 || o + bs > clen) return -1;
|
|
115
|
+
int32_t isize; memcpy(&isize, c + o + bs - 4, 4);
|
|
116
|
+
utot += isize;
|
|
117
|
+
o += bs;
|
|
118
|
+
}
|
|
119
|
+
return utot;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Descomprime un BGZF en paralelo. Devuelve bytes descomprimidos, o -1. */
|
|
123
|
+
EXPORT int64_t bio_bgzf_decompress_parallel(
|
|
124
|
+
const uint8_t* c, int64_t clen, uint8_t* obuf, int64_t ocap,
|
|
125
|
+
int n_threads)
|
|
126
|
+
{
|
|
127
|
+
/* Pass 0: contar bloques no vacíos y total descomprimido. */
|
|
128
|
+
int64_t o = 0, nb = 0, utot = 0;
|
|
129
|
+
while (o < clen) {
|
|
130
|
+
int64_t bs = _bgzf_block_size(c, clen, o);
|
|
131
|
+
if (bs < 0 || o + bs > clen) return -1;
|
|
132
|
+
int32_t isize; memcpy(&isize, c + o + bs - 4, 4);
|
|
133
|
+
if (isize > 0) { nb++; utot += isize; }
|
|
134
|
+
o += bs;
|
|
135
|
+
}
|
|
136
|
+
if (utot > ocap) return -1;
|
|
137
|
+
if (nb == 0) return 0;
|
|
138
|
+
|
|
139
|
+
int64_t* coff = (int64_t*)malloc((size_t)nb * sizeof(int64_t));
|
|
140
|
+
int32_t* csz = (int32_t*)malloc((size_t)nb * sizeof(int32_t));
|
|
141
|
+
int64_t* uoff = (int64_t*)malloc((size_t)nb * sizeof(int64_t));
|
|
142
|
+
int32_t* usz = (int32_t*)malloc((size_t)nb * sizeof(int32_t));
|
|
143
|
+
if (!coff || !csz || !uoff || !usz) {
|
|
144
|
+
free(coff); free(csz); free(uoff); free(usz); return -1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Pass 1: registrar offsets de cada bloque. */
|
|
148
|
+
o = 0; int64_t i = 0, uo = 0;
|
|
149
|
+
while (o < clen) {
|
|
150
|
+
int64_t bs = _bgzf_block_size(c, clen, o);
|
|
151
|
+
int32_t isize; memcpy(&isize, c + o + bs - 4, 4);
|
|
152
|
+
if (isize > 0) {
|
|
153
|
+
coff[i] = o; csz[i] = (int32_t)bs;
|
|
154
|
+
uoff[i] = uo; usz[i] = isize;
|
|
155
|
+
uo += isize; i++;
|
|
156
|
+
}
|
|
157
|
+
o += bs;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
int NT = n_threads;
|
|
161
|
+
if (NT < 1) NT = 1;
|
|
162
|
+
if (NT > omp_get_max_threads()) NT = omp_get_max_threads();
|
|
163
|
+
if (NT > 64) NT = 64;
|
|
164
|
+
|
|
165
|
+
/* Pass 2: descomprimir bloques en paralelo (1 descompresor por hilo). */
|
|
166
|
+
int err = 0;
|
|
167
|
+
#pragma omp parallel num_threads(NT)
|
|
168
|
+
{
|
|
169
|
+
/* El descompresor de libdeflate NO es seguro entre hilos: uno por hilo.
|
|
170
|
+
El bucle de trabajo va FUERA del if para que TODOS los hilos del
|
|
171
|
+
equipo encuentren el mismo 'omp for' (si no, deadlock en su barrera). */
|
|
172
|
+
struct libdeflate_decompressor* d = libdeflate_alloc_decompressor();
|
|
173
|
+
if (!d) err = 1;
|
|
174
|
+
#pragma omp for schedule(static)
|
|
175
|
+
for (int64_t k = 0; k < nb; k++) {
|
|
176
|
+
if (!d) continue;
|
|
177
|
+
size_t actual = 0;
|
|
178
|
+
enum libdeflate_result r = libdeflate_gzip_decompress(
|
|
179
|
+
d, c + coff[k], (size_t)csz[k],
|
|
180
|
+
obuf + uoff[k], (size_t)usz[k], &actual);
|
|
181
|
+
if (r != LIBDEFLATE_SUCCESS || (int32_t)actual != usz[k])
|
|
182
|
+
err = 1;
|
|
183
|
+
}
|
|
184
|
+
if (d) libdeflate_free_decompressor(d);
|
|
185
|
+
}
|
|
186
|
+
free(coff); free(csz); free(uoff); free(usz);
|
|
187
|
+
return err ? -1 : utot;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Marcador EOF estándar de BGZF (bloque vacío de 28 bytes). */
|
|
191
|
+
static const uint8_t _BGZF_EOF[28] = {
|
|
192
|
+
0x1f,0x8b,0x08,0x04,0,0,0,0,0,0xff,6,0,0x42,0x43,
|
|
193
|
+
2,0,0x1b,0,3,0,0,0,0,0,0,0,0,0
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/* Comprime ``in`` a BGZF en paralelo (bloques de 64 KB independientes).
|
|
197
|
+
Devuelve el tamaño comprimido, o -1 si no cabe / error. */
|
|
198
|
+
EXPORT int64_t bio_bgzf_compress(const uint8_t* in, int64_t in_len,
|
|
199
|
+
uint8_t* out, int64_t out_cap,
|
|
200
|
+
int level, int n_threads) {
|
|
201
|
+
const int64_t CHUNK = 0xff00; /* 65280: máx. descomprimido por bloque */
|
|
202
|
+
int64_t nb = (in_len + CHUNK - 1) / CHUNK;
|
|
203
|
+
int NT = n_threads;
|
|
204
|
+
if (NT < 1) NT = 1;
|
|
205
|
+
if (NT > omp_get_max_threads()) NT = omp_get_max_threads();
|
|
206
|
+
if (NT > 64) NT = 64;
|
|
207
|
+
|
|
208
|
+
uint8_t** bufs = NULL; int32_t* lens = NULL; int err = 0;
|
|
209
|
+
if (nb > 0) {
|
|
210
|
+
bufs = (uint8_t**)calloc((size_t)nb, sizeof(uint8_t*));
|
|
211
|
+
lens = (int32_t*)malloc((size_t)nb * sizeof(int32_t));
|
|
212
|
+
if (!bufs || !lens) { free(bufs); free(lens); return -1; }
|
|
213
|
+
|
|
214
|
+
#pragma omp parallel num_threads(NT)
|
|
215
|
+
{
|
|
216
|
+
/* Un compresor por hilo. El 'omp for' va FUERA del if para que
|
|
217
|
+
todos los hilos lo encuentren (si no, deadlock en la barrera). */
|
|
218
|
+
struct libdeflate_compressor* comp =
|
|
219
|
+
libdeflate_alloc_compressor(level);
|
|
220
|
+
if (!comp) err = 1;
|
|
221
|
+
#pragma omp for schedule(static)
|
|
222
|
+
for (int64_t i = 0; i < nb; i++) {
|
|
223
|
+
if (!comp) continue;
|
|
224
|
+
int64_t off = i * CHUNK;
|
|
225
|
+
int32_t csz = (int32_t)((in_len - off < CHUNK)
|
|
226
|
+
? in_len - off : CHUNK);
|
|
227
|
+
size_t bound = libdeflate_deflate_compress_bound(comp, csz);
|
|
228
|
+
uint8_t* blk = (uint8_t*)malloc(18 + bound + 8);
|
|
229
|
+
if (!blk) { err = 1; continue; }
|
|
230
|
+
size_t dlen = libdeflate_deflate_compress(
|
|
231
|
+
comp, in + off, csz, blk + 18, bound);
|
|
232
|
+
if (dlen == 0) { err = 1; free(blk); continue; }
|
|
233
|
+
int64_t total = 18 + (int64_t)dlen + 8;
|
|
234
|
+
int bsize = (int)total - 1;
|
|
235
|
+
blk[0]=0x1f; blk[1]=0x8b; blk[2]=0x08; blk[3]=0x04;
|
|
236
|
+
blk[4]=blk[5]=blk[6]=blk[7]=0; /* mtime */
|
|
237
|
+
blk[8]=0; blk[9]=0xff; /* xfl, os */
|
|
238
|
+
blk[10]=6; blk[11]=0; /* xlen */
|
|
239
|
+
blk[12]=0x42; blk[13]=0x43; /* 'B','C' */
|
|
240
|
+
blk[14]=2; blk[15]=0; /* slen */
|
|
241
|
+
blk[16]=(uint8_t)(bsize & 0xff);
|
|
242
|
+
blk[17]=(uint8_t)((bsize >> 8) & 0xff);
|
|
243
|
+
uint32_t crc = libdeflate_crc32(0, in + off, csz);
|
|
244
|
+
memcpy(blk + 18 + dlen, &crc, 4);
|
|
245
|
+
uint32_t isize = (uint32_t)csz;
|
|
246
|
+
memcpy(blk + 18 + dlen + 4, &isize, 4);
|
|
247
|
+
bufs[i] = blk; lens[i] = (int32_t)total;
|
|
248
|
+
}
|
|
249
|
+
if (comp) libdeflate_free_compressor(comp);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
int64_t pos = 0;
|
|
254
|
+
if (!err) {
|
|
255
|
+
for (int64_t i = 0; i < nb; i++) {
|
|
256
|
+
if (!bufs[i]) { err = 1; break; }
|
|
257
|
+
if (pos + lens[i] > out_cap) { err = 1; break; }
|
|
258
|
+
memcpy(out + pos, bufs[i], (size_t)lens[i]);
|
|
259
|
+
pos += lens[i];
|
|
260
|
+
}
|
|
261
|
+
if (!err && pos + 28 <= out_cap) {
|
|
262
|
+
memcpy(out + pos, _BGZF_EOF, 28); pos += 28;
|
|
263
|
+
} else if (!err) {
|
|
264
|
+
err = 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (int64_t i = 0; i < nb; i++) free(bufs ? bufs[i] : NULL);
|
|
268
|
+
free(bufs); free(lens);
|
|
269
|
+
return err ? -1 : pos;
|
|
270
|
+
}
|
|
271
|
+
#else
|
|
272
|
+
EXPORT int bio_has_libdeflate(void) { return 0; }
|
|
273
|
+
EXPORT int64_t bio_gzip_decompress(const uint8_t* cbuf, int64_t clen,
|
|
274
|
+
uint8_t* obuf, int64_t ocap) {
|
|
275
|
+
(void)cbuf; (void)clen; (void)obuf; (void)ocap; return -1;
|
|
276
|
+
}
|
|
277
|
+
EXPORT int bio_is_bgzf(const uint8_t* c, int64_t n) {
|
|
278
|
+
(void)c; (void)n; return 0;
|
|
279
|
+
}
|
|
280
|
+
EXPORT int64_t bio_bgzf_usize(const uint8_t* c, int64_t clen) {
|
|
281
|
+
(void)c; (void)clen; return -1;
|
|
282
|
+
}
|
|
283
|
+
EXPORT int64_t bio_bgzf_decompress_parallel(
|
|
284
|
+
const uint8_t* c, int64_t clen, uint8_t* obuf, int64_t ocap, int nt) {
|
|
285
|
+
(void)c; (void)clen; (void)obuf; (void)ocap; (void)nt; return -1;
|
|
286
|
+
}
|
|
287
|
+
EXPORT int64_t bio_bgzf_compress(const uint8_t* in, int64_t in_len,
|
|
288
|
+
uint8_t* out, int64_t out_cap,
|
|
289
|
+
int level, int nt) {
|
|
290
|
+
(void)in;(void)in_len;(void)out;(void)out_cap;(void)level;(void)nt;
|
|
291
|
+
return -1;
|
|
292
|
+
}
|
|
293
|
+
#endif
|
|
294
|
+
|
|
295
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
296
|
+
5-BIT PACK / UNPACK / GETITEM
|
|
297
|
+
Formato compatible con np.packbits(bitorder='big') +
|
|
298
|
+
np.unpackbits(bitorder='little', count=5)
|
|
299
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
300
|
+
|
|
301
|
+
/*
|
|
302
|
+
* Formato compatible con np.packbits(bits, bitorder='big') donde bits se
|
|
303
|
+
* extraen MSB-first: code=5 (0b00101) → bits [0,0,1,0,1] en el stream.
|
|
304
|
+
* Cada código ocupa 5 bits consecutivos en orden MSB→LSB.
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/* Extrae el código en la posición i. O(1) — lee 1-2 bytes. */
|
|
308
|
+
EXPORT uint8_t bio_getitem5(const uint8_t* packed, int32_t i) {
|
|
309
|
+
uint32_t bit_start = (uint32_t)i * 5;
|
|
310
|
+
uint32_t byte0 = bit_start >> 3; /* / 8 */
|
|
311
|
+
uint32_t shift0 = bit_start & 7; /* % 8 */
|
|
312
|
+
|
|
313
|
+
/* Cargar hasta 2 bytes en un word de 16 bits */
|
|
314
|
+
uint16_t word = (uint16_t)packed[byte0] << 8;
|
|
315
|
+
if (shift0 > 3) word |= packed[byte0 + 1];
|
|
316
|
+
|
|
317
|
+
/* Los 5 bits del código quedan en las posiciones [15-shift0 .. 11-shift0] */
|
|
318
|
+
return (uint8_t)((word >> (11u - shift0)) & 0x1Fu);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Desempaqueta n códigos hacia out[n].
|
|
322
|
+
Autónomo y seguro en los límites: nunca lee más allá de packed[plen-1], de
|
|
323
|
+
modo que el llamante NO necesita un byte extra de relleno. Esto elimina una
|
|
324
|
+
copia completa del array en cada decode() (ruta caliente: alineador, traductor,
|
|
325
|
+
GC/k-meros). El bucle es trivialmente auto-vectorizable por GCC -O3. */
|
|
326
|
+
EXPORT void bio_unpack5(const uint8_t* packed, int32_t n, uint8_t* out) {
|
|
327
|
+
int32_t plen = (n * 5 + 7) / 8;
|
|
328
|
+
for (int32_t i = 0; i < n; i++) {
|
|
329
|
+
uint32_t bit_start = (uint32_t)i * 5u;
|
|
330
|
+
uint32_t byte0 = bit_start >> 3;
|
|
331
|
+
uint32_t shift0 = bit_start & 7u;
|
|
332
|
+
uint16_t word = (uint16_t)packed[byte0] << 8;
|
|
333
|
+
uint32_t b1 = byte0 + 1u;
|
|
334
|
+
if (shift0 > 3u && b1 < (uint32_t)plen) word |= packed[b1];
|
|
335
|
+
out[i] = (uint8_t)((word >> (11u - shift0)) & 0x1Fu);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Empaqueta n códigos de 5 bits en out (tamaño >= ceil(n*5/8)+1). */
|
|
340
|
+
EXPORT void bio_pack5(const uint8_t* codes, int32_t n, uint8_t* out) {
|
|
341
|
+
int32_t out_len = (n * 5 + 7) / 8;
|
|
342
|
+
memset(out, 0, (size_t)out_len + 1);
|
|
343
|
+
|
|
344
|
+
for (int32_t i = 0; i < n; i++) {
|
|
345
|
+
uint32_t bit_start = (uint32_t)i * 5;
|
|
346
|
+
uint32_t byte0 = bit_start >> 3;
|
|
347
|
+
uint32_t shift0 = bit_start & 7;
|
|
348
|
+
|
|
349
|
+
/* Posicionar los 5 bits MSB-first en el word de 16 bits */
|
|
350
|
+
uint16_t w = (uint16_t)(codes[i] & 0x1Fu) << (11u - shift0);
|
|
351
|
+
out[byte0] |= (uint8_t)(w >> 8);
|
|
352
|
+
if (shift0 > 3) out[byte0 + 1] |= (uint8_t)(w & 0xFFu);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
357
|
+
TRADUCCION GENETICA
|
|
358
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
359
|
+
|
|
360
|
+
/*
|
|
361
|
+
* Localiza el primer codon ATG (A=0, T/U=3, G=2) en el array de codigos.
|
|
362
|
+
* Devuelve la posicion base-0 del primer nucleotido, o -1 si no se encuentra.
|
|
363
|
+
*/
|
|
364
|
+
EXPORT int32_t bio_find_atg(const uint8_t* codes, int32_t n) {
|
|
365
|
+
int32_t limit = n - 2;
|
|
366
|
+
for (int32_t i = 0; i < limit; i++) {
|
|
367
|
+
if (codes[i] == 0u && codes[i + 1] == 3u && codes[i + 2] == 2u)
|
|
368
|
+
return i;
|
|
369
|
+
}
|
|
370
|
+
return -1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/*
|
|
374
|
+
* Traduce n_codons codones usando el LUT de 64 entradas del codigo genetico.
|
|
375
|
+
*
|
|
376
|
+
* codon_lut : 64 bytes, indice = n1*16 + n2*4 + n3 (A=0 C=1 G=2 T/U=3)
|
|
377
|
+
* nuc_codes : array plano de n_codons*3 valores uint8 [0-3]
|
|
378
|
+
* out : array de salida, n_codons bytes (BioCode de AA o UNK=31)
|
|
379
|
+
*
|
|
380
|
+
* Codones con indices fuera de [0,63] (bases ambiguas > 3) se mapean a UNK.
|
|
381
|
+
* El bucle interno es trivialmente auto-vectorizable por GCC -O3.
|
|
382
|
+
*/
|
|
383
|
+
EXPORT void bio_translate(
|
|
384
|
+
const uint8_t* codon_lut,
|
|
385
|
+
const uint8_t* nuc_codes,
|
|
386
|
+
int32_t n_codons,
|
|
387
|
+
uint8_t* out
|
|
388
|
+
) {
|
|
389
|
+
for (int32_t i = 0; i < n_codons; i++) {
|
|
390
|
+
int32_t idx = (int32_t)nuc_codes[i * 3] * 16
|
|
391
|
+
+ (int32_t)nuc_codes[i * 3 + 1] * 4
|
|
392
|
+
+ (int32_t)nuc_codes[i * 3 + 2];
|
|
393
|
+
out[i] = (idx < 64) ? codon_lut[idx] : (uint8_t)31u; /* 31 = UNK */
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
398
|
+
NEEDLEMAN-WUNSCH
|
|
399
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
400
|
+
|
|
401
|
+
#define TB_DIAG ((uint8_t)0)
|
|
402
|
+
#define TB_UP ((uint8_t)1)
|
|
403
|
+
#define TB_LEFT ((uint8_t)2)
|
|
404
|
+
|
|
405
|
+
/*
|
|
406
|
+
* Núcleo interno: fill de la matriz NW + traceback.
|
|
407
|
+
* semiglobal=0 → global; semiglobal=1 → semi-global (extremos de seq_b libres).
|
|
408
|
+
* Devuelve longitud del alineamiento, -1 en error de memoria.
|
|
409
|
+
*/
|
|
410
|
+
static int32_t _nw_core(
|
|
411
|
+
const uint8_t* codes_a, int32_t m,
|
|
412
|
+
const uint8_t* codes_b, int32_t n,
|
|
413
|
+
const char* decode, /* LUT 32 bytes: BioCode -> carácter ASCII */
|
|
414
|
+
int32_t match_sc, int32_t mismatch_sc, int32_t gap_sc,
|
|
415
|
+
int32_t semiglobal,
|
|
416
|
+
char* out_a, char* out_b, /* buffers de salida, tamaño >= m+n+1 */
|
|
417
|
+
int32_t* p_score,
|
|
418
|
+
int32_t* p_matches, int32_t* p_mismatches, int32_t* p_gaps
|
|
419
|
+
) {
|
|
420
|
+
const int64_t cols = (int64_t)n + 1;
|
|
421
|
+
const int64_t total = ((int64_t)m + 1) * cols;
|
|
422
|
+
|
|
423
|
+
int32_t* H = (int32_t*)malloc((size_t)total * sizeof(int32_t));
|
|
424
|
+
uint8_t* tb = (uint8_t*)malloc((size_t)total);
|
|
425
|
+
if (!H || !tb) { free(H); free(tb); return -1; }
|
|
426
|
+
|
|
427
|
+
/* Inicializar primera fila y columna */
|
|
428
|
+
H[0] = 0; tb[0] = TB_DIAG;
|
|
429
|
+
for (int32_t i = 1; i <= m; i++) {
|
|
430
|
+
H[(int64_t)i * cols] = i * gap_sc;
|
|
431
|
+
tb[(int64_t)i * cols] = TB_UP;
|
|
432
|
+
}
|
|
433
|
+
for (int32_t j = 1; j <= n; j++) {
|
|
434
|
+
H[j] = semiglobal ? 0 : j * gap_sc; /* semi-global: primera fila a 0 */
|
|
435
|
+
tb[j] = TB_LEFT;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* Fill de la matriz — bucle interno auto-vectorizable con -O3 */
|
|
439
|
+
for (int32_t i = 1; i <= m; i++) {
|
|
440
|
+
const uint8_t ca = codes_a[i - 1];
|
|
441
|
+
const int32_t* pr = H + (int64_t)(i - 1) * cols; /* fila anterior */
|
|
442
|
+
int32_t* cr = H + (int64_t) i * cols; /* fila actual */
|
|
443
|
+
uint8_t* tr = tb + (int64_t)i * cols;
|
|
444
|
+
|
|
445
|
+
for (int32_t j = 1; j <= n; j++) {
|
|
446
|
+
int32_t s = (ca == codes_b[j - 1]) ? match_sc : mismatch_sc;
|
|
447
|
+
int32_t diag = pr[j - 1] + s;
|
|
448
|
+
int32_t up = pr[j] + gap_sc;
|
|
449
|
+
int32_t left = cr[j - 1] + gap_sc;
|
|
450
|
+
|
|
451
|
+
uint8_t dir; int32_t best;
|
|
452
|
+
if (diag >= up && diag >= left) { best = diag; dir = TB_DIAG; }
|
|
453
|
+
else if (up >= left) { best = up; dir = TB_UP; }
|
|
454
|
+
else { best = left; dir = TB_LEFT; }
|
|
455
|
+
|
|
456
|
+
cr[j] = best; tr[j] = dir;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Determinar punto de inicio del traceback */
|
|
461
|
+
int32_t end_i = m, end_j = n;
|
|
462
|
+
if (semiglobal) {
|
|
463
|
+
/* Buscar el mejor score en la ultima fila Y en la ultima columna.
|
|
464
|
+
* semi-global: gaps terminales del query (seq_b) son gratuitos.
|
|
465
|
+
* La ultima fila (i=m) contiene el final del query alineado contra
|
|
466
|
+
* distintas posiciones de la referencia (seq_a).
|
|
467
|
+
* La ultima columna (j=n) contiene el final de la referencia. */
|
|
468
|
+
int32_t best_sc = INT32_MIN;
|
|
469
|
+
|
|
470
|
+
/* Escanear ultima fila (i=m, j de 0 a n) */
|
|
471
|
+
for (int32_t j = 0; j <= n; j++) {
|
|
472
|
+
int32_t sc = H[(int64_t)m * cols + j];
|
|
473
|
+
if (sc > best_sc) { best_sc = sc; end_i = m; end_j = j; }
|
|
474
|
+
}
|
|
475
|
+
/* Escanear ultima columna (j=n, i de 0 a m) */
|
|
476
|
+
for (int32_t i = 0; i <= m; i++) {
|
|
477
|
+
int32_t sc = H[(int64_t)i * cols + n];
|
|
478
|
+
if (sc > best_sc) { best_sc = sc; end_i = i; end_j = n; }
|
|
479
|
+
}
|
|
480
|
+
*p_score = best_sc;
|
|
481
|
+
} else {
|
|
482
|
+
end_j = n;
|
|
483
|
+
*p_score = H[(int64_t)m * cols + n];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Buffers temporales para traceback (se invierten al final) */
|
|
487
|
+
char* ta = (char*)malloc((size_t)(m + n + 1));
|
|
488
|
+
char* tb2 = (char*)malloc((size_t)(m + n + 1));
|
|
489
|
+
if (!ta || !tb2) { free(H); free(tb); free(ta); free(tb2); return -1; }
|
|
490
|
+
|
|
491
|
+
int32_t pos = 0, nm = 0, nmi = 0, ng = 0;
|
|
492
|
+
int32_t i = end_i, j = end_j;
|
|
493
|
+
|
|
494
|
+
/* Semi-global: gaps terminales gratuitos en seq_a (end_i<m) o seq_b (end_j<n) */
|
|
495
|
+
if (semiglobal) {
|
|
496
|
+
for (int32_t k = m; k > end_i; k--) {
|
|
497
|
+
ta[pos] = decode[(uint8_t)codes_a[k - 1]];
|
|
498
|
+
tb2[pos] = '-';
|
|
499
|
+
ng++; pos++;
|
|
500
|
+
}
|
|
501
|
+
for (int32_t k = n; k > end_j; k--) {
|
|
502
|
+
ta[pos] = '-';
|
|
503
|
+
tb2[pos] = decode[(uint8_t)codes_b[k - 1]];
|
|
504
|
+
ng++; pos++;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* Traceback */
|
|
509
|
+
while (i > 0 || j > 0) {
|
|
510
|
+
uint8_t dir = tb[(int64_t)i * cols + j];
|
|
511
|
+
if (dir == TB_DIAG) {
|
|
512
|
+
uint8_t ca = codes_a[i - 1], cb = codes_b[j - 1];
|
|
513
|
+
ta[pos] = decode[ca];
|
|
514
|
+
tb2[pos] = decode[cb];
|
|
515
|
+
if (ca == cb) nm++; else nmi++;
|
|
516
|
+
i--; j--;
|
|
517
|
+
} else if (dir == TB_UP) {
|
|
518
|
+
ta[pos] = decode[(uint8_t)codes_a[i - 1]];
|
|
519
|
+
tb2[pos] = '-';
|
|
520
|
+
ng++; i--;
|
|
521
|
+
} else {
|
|
522
|
+
ta[pos] = '-';
|
|
523
|
+
tb2[pos] = decode[(uint8_t)codes_b[j - 1]];
|
|
524
|
+
ng++; j--;
|
|
525
|
+
}
|
|
526
|
+
pos++;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Invertir en los buffers de salida */
|
|
530
|
+
for (int32_t k = 0; k < pos; k++) {
|
|
531
|
+
out_a[k] = ta[pos - 1 - k];
|
|
532
|
+
out_b[k] = tb2[pos - 1 - k];
|
|
533
|
+
}
|
|
534
|
+
out_a[pos] = '\0';
|
|
535
|
+
out_b[pos] = '\0';
|
|
536
|
+
|
|
537
|
+
*p_matches = nm;
|
|
538
|
+
*p_mismatches = nmi;
|
|
539
|
+
*p_gaps = ng;
|
|
540
|
+
|
|
541
|
+
free(H); free(tb); free(ta); free(tb2);
|
|
542
|
+
return pos;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
EXPORT int32_t nw_global(
|
|
546
|
+
const uint8_t* ca, int32_t m,
|
|
547
|
+
const uint8_t* cb, int32_t n,
|
|
548
|
+
const char* decode,
|
|
549
|
+
int32_t match, int32_t mismatch, int32_t gap,
|
|
550
|
+
char* out_a, char* out_b,
|
|
551
|
+
int32_t* score, int32_t* matches, int32_t* mismatches, int32_t* gaps
|
|
552
|
+
) {
|
|
553
|
+
return _nw_core(ca, m, cb, n, decode, match, mismatch, gap, 0,
|
|
554
|
+
out_a, out_b, score, matches, mismatches, gaps);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
EXPORT int32_t nw_semiglobal(
|
|
558
|
+
const uint8_t* ca, int32_t m,
|
|
559
|
+
const uint8_t* cb, int32_t n,
|
|
560
|
+
const char* decode,
|
|
561
|
+
int32_t match, int32_t mismatch, int32_t gap,
|
|
562
|
+
char* out_a, char* out_b,
|
|
563
|
+
int32_t* score, int32_t* matches, int32_t* mismatches, int32_t* gaps
|
|
564
|
+
) {
|
|
565
|
+
return _nw_core(ca, m, cb, n, decode, match, mismatch, gap, 1,
|
|
566
|
+
out_a, out_b, score, matches, mismatches, gaps);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
570
|
+
SMITH-WATERMAN (alineamiento local)
|
|
571
|
+
H[i,j] = max(0, diag+s, up+gap, left+gap)
|
|
572
|
+
Traceback desde el maximo de H hasta H[i][j]==0.
|
|
573
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
574
|
+
|
|
575
|
+
static int32_t _sw_core(
|
|
576
|
+
const uint8_t* codes_a, int32_t m,
|
|
577
|
+
const uint8_t* codes_b, int32_t n,
|
|
578
|
+
const char* decode,
|
|
579
|
+
int32_t match_sc, int32_t mismatch_sc, int32_t gap_sc,
|
|
580
|
+
char* out_a, char* out_b,
|
|
581
|
+
int32_t* p_score,
|
|
582
|
+
int32_t* p_matches, int32_t* p_mismatches, int32_t* p_gaps
|
|
583
|
+
) {
|
|
584
|
+
const int64_t cols = (int64_t)n + 1;
|
|
585
|
+
const int64_t total = ((int64_t)m + 1) * cols;
|
|
586
|
+
|
|
587
|
+
/* calloc: todas las celdas a 0 (condicion de borde SW) */
|
|
588
|
+
int32_t* H = (int32_t*)calloc((size_t)total, sizeof(int32_t));
|
|
589
|
+
uint8_t* tb = (uint8_t*)calloc((size_t)total, 1);
|
|
590
|
+
if (!H || !tb) { free(H); free(tb); return -1; }
|
|
591
|
+
|
|
592
|
+
int32_t max_score = 0, max_i = 0, max_j = 0;
|
|
593
|
+
|
|
594
|
+
for (int32_t i = 1; i <= m; i++) {
|
|
595
|
+
const uint8_t ca = codes_a[i - 1];
|
|
596
|
+
const int32_t* pr = H + (int64_t)(i - 1) * cols;
|
|
597
|
+
int32_t* cr = H + (int64_t) i * cols;
|
|
598
|
+
uint8_t* tr = tb + (int64_t) i * cols;
|
|
599
|
+
|
|
600
|
+
for (int32_t j = 1; j <= n; j++) {
|
|
601
|
+
int32_t s = (ca == codes_b[j - 1]) ? match_sc : mismatch_sc;
|
|
602
|
+
int32_t diag = pr[j - 1] + s;
|
|
603
|
+
int32_t up = pr[j] + gap_sc;
|
|
604
|
+
int32_t left = cr[j - 1] + gap_sc;
|
|
605
|
+
|
|
606
|
+
/* SW: piso en 0 */
|
|
607
|
+
int32_t best = 0; uint8_t dir = TB_DIAG;
|
|
608
|
+
if (diag > best) { best = diag; dir = TB_DIAG; }
|
|
609
|
+
if (up > best) { best = up; dir = TB_UP; }
|
|
610
|
+
if (left > best) { best = left; dir = TB_LEFT; }
|
|
611
|
+
|
|
612
|
+
cr[j] = best; tr[j] = dir;
|
|
613
|
+
if (best > max_score) { max_score = best; max_i = i; max_j = j; }
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
*p_score = max_score;
|
|
617
|
+
|
|
618
|
+
char* ta = (char*)malloc((size_t)(m + n + 1));
|
|
619
|
+
char* tb2 = (char*)malloc((size_t)(m + n + 1));
|
|
620
|
+
if (!ta || !tb2) { free(H); free(tb); free(ta); free(tb2); return -1; }
|
|
621
|
+
|
|
622
|
+
int32_t pos = 0, nm = 0, nmi = 0, ng = 0;
|
|
623
|
+
int32_t i = max_i, j = max_j;
|
|
624
|
+
|
|
625
|
+
/* Traceback: parar cuando H[i][j] == 0 */
|
|
626
|
+
while (i > 0 && j > 0 && H[(int64_t)i * cols + j] > 0) {
|
|
627
|
+
uint8_t dir = tb[(int64_t)i * cols + j];
|
|
628
|
+
if (dir == TB_DIAG) {
|
|
629
|
+
uint8_t ca = codes_a[i - 1], cb = codes_b[j - 1];
|
|
630
|
+
ta[pos] = decode[ca]; tb2[pos] = decode[cb];
|
|
631
|
+
if (ca == cb) nm++; else nmi++;
|
|
632
|
+
i--; j--;
|
|
633
|
+
} else if (dir == TB_UP) {
|
|
634
|
+
ta[pos] = decode[codes_a[i - 1]]; tb2[pos] = '-';
|
|
635
|
+
ng++; i--;
|
|
636
|
+
} else {
|
|
637
|
+
ta[pos] = '-'; tb2[pos] = decode[codes_b[j - 1]];
|
|
638
|
+
ng++; j--;
|
|
639
|
+
}
|
|
640
|
+
pos++;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
for (int32_t k = 0; k < pos; k++) {
|
|
644
|
+
out_a[k] = ta[pos - 1 - k];
|
|
645
|
+
out_b[k] = tb2[pos - 1 - k];
|
|
646
|
+
}
|
|
647
|
+
out_a[pos] = '\0'; out_b[pos] = '\0';
|
|
648
|
+
|
|
649
|
+
*p_matches = nm; *p_mismatches = nmi; *p_gaps = ng;
|
|
650
|
+
free(H); free(tb); free(ta); free(tb2);
|
|
651
|
+
return pos;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
EXPORT int32_t sw_align(
|
|
655
|
+
const uint8_t* ca, int32_t m,
|
|
656
|
+
const uint8_t* cb, int32_t n,
|
|
657
|
+
const char* decode,
|
|
658
|
+
int32_t match, int32_t mismatch, int32_t gap,
|
|
659
|
+
char* out_a, char* out_b,
|
|
660
|
+
int32_t* score, int32_t* matches, int32_t* mismatches, int32_t* gaps
|
|
661
|
+
) {
|
|
662
|
+
return _sw_core(ca, m, cb, n, decode, match, mismatch, gap,
|
|
663
|
+
out_a, out_b, score, matches, mismatches, gaps);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
667
|
+
NEEDLEMAN-WUNSCH CON BANDA (banded NW)
|
|
668
|
+
Solo se calculan celdas con |i-j| <= band.
|
|
669
|
+
Almacenamiento en banda: H_band[i][k], k = j - i + band, j = k + i - band.
|
|
670
|
+
Memoria: O(m * band) frente a O(m*n) del NW completo.
|
|
671
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
672
|
+
|
|
673
|
+
#define _BH(i,k) H_band[(int64_t)(i) * W + (k)]
|
|
674
|
+
#define _BTB(i,k) tb_band[(int64_t)(i) * W + (k)]
|
|
675
|
+
|
|
676
|
+
static int32_t _nw_banded_core(
|
|
677
|
+
const uint8_t* codes_a, int32_t m,
|
|
678
|
+
const uint8_t* codes_b, int32_t n,
|
|
679
|
+
const char* decode,
|
|
680
|
+
int32_t match_sc, int32_t mismatch_sc, int32_t gap_sc,
|
|
681
|
+
int32_t band, int32_t semiglobal,
|
|
682
|
+
char* out_a, char* out_b,
|
|
683
|
+
int32_t* p_score,
|
|
684
|
+
int32_t* p_matches, int32_t* p_mismatches, int32_t* p_gaps
|
|
685
|
+
) {
|
|
686
|
+
const int32_t W = 2 * band + 1;
|
|
687
|
+
const int64_t total = ((int64_t)m + 1) * W;
|
|
688
|
+
const int32_t NEG = INT32_MIN / 2;
|
|
689
|
+
|
|
690
|
+
int32_t* H_band = (int32_t*)malloc((size_t)total * sizeof(int32_t));
|
|
691
|
+
uint8_t* tb_band = (uint8_t*)malloc((size_t)total);
|
|
692
|
+
if (!H_band || !tb_band) { free(H_band); free(tb_band); return -1; }
|
|
693
|
+
|
|
694
|
+
for (int64_t x = 0; x < total; x++) H_band[x] = NEG;
|
|
695
|
+
memset(tb_band, TB_DIAG, (size_t)total);
|
|
696
|
+
|
|
697
|
+
/* Fila 0: j en [0, min(n,band)], k = j + band */
|
|
698
|
+
_BH(0, band) = 0;
|
|
699
|
+
for (int32_t j = 1; j <= (band < n ? band : n); j++) {
|
|
700
|
+
int32_t k = j + band;
|
|
701
|
+
if (k >= W) break;
|
|
702
|
+
_BH(0, k) = semiglobal ? 0 : j * gap_sc;
|
|
703
|
+
_BTB(0, k) = TB_LEFT;
|
|
704
|
+
}
|
|
705
|
+
/* Columna 0: i en [1, min(m,band)], k = band - i */
|
|
706
|
+
for (int32_t i = 1; i <= (band < m ? band : m); i++) {
|
|
707
|
+
int32_t k = band - i;
|
|
708
|
+
if (k < 0) break;
|
|
709
|
+
_BH(i, k) = i * gap_sc;
|
|
710
|
+
_BTB(i, k) = TB_UP;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/* Fill */
|
|
714
|
+
for (int32_t i = 1; i <= m; i++) {
|
|
715
|
+
int32_t j_lo = i - band; if (j_lo < 1) j_lo = 1;
|
|
716
|
+
int32_t j_hi = i + band; if (j_hi > n) j_hi = n;
|
|
717
|
+
|
|
718
|
+
for (int32_t j = j_lo; j <= j_hi; j++) {
|
|
719
|
+
int32_t k = j - i + band;
|
|
720
|
+
int32_t s = (codes_a[i-1] == codes_b[j-1]) ? match_sc : mismatch_sc;
|
|
721
|
+
|
|
722
|
+
int32_t dv = _BH(i-1, k);
|
|
723
|
+
int32_t uv = (k+1 < W) ? _BH(i-1, k+1) : NEG;
|
|
724
|
+
int32_t lv = (k-1 >= 0) ? _BH(i, k-1) : NEG;
|
|
725
|
+
|
|
726
|
+
int32_t d = (dv != NEG) ? dv + s : NEG;
|
|
727
|
+
int32_t u = (uv != NEG) ? uv + gap_sc : NEG;
|
|
728
|
+
int32_t l = (lv != NEG) ? lv + gap_sc : NEG;
|
|
729
|
+
|
|
730
|
+
uint8_t dir; int32_t best;
|
|
731
|
+
if (d >= u && d >= l && d != NEG) { best = d; dir = TB_DIAG; }
|
|
732
|
+
else if (u >= l && u != NEG) { best = u; dir = TB_UP; }
|
|
733
|
+
else if (l != NEG) { best = l; dir = TB_LEFT; }
|
|
734
|
+
else { best = NEG; dir = TB_DIAG; }
|
|
735
|
+
|
|
736
|
+
_BH(i, k) = best; _BTB(i, k) = dir;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/* Score y punto de inicio del traceback */
|
|
741
|
+
int32_t end_i = m, end_j = n;
|
|
742
|
+
if (semiglobal) {
|
|
743
|
+
int32_t best_sc = NEG;
|
|
744
|
+
int32_t i_lo = (n - band > 0) ? n - band : 0;
|
|
745
|
+
int32_t i_hi = (n + band < m) ? n + band : m;
|
|
746
|
+
for (int32_t i2 = i_lo; i2 <= i_hi; i2++) {
|
|
747
|
+
int32_t k2 = n - i2 + band;
|
|
748
|
+
if (k2 < 0 || k2 >= W) continue;
|
|
749
|
+
int32_t v = _BH(i2, k2);
|
|
750
|
+
if (v > best_sc) { best_sc = v; end_i = i2; }
|
|
751
|
+
}
|
|
752
|
+
end_j = n;
|
|
753
|
+
*p_score = best_sc;
|
|
754
|
+
} else {
|
|
755
|
+
int32_t k_end = n - m + band;
|
|
756
|
+
*p_score = (k_end >= 0 && k_end < W) ? _BH(m, k_end) : NEG;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
char* ta = (char*)malloc((size_t)(m + n + 1));
|
|
760
|
+
char* tb2 = (char*)malloc((size_t)(m + n + 1));
|
|
761
|
+
if (!ta || !tb2) { free(H_band); free(tb_band); free(ta); free(tb2); return -1; }
|
|
762
|
+
|
|
763
|
+
int32_t pos = 0, nm = 0, nmi = 0, ng = 0;
|
|
764
|
+
int32_t i = end_i, j = end_j;
|
|
765
|
+
|
|
766
|
+
if (semiglobal) {
|
|
767
|
+
for (int32_t ki = m; ki > end_i; ki--) {
|
|
768
|
+
ta[pos] = decode[codes_a[ki-1]]; tb2[pos] = '-';
|
|
769
|
+
ng++; pos++;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
while (i > 0 || j > 0) {
|
|
774
|
+
int32_t k = j - i + band;
|
|
775
|
+
uint8_t dir = (k >= 0 && k < W) ? _BTB(i, k) : TB_DIAG;
|
|
776
|
+
|
|
777
|
+
if (i > 0 && j > 0 && dir == TB_DIAG) {
|
|
778
|
+
uint8_t ca = codes_a[i-1], cb = codes_b[j-1];
|
|
779
|
+
ta[pos] = decode[ca]; tb2[pos] = decode[cb];
|
|
780
|
+
if (ca == cb) nm++; else nmi++;
|
|
781
|
+
i--; j--;
|
|
782
|
+
} else if (j == 0 || (i > 0 && dir == TB_UP)) {
|
|
783
|
+
ta[pos] = decode[codes_a[i-1]]; tb2[pos] = '-';
|
|
784
|
+
ng++; i--;
|
|
785
|
+
} else {
|
|
786
|
+
ta[pos] = '-'; tb2[pos] = decode[codes_b[j-1]];
|
|
787
|
+
ng++; j--;
|
|
788
|
+
}
|
|
789
|
+
pos++;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
for (int32_t k = 0; k < pos; k++) {
|
|
793
|
+
out_a[k] = ta[pos-1-k]; out_b[k] = tb2[pos-1-k];
|
|
794
|
+
}
|
|
795
|
+
out_a[pos] = '\0'; out_b[pos] = '\0';
|
|
796
|
+
|
|
797
|
+
*p_matches = nm; *p_mismatches = nmi; *p_gaps = ng;
|
|
798
|
+
free(H_band); free(tb_band); free(ta); free(tb2);
|
|
799
|
+
return pos;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
#undef _BH
|
|
803
|
+
#undef _BTB
|
|
804
|
+
|
|
805
|
+
EXPORT int32_t nw_banded(
|
|
806
|
+
const uint8_t* ca, int32_t m,
|
|
807
|
+
const uint8_t* cb, int32_t n,
|
|
808
|
+
const char* decode,
|
|
809
|
+
int32_t match, int32_t mismatch, int32_t gap, int32_t band,
|
|
810
|
+
char* out_a, char* out_b,
|
|
811
|
+
int32_t* score, int32_t* matches, int32_t* mismatches, int32_t* gaps
|
|
812
|
+
) {
|
|
813
|
+
return _nw_banded_core(ca, m, cb, n, decode, match, mismatch, gap, band, 0,
|
|
814
|
+
out_a, out_b, score, matches, mismatches, gaps);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
EXPORT int32_t nw_banded_semiglobal(
|
|
818
|
+
const uint8_t* ca, int32_t m,
|
|
819
|
+
const uint8_t* cb, int32_t n,
|
|
820
|
+
const char* decode,
|
|
821
|
+
int32_t match, int32_t mismatch, int32_t gap, int32_t band,
|
|
822
|
+
char* out_a, char* out_b,
|
|
823
|
+
int32_t* score, int32_t* matches, int32_t* mismatches, int32_t* gaps
|
|
824
|
+
) {
|
|
825
|
+
return _nw_banded_core(ca, m, cb, n, decode, match, mismatch, gap, band, 1,
|
|
826
|
+
out_a, out_b, score, matches, mismatches, gaps);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
830
|
+
STREAMING PARSER (FASTA + FASTQ)
|
|
831
|
+
─────────────────────────────────────────────────────────────────────────
|
|
832
|
+
Buffer de 64 KB. memchr() usa SIMD de la libc (AVX2/SSE2/NEON) para
|
|
833
|
+
encontrar saltos de línea sin escribir intrínsecos manuales.
|
|
834
|
+
La secuencia se codifica a BioCode 5-bit directamente en C, sin pasar
|
|
835
|
+
nunca por strings Python — esto elimina el cuello de botella principal
|
|
836
|
+
de Biopython y del SmartImporter.stream() en Python puro.
|
|
837
|
+
|
|
838
|
+
API:
|
|
839
|
+
bio_parser_open(path) → handle opaco (NULL si falla)
|
|
840
|
+
bio_parser_next(handle, …) → 1 OK | 0 EOF | -1 error | -2 overflow
|
|
841
|
+
bio_parser_close(handle)
|
|
842
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
843
|
+
|
|
844
|
+
#define PBUF 65536 /* 64 KB de buffer de lectura */
|
|
845
|
+
|
|
846
|
+
#define PSCR_HDR 4096 /* cabecera máxima en modo batch */
|
|
847
|
+
#define PSCR_CAP (1 << 24) /* 16 MB scratch por secuencia (modo batch) */
|
|
848
|
+
|
|
849
|
+
typedef struct {
|
|
850
|
+
BIO_FILE fp;
|
|
851
|
+
uint8_t buf[PBUF];
|
|
852
|
+
int32_t beg, end;
|
|
853
|
+
int eof;
|
|
854
|
+
int fmt; /* 1 = FASTA, 2 = FASTQ */
|
|
855
|
+
int first; /* 1 = no se ha leído aún el primer registro */
|
|
856
|
+
uint8_t nuc_lut[256];
|
|
857
|
+
uint8_t aa_lut[256];
|
|
858
|
+
uint8_t is_prot[256];
|
|
859
|
+
|
|
860
|
+
/* ── scratch para el parser por lotes (bio_parser_next_batch) ───────── */
|
|
861
|
+
char scr_hdr[PSCR_HDR];
|
|
862
|
+
uint8_t* scr_codes; /* malloc PSCR_CAP, NULL hasta el primer batch */
|
|
863
|
+
uint8_t* scr_qual; /* malloc PSCR_CAP, NULL hasta el primer batch */
|
|
864
|
+
int32_t scr_n, scr_q, scr_type;
|
|
865
|
+
int has_pending; /* 1 = hay un registro en scratch sin emitir */
|
|
866
|
+
} BioParser;
|
|
867
|
+
|
|
868
|
+
/* ── LUTs idénticas a las de Python (mismo esquema BioCode 5-bit) ─────────
|
|
869
|
+
Standalone para que el parser paralelo construya LUTs en la pila y las
|
|
870
|
+
comparta entre hilos (solo lectura). */
|
|
871
|
+
static void _build_luts(uint8_t* nuc_lut, uint8_t* aa_lut, uint8_t* is_prot) {
|
|
872
|
+
memset(nuc_lut, 31, 256);
|
|
873
|
+
nuc_lut['A'] = nuc_lut['a'] = 0;
|
|
874
|
+
nuc_lut['C'] = nuc_lut['c'] = 1;
|
|
875
|
+
nuc_lut['G'] = nuc_lut['g'] = 2;
|
|
876
|
+
nuc_lut['T'] = nuc_lut['t'] = 3;
|
|
877
|
+
nuc_lut['U'] = nuc_lut['u'] = 3;
|
|
878
|
+
nuc_lut['N'] = nuc_lut['n'] = 31;
|
|
879
|
+
nuc_lut['-'] = nuc_lut['.'] = 25;
|
|
880
|
+
|
|
881
|
+
memset(aa_lut, 31, 256);
|
|
882
|
+
aa_lut['A'] = aa_lut['a'] = 4;
|
|
883
|
+
aa_lut['C'] = aa_lut['c'] = 5;
|
|
884
|
+
aa_lut['D'] = aa_lut['d'] = 6;
|
|
885
|
+
aa_lut['E'] = aa_lut['e'] = 7;
|
|
886
|
+
aa_lut['F'] = aa_lut['f'] = 8;
|
|
887
|
+
aa_lut['G'] = aa_lut['g'] = 9;
|
|
888
|
+
aa_lut['H'] = aa_lut['h'] = 10;
|
|
889
|
+
aa_lut['I'] = aa_lut['i'] = 11;
|
|
890
|
+
aa_lut['K'] = aa_lut['k'] = 12;
|
|
891
|
+
aa_lut['L'] = aa_lut['l'] = 13;
|
|
892
|
+
aa_lut['M'] = aa_lut['m'] = 14;
|
|
893
|
+
aa_lut['N'] = aa_lut['n'] = 15;
|
|
894
|
+
aa_lut['P'] = aa_lut['p'] = 16;
|
|
895
|
+
aa_lut['Q'] = aa_lut['q'] = 17;
|
|
896
|
+
aa_lut['R'] = aa_lut['r'] = 18;
|
|
897
|
+
aa_lut['S'] = aa_lut['s'] = 19;
|
|
898
|
+
aa_lut['T'] = aa_lut['t'] = 20;
|
|
899
|
+
aa_lut['V'] = aa_lut['v'] = 21;
|
|
900
|
+
aa_lut['W'] = aa_lut['w'] = 22;
|
|
901
|
+
aa_lut['Y'] = aa_lut['y'] = 23;
|
|
902
|
+
aa_lut['*'] = 24;
|
|
903
|
+
aa_lut['-'] = 25;
|
|
904
|
+
aa_lut['X'] = aa_lut['x'] = 31;
|
|
905
|
+
|
|
906
|
+
memset(is_prot, 0, 256);
|
|
907
|
+
is_prot['E'] = is_prot['e'] = 1;
|
|
908
|
+
is_prot['F'] = is_prot['f'] = 1;
|
|
909
|
+
is_prot['I'] = is_prot['i'] = 1;
|
|
910
|
+
is_prot['L'] = is_prot['l'] = 1;
|
|
911
|
+
is_prot['P'] = is_prot['p'] = 1;
|
|
912
|
+
is_prot['Q'] = is_prot['q'] = 1;
|
|
913
|
+
is_prot['*'] = 1;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static void _init_luts(BioParser* p) {
|
|
917
|
+
_build_luts(p->nuc_lut, p->aa_lut, p->is_prot);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/* ── Rellena el buffer conservando bytes no consumidos ─────────────────── */
|
|
921
|
+
static void _refill_buf(BioParser* p) {
|
|
922
|
+
if (p->eof) return;
|
|
923
|
+
int32_t rem = p->end - p->beg;
|
|
924
|
+
if (rem > 0) memmove(p->buf, p->buf + p->beg, (size_t)rem);
|
|
925
|
+
p->beg = 0; p->end = rem;
|
|
926
|
+
int32_t n = (int32_t)BIO_READ(p->fp, p->buf + rem, PBUF - rem);
|
|
927
|
+
if (n <= 0) { p->eof = 1; n = 0; } /* 0 = EOF, -1 = error de descompresión */
|
|
928
|
+
p->end += n;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* ── Primer byte disponible sin consumirlo, o -1 en EOF ─────────────────── */
|
|
932
|
+
static int _peek(BioParser* p) {
|
|
933
|
+
if (p->beg >= p->end && !p->eof) _refill_buf(p);
|
|
934
|
+
return (p->beg < p->end) ? (int)p->buf[p->beg] : -1;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/* ── Busca 'ch' usando memchr (SIMD en libc moderna), rellenando si hace falta ─ */
|
|
938
|
+
static uint8_t* _find_ch(BioParser* p, uint8_t ch) {
|
|
939
|
+
for (;;) {
|
|
940
|
+
uint8_t* f = (uint8_t*)memchr(
|
|
941
|
+
p->buf + p->beg, (int)ch, (size_t)(p->end - p->beg));
|
|
942
|
+
if (f) return f;
|
|
943
|
+
if (p->eof) return NULL;
|
|
944
|
+
_refill_buf(p);
|
|
945
|
+
if (p->end == 0) return NULL;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/* ── Lee texto hasta '\n', escribe en out[] si out!=NULL.
|
|
950
|
+
Elimina '\r'. Retorna longitud leída (sin '\n'), o -1 si max es 0. ─── */
|
|
951
|
+
static int32_t _readline(BioParser* p, char* out, int32_t max) {
|
|
952
|
+
if (out && max <= 0) out = NULL;
|
|
953
|
+
int32_t total = 0;
|
|
954
|
+
for (;;) {
|
|
955
|
+
if (p->beg >= p->end) {
|
|
956
|
+
_refill_buf(p);
|
|
957
|
+
if (p->beg >= p->end) {
|
|
958
|
+
if (out) out[total] = '\0';
|
|
959
|
+
return total;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
uint8_t* nl = (uint8_t*)memchr(
|
|
963
|
+
p->buf + p->beg, '\n', (size_t)(p->end - p->beg));
|
|
964
|
+
int32_t seg_end = nl ? (int32_t)(nl - p->buf) : p->end;
|
|
965
|
+
int32_t seg_len = seg_end - p->beg;
|
|
966
|
+
if (seg_len > 0 && p->buf[seg_end - 1] == '\r') seg_len--;
|
|
967
|
+
if (out) {
|
|
968
|
+
int32_t copy = seg_len;
|
|
969
|
+
if (total + copy >= max) copy = max - 1 - total;
|
|
970
|
+
if (copy > 0) memcpy(out + total, p->buf + p->beg, (size_t)copy);
|
|
971
|
+
}
|
|
972
|
+
total += seg_len;
|
|
973
|
+
p->beg = nl ? seg_end + 1 : p->end;
|
|
974
|
+
if (nl) { if (out) out[total < max ? total : max - 1] = '\0'; return total; }
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/* ── Lee bytes crudos hasta '\n' en out[]; elimina '\r'.
|
|
979
|
+
Retorna conteo, -1 si overflow. ─────────────────────────────────────── */
|
|
980
|
+
static int32_t _readbytes(BioParser* p, uint8_t* out, int32_t max) {
|
|
981
|
+
int32_t total = 0;
|
|
982
|
+
for (;;) {
|
|
983
|
+
if (p->beg >= p->end) {
|
|
984
|
+
_refill_buf(p);
|
|
985
|
+
if (p->beg >= p->end) return total;
|
|
986
|
+
}
|
|
987
|
+
uint8_t* nl = (uint8_t*)memchr(
|
|
988
|
+
p->buf + p->beg, '\n', (size_t)(p->end - p->beg));
|
|
989
|
+
int32_t seg_end = nl ? (int32_t)(nl - p->buf) : p->end;
|
|
990
|
+
int32_t seg_len = seg_end - p->beg;
|
|
991
|
+
if (seg_len > 0 && p->buf[seg_end - 1] == '\r') seg_len--;
|
|
992
|
+
if (total + seg_len > max) return -1;
|
|
993
|
+
if (out && seg_len > 0)
|
|
994
|
+
memcpy(out + total, p->buf + p->beg, (size_t)seg_len);
|
|
995
|
+
total += seg_len;
|
|
996
|
+
p->beg = nl ? seg_end + 1 : p->end;
|
|
997
|
+
if (nl) return total;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/* ── Codifica in-place raw_bytes → BioCode usando lut ─────────────────── */
|
|
1002
|
+
static void _encode_inplace(const uint8_t* lut, uint8_t* data, int32_t n) {
|
|
1003
|
+
for (int32_t i = 0; i < n; i++) data[i] = lut[data[i]];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* ── Detecta si hay caracteres exclusivos de proteína ─────────────────── */
|
|
1007
|
+
static int _has_prot_chars(const BioParser* p, const uint8_t* raw, int32_t n) {
|
|
1008
|
+
for (int32_t i = 0; i < n; i++)
|
|
1009
|
+
if (p->is_prot[raw[i]]) return 1;
|
|
1010
|
+
return 0;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/* ─────────────────── API pública ─────────────────────────────────────── */
|
|
1014
|
+
|
|
1015
|
+
EXPORT void* bio_parser_open(const char* path) {
|
|
1016
|
+
BioParser* p = (BioParser*)calloc(1, sizeof(BioParser));
|
|
1017
|
+
if (!p) return NULL;
|
|
1018
|
+
p->fp = BIO_OPEN(path);
|
|
1019
|
+
if (!p->fp) { free(p); return NULL; }
|
|
1020
|
+
_init_luts(p);
|
|
1021
|
+
p->first = 1;
|
|
1022
|
+
/* Detectar formato: leer primer carácter no-espacio */
|
|
1023
|
+
_refill_buf(p);
|
|
1024
|
+
while (p->beg < p->end) {
|
|
1025
|
+
uint8_t c = p->buf[p->beg];
|
|
1026
|
+
if (c == '>') { p->fmt = 1; break; }
|
|
1027
|
+
if (c == '@') { p->fmt = 2; break; }
|
|
1028
|
+
if (c != '\n' && c != '\r' && c != ' ') break;
|
|
1029
|
+
p->beg++;
|
|
1030
|
+
}
|
|
1031
|
+
if (!p->fmt) p->fmt = 1; /* por defecto FASTA */
|
|
1032
|
+
return (void*)p;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/*
|
|
1036
|
+
* bio_parser_next — lee el siguiente registro FASTA o FASTQ.
|
|
1037
|
+
*
|
|
1038
|
+
* handle : devuelto por bio_parser_open
|
|
1039
|
+
* hdr/hdr_max : buffer para la cabecera sin '>'/'@' (null-terminado)
|
|
1040
|
+
* codes/codes_max/n_out : buffer de BioCode 5-bit (sin empaquetar), símbolos escritos
|
|
1041
|
+
* force_type : -1 auto | 0 nucleótido | 1 proteína
|
|
1042
|
+
* type_out : tipo detectado (0=nuc, 1=prot)
|
|
1043
|
+
* qual/qual_out : calidades Phred crudas (ASCII-33); NULL para FASTA
|
|
1044
|
+
*
|
|
1045
|
+
* Retorna: 1=registro leído | 0=EOF | -1=error | -2=overflow de buffer
|
|
1046
|
+
*/
|
|
1047
|
+
static int32_t _parse_one(
|
|
1048
|
+
BioParser* p,
|
|
1049
|
+
char* hdr, int32_t hdr_max,
|
|
1050
|
+
uint8_t* codes, int32_t codes_max, int32_t* n_out,
|
|
1051
|
+
int32_t force_type,
|
|
1052
|
+
int32_t* type_out,
|
|
1053
|
+
uint8_t* qual, int32_t* qual_out
|
|
1054
|
+
) {
|
|
1055
|
+
if (!p) return -1;
|
|
1056
|
+
*n_out = 0;
|
|
1057
|
+
if (type_out) *type_out = 0;
|
|
1058
|
+
if (qual_out) *qual_out = 0;
|
|
1059
|
+
|
|
1060
|
+
/* Bucle externo: los registros vacíos se SALTAN (no detienen la lectura).
|
|
1061
|
+
Devolver 0 solo en el fin de archivo real — nunca por un registro vacío,
|
|
1062
|
+
que de otro modo truncaría el resto del fichero. */
|
|
1063
|
+
for (;;) {
|
|
1064
|
+
if (p->fmt == 1) {
|
|
1065
|
+
/* ── FASTA ─────────────────────────────────────────────────────── */
|
|
1066
|
+
uint8_t* gt = _find_ch(p, '>');
|
|
1067
|
+
if (!gt) return 0;
|
|
1068
|
+
p->beg = (int32_t)(gt - p->buf) + 1; /* saltar '>' */
|
|
1069
|
+
|
|
1070
|
+
_readline(p, hdr, hdr_max);
|
|
1071
|
+
|
|
1072
|
+
int32_t total = 0;
|
|
1073
|
+
for (;;) {
|
|
1074
|
+
int c = _peek(p);
|
|
1075
|
+
if (c < 0 || c == '>') break; /* EOF / siguiente registro */
|
|
1076
|
+
if (c == '\n' || c == '\r') { p->beg++; continue; } /* línea vacía */
|
|
1077
|
+
if (c == ';') { _readline(p, NULL, 0); continue; } /* comentario */
|
|
1078
|
+
int32_t n = _readbytes(p, codes + total, codes_max - total);
|
|
1079
|
+
if (n < 0) { *n_out = total; return -2; }
|
|
1080
|
+
total += n;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (total > 0) {
|
|
1084
|
+
int type = (force_type >= 0) ? force_type
|
|
1085
|
+
: (_has_prot_chars(p, codes, total) ? 1 : 0);
|
|
1086
|
+
_encode_inplace(type ? p->aa_lut : p->nuc_lut, codes, total);
|
|
1087
|
+
if (type_out) *type_out = type;
|
|
1088
|
+
*n_out = total;
|
|
1089
|
+
return 1;
|
|
1090
|
+
}
|
|
1091
|
+
if (_peek(p) < 0) return 0; /* fin de archivo real */
|
|
1092
|
+
continue; /* registro vacío: saltar al siguiente */
|
|
1093
|
+
|
|
1094
|
+
} else {
|
|
1095
|
+
/* ── FASTQ ─────────────────────────────────────────────────────── */
|
|
1096
|
+
if (p->first) {
|
|
1097
|
+
/* Sincronizar al primer '@' */
|
|
1098
|
+
uint8_t* at = _find_ch(p, '@');
|
|
1099
|
+
if (!at) return 0;
|
|
1100
|
+
p->beg = (int32_t)(at - p->buf) + 1;
|
|
1101
|
+
p->first = 0;
|
|
1102
|
+
} else {
|
|
1103
|
+
/* Registros posteriores: el buffer apunta justo al '@' */
|
|
1104
|
+
if (p->beg >= p->end) _refill_buf(p);
|
|
1105
|
+
if (p->beg >= p->end) return 0;
|
|
1106
|
+
if (p->buf[p->beg] == '@') p->beg++;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* Línea 1: cabecera */
|
|
1110
|
+
_readline(p, hdr, hdr_max);
|
|
1111
|
+
|
|
1112
|
+
/* Línea 2: secuencia */
|
|
1113
|
+
int32_t n = _readbytes(p, codes, codes_max);
|
|
1114
|
+
if (n < 0) return -2;
|
|
1115
|
+
|
|
1116
|
+
/* Línea 3: '+comment' — consumir y descartar */
|
|
1117
|
+
_readline(p, NULL, 0);
|
|
1118
|
+
|
|
1119
|
+
/* Línea 4: calidades */
|
|
1120
|
+
int32_t q = 0;
|
|
1121
|
+
if (qual && qual_out) {
|
|
1122
|
+
q = _readbytes(p, qual, codes_max);
|
|
1123
|
+
if (q < 0) q = 0;
|
|
1124
|
+
for (int32_t i = 0; i < q; i++)
|
|
1125
|
+
qual[i] = (uint8_t)((qual[i] >= 33u) ? qual[i] - 33u : 0u);
|
|
1126
|
+
} else {
|
|
1127
|
+
_readline(p, NULL, 0);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (n > 0) {
|
|
1131
|
+
int type = (force_type == 1) ? 1 : 0;
|
|
1132
|
+
_encode_inplace(type ? p->aa_lut : p->nuc_lut, codes, n);
|
|
1133
|
+
if (type_out) *type_out = type;
|
|
1134
|
+
if (qual_out) *qual_out = q;
|
|
1135
|
+
*n_out = n;
|
|
1136
|
+
return 1;
|
|
1137
|
+
}
|
|
1138
|
+
if (_peek(p) < 0) return 0; /* fin de archivo real */
|
|
1139
|
+
continue; /* lectura vacía: saltar a la siguiente */
|
|
1140
|
+
}
|
|
1141
|
+
} /* for (;;) */
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/* Envoltorio público de un solo registro (compatibilidad v2.0). */
|
|
1145
|
+
EXPORT int32_t bio_parser_next(
|
|
1146
|
+
void* handle,
|
|
1147
|
+
char* hdr, int32_t hdr_max,
|
|
1148
|
+
uint8_t* codes, int32_t codes_max, int32_t* n_out,
|
|
1149
|
+
int32_t force_type,
|
|
1150
|
+
int32_t* type_out,
|
|
1151
|
+
uint8_t* qual, int32_t* qual_out
|
|
1152
|
+
) {
|
|
1153
|
+
return _parse_one((BioParser*)handle, hdr, hdr_max, codes, codes_max,
|
|
1154
|
+
n_out, force_type, type_out, qual, qual_out);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/*
|
|
1158
|
+
* bio_parser_next_batch — parsea hasta max_records registros en UNA llamada.
|
|
1159
|
+
*
|
|
1160
|
+
* Empaqueta cada secuencia a BioCode 5-bit dentro de C (bio_pack5), de modo
|
|
1161
|
+
* que Python no cruza la frontera ni llama a pack() por registro. Esto elimina
|
|
1162
|
+
* los dos cuellos de botella medidos: el peaje ctypes y el pack NumPy por
|
|
1163
|
+
* registro.
|
|
1164
|
+
*
|
|
1165
|
+
* Buffers de salida (asignados una vez en Python y reutilizados):
|
|
1166
|
+
* hdr_buf [hdr_buf_max] cabeceras concatenadas, null-terminadas
|
|
1167
|
+
* hdr_off [max_records+1] offset de inicio de cada cabecera en hdr_buf
|
|
1168
|
+
* pack_buf [pack_buf_max] secuencias 5-bit concatenadas, byte-alineadas
|
|
1169
|
+
* pack_off [max_records+1] offset de byte de inicio de cada secuencia
|
|
1170
|
+
* n_syms [max_records] nº de símbolos por registro (para desempaquetar)
|
|
1171
|
+
* types [max_records] tipo (0=nuc, 1=prot) por registro
|
|
1172
|
+
* qual_buf [qual_buf_max] calidades Phred concatenadas (NULL en FASTA)
|
|
1173
|
+
* qual_off [max_records+1] offset de inicio de calidades por registro
|
|
1174
|
+
*
|
|
1175
|
+
* Si un registro no cabe en lo que queda de los buffers, se guarda en scratch
|
|
1176
|
+
* (has_pending) y se emite en la siguiente llamada.
|
|
1177
|
+
*
|
|
1178
|
+
* Retorna: nº de registros parseados (>=0) | -1 error | -2 registro demasiado
|
|
1179
|
+
* grande para el buffer entero.
|
|
1180
|
+
*/
|
|
1181
|
+
EXPORT int32_t bio_parser_next_batch(
|
|
1182
|
+
void* handle, int32_t max_records, int32_t force_type,
|
|
1183
|
+
char* hdr_buf, int32_t hdr_buf_max, int32_t* hdr_off,
|
|
1184
|
+
uint8_t* pack_buf, int32_t pack_buf_max, int32_t* pack_off,
|
|
1185
|
+
int32_t* n_syms, int32_t* types,
|
|
1186
|
+
uint8_t* qual_buf, int32_t qual_buf_max, int32_t* qual_off
|
|
1187
|
+
) {
|
|
1188
|
+
BioParser* p = (BioParser*)handle;
|
|
1189
|
+
if (!p) return -1;
|
|
1190
|
+
|
|
1191
|
+
/* Reserva perezosa del scratch en el primer batch. */
|
|
1192
|
+
if (!p->scr_codes) {
|
|
1193
|
+
p->scr_codes = (uint8_t*)malloc(PSCR_CAP);
|
|
1194
|
+
p->scr_qual = (uint8_t*)malloc(PSCR_CAP);
|
|
1195
|
+
if (!p->scr_codes || !p->scr_qual) return -1;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
int32_t count = 0, hdr_used = 0, pack_used = 0, qual_used = 0;
|
|
1199
|
+
hdr_off[0] = 0; pack_off[0] = 0;
|
|
1200
|
+
if (qual_off) qual_off[0] = 0;
|
|
1201
|
+
|
|
1202
|
+
while (count < max_records) {
|
|
1203
|
+
int32_t n, q = 0, type;
|
|
1204
|
+
|
|
1205
|
+
if (p->has_pending) {
|
|
1206
|
+
n = p->scr_n; q = p->scr_q; type = p->scr_type;
|
|
1207
|
+
} else {
|
|
1208
|
+
int32_t r = _parse_one(
|
|
1209
|
+
p, p->scr_hdr, PSCR_HDR,
|
|
1210
|
+
p->scr_codes, PSCR_CAP, &n,
|
|
1211
|
+
force_type, &type,
|
|
1212
|
+
qual_buf ? p->scr_qual : NULL, &q);
|
|
1213
|
+
if (r == 0) break; /* EOF */
|
|
1214
|
+
if (r < 0) return r; /* error / registro mayor que el scratch */
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
int32_t hlen = (int32_t)strlen(p->scr_hdr);
|
|
1218
|
+
int32_t plen = (n * 5 + 7) / 8;
|
|
1219
|
+
|
|
1220
|
+
/* ¿Cabe en lo que queda de los buffers de salida? (+1 slack de pack5) */
|
|
1221
|
+
if (hdr_used + hlen + 1 > hdr_buf_max ||
|
|
1222
|
+
pack_used + plen + 1 > pack_buf_max ||
|
|
1223
|
+
(qual_buf && qual_used + q > qual_buf_max)) {
|
|
1224
|
+
if (count == 0) return -2; /* no cabe ni en buffer vacío */
|
|
1225
|
+
p->scr_n = n; p->scr_q = q; p->scr_type = type;
|
|
1226
|
+
p->has_pending = 1;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
p->has_pending = 0;
|
|
1230
|
+
|
|
1231
|
+
memcpy(hdr_buf + hdr_used, p->scr_hdr, (size_t)hlen);
|
|
1232
|
+
hdr_buf[hdr_used + hlen] = '\0';
|
|
1233
|
+
hdr_used += hlen + 1;
|
|
1234
|
+
hdr_off[count + 1] = hdr_used;
|
|
1235
|
+
|
|
1236
|
+
bio_pack5(p->scr_codes, n, pack_buf + pack_used);
|
|
1237
|
+
pack_used += plen;
|
|
1238
|
+
pack_off[count + 1] = pack_used;
|
|
1239
|
+
|
|
1240
|
+
n_syms[count] = n;
|
|
1241
|
+
types[count] = type;
|
|
1242
|
+
|
|
1243
|
+
if (qual_buf) {
|
|
1244
|
+
if (q > 0) memcpy(qual_buf + qual_used, p->scr_qual, (size_t)q);
|
|
1245
|
+
qual_used += q;
|
|
1246
|
+
qual_off[count + 1] = qual_used;
|
|
1247
|
+
}
|
|
1248
|
+
count++;
|
|
1249
|
+
}
|
|
1250
|
+
return count;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
EXPORT void bio_parser_close(void* handle) {
|
|
1254
|
+
BioParser* p = (BioParser*)handle;
|
|
1255
|
+
if (p) {
|
|
1256
|
+
if (p->fp) BIO_CLOSE(p->fp);
|
|
1257
|
+
if (p->scr_codes) free(p->scr_codes);
|
|
1258
|
+
if (p->scr_qual) free(p->scr_qual);
|
|
1259
|
+
free(p);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1264
|
+
PARSER PARALELO EN MEMORIA (OpenMP)
|
|
1265
|
+
─────────────────────────────────────────────────────────────────────────
|
|
1266
|
+
Parsea un bloque de memoria que contiene SOLO registros completos (el
|
|
1267
|
+
llamante en Python garantiza que la ventana termina en un registro completo).
|
|
1268
|
+
El bloque se trocea en N rangos alineados a límites de registro; cada hilo
|
|
1269
|
+
parsea su rango en buffers propios y luego se fusiona en orden. Salida
|
|
1270
|
+
columnar idéntica a bio_parser_next_batch.
|
|
1271
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
1272
|
+
|
|
1273
|
+
typedef struct { int64_t start, len; } Seg;
|
|
1274
|
+
|
|
1275
|
+
/* Lee una línea desde *pos (sin '\n', sin '\r'); avanza *pos tras el '\n'. */
|
|
1276
|
+
static Seg _mem_line(const uint8_t* D, int64_t L, int64_t* pos) {
|
|
1277
|
+
int64_t p = *pos;
|
|
1278
|
+
Seg s; s.start = p; s.len = 0;
|
|
1279
|
+
if (p >= L) { *pos = p; return s; }
|
|
1280
|
+
const uint8_t* nl = (const uint8_t*)memchr(D + p, '\n', (size_t)(L - p));
|
|
1281
|
+
int64_t e = nl ? (int64_t)(nl - D) : L;
|
|
1282
|
+
int64_t len = e - p;
|
|
1283
|
+
if (len > 0 && D[e - 1] == '\r') len--;
|
|
1284
|
+
s.len = len;
|
|
1285
|
+
*pos = nl ? e + 1 : L;
|
|
1286
|
+
return s;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* Siguiente inicio de registro FASTA ('>' a principio de línea) en [from, L). */
|
|
1290
|
+
static int64_t _next_fasta_start(const uint8_t* D, int64_t L, int64_t from) {
|
|
1291
|
+
int64_t p = from;
|
|
1292
|
+
while (p < L) {
|
|
1293
|
+
const uint8_t* nl = (const uint8_t*)memchr(D + p, '\n', (size_t)(L - p));
|
|
1294
|
+
if (!nl) return L;
|
|
1295
|
+
int64_t q = (int64_t)(nl - D) + 1;
|
|
1296
|
+
if (q < L && D[q] == '>') return q;
|
|
1297
|
+
p = q;
|
|
1298
|
+
}
|
|
1299
|
+
return L;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/* Siguiente inicio de registro FASTQ en [from, L). Verifica la estructura de
|
|
1303
|
+
4 líneas (la 3ª empieza por '+') para no confundir un '@' de calidad. */
|
|
1304
|
+
static int64_t _next_fastq_start(const uint8_t* D, int64_t L, int64_t from) {
|
|
1305
|
+
int64_t p = from;
|
|
1306
|
+
while (p < L) {
|
|
1307
|
+
const uint8_t* nl = (const uint8_t*)memchr(D + p, '\n', (size_t)(L - p));
|
|
1308
|
+
if (!nl) return L;
|
|
1309
|
+
int64_t q = (int64_t)(nl - D) + 1;
|
|
1310
|
+
if (q < L && D[q] == '@') {
|
|
1311
|
+
int64_t r = q; int ok = 1;
|
|
1312
|
+
for (int k = 0; k < 2; k++) { /* avanzar 2 líneas */
|
|
1313
|
+
const uint8_t* n2 =
|
|
1314
|
+
(const uint8_t*)memchr(D + r, '\n', (size_t)(L - r));
|
|
1315
|
+
if (!n2) { ok = 0; break; }
|
|
1316
|
+
r = (int64_t)(n2 - D) + 1;
|
|
1317
|
+
}
|
|
1318
|
+
if (ok && r < L && D[r] == '+') return q;
|
|
1319
|
+
}
|
|
1320
|
+
p = q;
|
|
1321
|
+
}
|
|
1322
|
+
return L;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/* Buffers de salida por hilo. */
|
|
1326
|
+
typedef struct {
|
|
1327
|
+
uint8_t* pack; int64_t pack_len, pack_cap;
|
|
1328
|
+
uint8_t* qual; int64_t qual_len, qual_cap;
|
|
1329
|
+
char* hdr; int64_t hdr_len, hdr_cap;
|
|
1330
|
+
uint8_t* code; int64_t code_cap; /* scratch para codificar 1 seq */
|
|
1331
|
+
int32_t* nsy; int32_t nrec; int64_t rec_cap;
|
|
1332
|
+
int32_t* typ;
|
|
1333
|
+
int32_t* hln; /* longitud de cabecera (incl '\0') */
|
|
1334
|
+
int oom; /* 1 si falló una reserva */
|
|
1335
|
+
} TOut;
|
|
1336
|
+
|
|
1337
|
+
static int _ensure(void** buf, int64_t* cap, int64_t need, int64_t esz) {
|
|
1338
|
+
if (*cap >= need) return 1;
|
|
1339
|
+
int64_t nc = (*cap < 1024) ? 1024 : *cap;
|
|
1340
|
+
while (nc < need) nc *= 2;
|
|
1341
|
+
void* nb = realloc(*buf, (size_t)(nc * esz));
|
|
1342
|
+
if (!nb) return 0;
|
|
1343
|
+
*buf = nb; *cap = nc; return 1;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/* Crece nsy/typ/hln a la vez (comparten rec_cap). */
|
|
1347
|
+
static int _ensure_rec(TOut* t) {
|
|
1348
|
+
if (t->rec_cap > t->nrec) return 1;
|
|
1349
|
+
int64_t nc = (t->rec_cap < 1024) ? 1024 : t->rec_cap * 2;
|
|
1350
|
+
int32_t* a = (int32_t*)realloc(t->nsy, (size_t)nc * sizeof(int32_t));
|
|
1351
|
+
if (a) t->nsy = a;
|
|
1352
|
+
int32_t* b = (int32_t*)realloc(t->typ, (size_t)nc * sizeof(int32_t));
|
|
1353
|
+
if (b) t->typ = b;
|
|
1354
|
+
int32_t* c = (int32_t*)realloc(t->hln, (size_t)nc * sizeof(int32_t));
|
|
1355
|
+
if (c) t->hln = c;
|
|
1356
|
+
if (!a || !b || !c) return 0;
|
|
1357
|
+
t->rec_cap = nc;
|
|
1358
|
+
return 1;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/* ── API pública ─────────────────────────────────────────────────────────── */
|
|
1362
|
+
/*
|
|
1363
|
+
* bio_parse_mem_parallel — parsea D[0..len) (solo registros completos) en
|
|
1364
|
+
* paralelo. Devuelve nº de registros (>=0) | -1 error | -2 overflow de buffer.
|
|
1365
|
+
*/
|
|
1366
|
+
EXPORT int32_t bio_parse_mem_parallel(
|
|
1367
|
+
const uint8_t* D, int64_t len,
|
|
1368
|
+
int32_t fmt, int32_t n_threads, int32_t force_type,
|
|
1369
|
+
char* hdr_buf, int32_t hdr_buf_max, int32_t* hdr_off,
|
|
1370
|
+
uint8_t* pack_buf, int64_t pack_buf_max, int32_t* pack_off,
|
|
1371
|
+
int32_t* n_syms, int32_t* types,
|
|
1372
|
+
uint8_t* qual_buf, int64_t qual_buf_max, int32_t* qual_off,
|
|
1373
|
+
int32_t max_records
|
|
1374
|
+
) {
|
|
1375
|
+
if (!D || len <= 0) return 0;
|
|
1376
|
+
|
|
1377
|
+
uint8_t nuc_lut[256], aa_lut[256], is_prot[256];
|
|
1378
|
+
_build_luts(nuc_lut, aa_lut, is_prot);
|
|
1379
|
+
|
|
1380
|
+
int maxt = omp_get_max_threads();
|
|
1381
|
+
int NT = n_threads;
|
|
1382
|
+
if (NT < 1) NT = 1;
|
|
1383
|
+
if (NT > maxt) NT = maxt;
|
|
1384
|
+
if (NT > 64) NT = 64;
|
|
1385
|
+
|
|
1386
|
+
/* Límites de los rangos, alineados a inicio de registro. */
|
|
1387
|
+
int64_t bound[65];
|
|
1388
|
+
bound[0] = 0;
|
|
1389
|
+
for (int t = 1; t < NT; t++) {
|
|
1390
|
+
int64_t guess = (int64_t)((double)len * t / NT);
|
|
1391
|
+
bound[t] = (fmt == 1) ? _next_fasta_start(D, len, guess)
|
|
1392
|
+
: _next_fastq_start(D, len, guess);
|
|
1393
|
+
}
|
|
1394
|
+
bound[NT] = len;
|
|
1395
|
+
/* Colapsar rangos vacíos o desordenados. */
|
|
1396
|
+
for (int t = 1; t <= NT; t++)
|
|
1397
|
+
if (bound[t] < bound[t - 1]) bound[t] = bound[t - 1];
|
|
1398
|
+
|
|
1399
|
+
TOut* outs = (TOut*)calloc((size_t)NT, sizeof(TOut));
|
|
1400
|
+
if (!outs) return -1;
|
|
1401
|
+
|
|
1402
|
+
#pragma omp parallel num_threads(NT)
|
|
1403
|
+
{
|
|
1404
|
+
int tid = omp_get_thread_num();
|
|
1405
|
+
TOut* t = &outs[tid];
|
|
1406
|
+
int64_t lo = bound[tid], hi = bound[tid + 1];
|
|
1407
|
+
int64_t pos = lo;
|
|
1408
|
+
|
|
1409
|
+
while (pos < hi) {
|
|
1410
|
+
int64_t rec_start = pos;
|
|
1411
|
+
int32_t n = 0, type = 0, q = 0;
|
|
1412
|
+
const char* hdr_ptr = NULL; int32_t hlen = 0;
|
|
1413
|
+
const uint8_t* qsrc = NULL;
|
|
1414
|
+
|
|
1415
|
+
if (fmt == 1) {
|
|
1416
|
+
/* FASTA */
|
|
1417
|
+
Seg h = _mem_line(D, len, &pos); /* '>...' */
|
|
1418
|
+
hdr_ptr = (const char*)(D + h.start + 1);
|
|
1419
|
+
hlen = (int32_t)(h.len > 0 ? h.len - 1 : 0);
|
|
1420
|
+
while (pos < len && D[pos] != '>') {
|
|
1421
|
+
Seg sg = _mem_line(D, len, &pos);
|
|
1422
|
+
if (!_ensure((void**)&t->code, &t->code_cap,
|
|
1423
|
+
(int64_t)n + sg.len, 1)) { t->oom = 1; break; }
|
|
1424
|
+
for (int64_t k = 0; k < sg.len; k++)
|
|
1425
|
+
t->code[n++] = D[sg.start + k]; /* crudo; se codifica abajo */
|
|
1426
|
+
}
|
|
1427
|
+
if (t->oom) break;
|
|
1428
|
+
type = (force_type >= 0) ? force_type : 0;
|
|
1429
|
+
if (force_type < 0) {
|
|
1430
|
+
for (int32_t k = 0; k < n; k++)
|
|
1431
|
+
if (is_prot[t->code[k]]) { type = 1; break; }
|
|
1432
|
+
}
|
|
1433
|
+
const uint8_t* lut = type ? aa_lut : nuc_lut;
|
|
1434
|
+
for (int32_t k = 0; k < n; k++) t->code[k] = lut[t->code[k]];
|
|
1435
|
+
} else {
|
|
1436
|
+
/* FASTQ: 4 líneas */
|
|
1437
|
+
Seg h = _mem_line(D, len, &pos);
|
|
1438
|
+
Seg sq = _mem_line(D, len, &pos);
|
|
1439
|
+
Seg pl = _mem_line(D, len, &pos); (void)pl;
|
|
1440
|
+
Seg ql = _mem_line(D, len, &pos);
|
|
1441
|
+
hdr_ptr = (const char*)(D + h.start + 1);
|
|
1442
|
+
hlen = (int32_t)(h.len > 0 ? h.len - 1 : 0);
|
|
1443
|
+
n = (int32_t)sq.len;
|
|
1444
|
+
if (!_ensure((void**)&t->code, &t->code_cap, n, 1)) { t->oom = 1; break; }
|
|
1445
|
+
type = (force_type == 1) ? 1 : 0;
|
|
1446
|
+
const uint8_t* lut = type ? aa_lut : nuc_lut;
|
|
1447
|
+
for (int32_t k = 0; k < n; k++)
|
|
1448
|
+
t->code[k] = lut[D[sq.start + k]];
|
|
1449
|
+
q = (int32_t)ql.len;
|
|
1450
|
+
qsrc = D + ql.start;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (n <= 0) continue; /* registro vacío: saltar (no contar) */
|
|
1454
|
+
|
|
1455
|
+
/* Reservar y volcar al buffer del hilo. La calidad se escribe con
|
|
1456
|
+
longitud == n (se rellena con 0 o se trunca) para mantener el
|
|
1457
|
+
invariante calidad==secuencia incluso en FASTQ malformado. */
|
|
1458
|
+
int64_t plen = (int64_t)(n * 5 + 7) / 8;
|
|
1459
|
+
if (!_ensure((void**)&t->pack, &t->pack_cap, t->pack_len + plen + 1, 1) ||
|
|
1460
|
+
!_ensure((void**)&t->hdr, &t->hdr_cap, t->hdr_len + hlen + 1, 1) ||
|
|
1461
|
+
!_ensure_rec(t) ||
|
|
1462
|
+
(fmt == 2 &&
|
|
1463
|
+
!_ensure((void**)&t->qual, &t->qual_cap, t->qual_len + n, 1))) {
|
|
1464
|
+
t->oom = 1; break;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
bio_pack5(t->code, n, t->pack + t->pack_len);
|
|
1468
|
+
t->pack_len += plen;
|
|
1469
|
+
if (hlen > 0) memcpy(t->hdr + t->hdr_len, hdr_ptr, (size_t)hlen);
|
|
1470
|
+
t->hdr[t->hdr_len + hlen] = '\0';
|
|
1471
|
+
t->hdr_len += hlen + 1;
|
|
1472
|
+
if (fmt == 2) {
|
|
1473
|
+
for (int32_t k = 0; k < n; k++) {
|
|
1474
|
+
uint8_t c = (k < q) ? qsrc[k] : 33u;
|
|
1475
|
+
t->qual[t->qual_len + k] =
|
|
1476
|
+
(uint8_t)((c >= 33u) ? c - 33u : 0u);
|
|
1477
|
+
}
|
|
1478
|
+
t->qual_len += n;
|
|
1479
|
+
}
|
|
1480
|
+
t->nsy[t->nrec] = n;
|
|
1481
|
+
t->typ[t->nrec] = type;
|
|
1482
|
+
t->hln[t->nrec] = hlen + 1;
|
|
1483
|
+
t->nrec++;
|
|
1484
|
+
|
|
1485
|
+
if (rec_start >= hi) break;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/* ── Fusión serial en orden de hilo ──────────────────────────────────── */
|
|
1490
|
+
int rc = 0;
|
|
1491
|
+
int32_t total_rec = 0;
|
|
1492
|
+
int64_t total_pack = 0, total_qual = 0, total_hdr = 0;
|
|
1493
|
+
for (int t = 0; t < NT; t++) {
|
|
1494
|
+
if (outs[t].oom) { rc = -1; }
|
|
1495
|
+
total_rec += outs[t].nrec;
|
|
1496
|
+
total_pack += outs[t].pack_len;
|
|
1497
|
+
total_qual += outs[t].qual_len;
|
|
1498
|
+
total_hdr += outs[t].hdr_len;
|
|
1499
|
+
}
|
|
1500
|
+
if (rc == 0) {
|
|
1501
|
+
if (total_rec > max_records ||
|
|
1502
|
+
total_pack + 1 > pack_buf_max ||
|
|
1503
|
+
total_hdr > hdr_buf_max ||
|
|
1504
|
+
(qual_buf && total_qual > qual_buf_max)) {
|
|
1505
|
+
rc = -2;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (rc == 0) {
|
|
1509
|
+
int32_t ri = 0; int64_t po = 0, qo = 0, ho = 0;
|
|
1510
|
+
pack_off[0] = 0; hdr_off[0] = 0;
|
|
1511
|
+
if (qual_off) qual_off[0] = 0;
|
|
1512
|
+
for (int t = 0; t < NT; t++) {
|
|
1513
|
+
TOut* o = &outs[t];
|
|
1514
|
+
if (o->pack_len) memcpy(pack_buf + po, o->pack, (size_t)o->pack_len);
|
|
1515
|
+
if (o->hdr_len) memcpy(hdr_buf + ho, o->hdr, (size_t)o->hdr_len);
|
|
1516
|
+
if (qual_buf && o->qual_len)
|
|
1517
|
+
memcpy(qual_buf + qo, o->qual, (size_t)o->qual_len);
|
|
1518
|
+
int64_t lpo = po, lqo = qo, lho = ho;
|
|
1519
|
+
for (int32_t j = 0; j < o->nrec; j++) {
|
|
1520
|
+
int32_t n = o->nsy[j];
|
|
1521
|
+
n_syms[ri] = n;
|
|
1522
|
+
types[ri] = o->typ[j];
|
|
1523
|
+
lpo += (int64_t)(n * 5 + 7) / 8; pack_off[ri + 1] = (int32_t)lpo;
|
|
1524
|
+
if (qual_off) { lqo += n; qual_off[ri + 1] = (int32_t)lqo; }
|
|
1525
|
+
lho += o->hln[j]; hdr_off[ri + 1] = (int32_t)lho;
|
|
1526
|
+
ri++;
|
|
1527
|
+
}
|
|
1528
|
+
po += o->pack_len; qo += o->qual_len; ho += o->hdr_len;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
for (int t = 0; t < NT; t++) {
|
|
1533
|
+
free(outs[t].pack); free(outs[t].qual); free(outs[t].hdr);
|
|
1534
|
+
free(outs[t].code); free(outs[t].nsy); free(outs[t].typ); free(outs[t].hln);
|
|
1535
|
+
}
|
|
1536
|
+
free(outs);
|
|
1537
|
+
return (rc != 0) ? rc : total_rec;
|
|
1538
|
+
}
|