minibwa 0.0.0

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +119 -0
  4. data/ext/minibwa/extconf.rb +105 -0
  5. data/ext/minibwa/mb_buffer.c +113 -0
  6. data/ext/minibwa/mb_hit.c +192 -0
  7. data/ext/minibwa/mb_index.c +462 -0
  8. data/ext/minibwa/mb_index_build.c +174 -0
  9. data/ext/minibwa/mb_options.c +301 -0
  10. data/ext/minibwa/minibwa/LICENSE.txt +37 -0
  11. data/ext/minibwa/minibwa/align.c +930 -0
  12. data/ext/minibwa/minibwa/bseq.h +45 -0
  13. data/ext/minibwa/minibwa/bwt.c +715 -0
  14. data/ext/minibwa/minibwa/bwt.h +86 -0
  15. data/ext/minibwa/minibwa/cs.c +161 -0
  16. data/ext/minibwa/minibwa/format.c +356 -0
  17. data/ext/minibwa/minibwa/index.c +342 -0
  18. data/ext/minibwa/minibwa/kalloc.c +224 -0
  19. data/ext/minibwa/minibwa/kalloc.h +54 -0
  20. data/ext/minibwa/minibwa/ketopt.h +123 -0
  21. data/ext/minibwa/minibwa/kommon.c +374 -0
  22. data/ext/minibwa/minibwa/kommon.h +85 -0
  23. data/ext/minibwa/minibwa/kseq.h +256 -0
  24. data/ext/minibwa/minibwa/ksort.h +163 -0
  25. data/ext/minibwa/minibwa/ksw2.h +220 -0
  26. data/ext/minibwa/minibwa/ksw2_extd2_sse.c +403 -0
  27. data/ext/minibwa/minibwa/ksw2_extz2_sse.c +296 -0
  28. data/ext/minibwa/minibwa/ksw2_ll_sse.c +341 -0
  29. data/ext/minibwa/minibwa/kthread.h +15 -0
  30. data/ext/minibwa/minibwa/l2bit.c +479 -0
  31. data/ext/minibwa/minibwa/l2bit.h +72 -0
  32. data/ext/minibwa/minibwa/lchain.c +231 -0
  33. data/ext/minibwa/minibwa/libsais.c +6985 -0
  34. data/ext/minibwa/minibwa/libsais.h +106 -0
  35. data/ext/minibwa/minibwa/libsais64.c +7064 -0
  36. data/ext/minibwa/minibwa/libsais64.h +81 -0
  37. data/ext/minibwa/minibwa/map-algo.c +769 -0
  38. data/ext/minibwa/minibwa/mbpriv.h +148 -0
  39. data/ext/minibwa/minibwa/minibwa.h +176 -0
  40. data/ext/minibwa/minibwa/options.c +116 -0
  41. data/ext/minibwa/minibwa/pe.c +559 -0
  42. data/ext/minibwa/minibwa/s2n-lite.h +59 -0
  43. data/ext/minibwa/minibwa/seed.c +354 -0
  44. data/ext/minibwa/minibwa.c +67 -0
  45. data/ext/minibwa/minibwa.h +51 -0
  46. data/lib/minibwa/hit.rb +110 -0
  47. data/lib/minibwa/index.rb +77 -0
  48. data/lib/minibwa/options.rb +235 -0
  49. data/lib/minibwa/sam.rb +85 -0
  50. data/lib/minibwa/version.rb +6 -0
  51. data/lib/minibwa.rb +11 -0
  52. metadata +88 -0
@@ -0,0 +1,354 @@
1
+ #include <string.h>
2
+ #include <assert.h>
3
+ #include <stdio.h>
4
+ #include "mbpriv.h"
5
+ #include "kalloc.h"
6
+ #include "ksort.h"
7
+
8
+ #define key_sai0(a) ((a).x[0])
9
+ KRADIX_SORT_INIT(mb_sai0, mb_sai_t, key_sai0, 8)
10
+
11
+ #define key_sais(a) ((a).size)
12
+ KRADIX_SORT_INIT(mb_sais, mb_sai_t, key_sais, 8)
13
+
14
+ #define key_saii(a) ((a).info)
15
+ KRADIX_SORT_INIT(mb_saii, mb_sai_t, key_saii, 8)
16
+
17
+ #define key_anchor(a) ((a).tpos)
18
+ KRADIX_SORT_INIT(mb_anchor, mb_anchor_t, key_anchor, 8)
19
+
20
+ /***********
21
+ * Seeding *
22
+ ***********/
23
+
24
+ void mb_seed_intv(void *km, const mb_bwt_t *bwt, int32_t len, const uint8_t *seq, int32_t min_len, int32_t max_sub_occ, mb_sai_v *v)
25
+ {
26
+ int64_t x = 0, i, n_a0;
27
+ mb_sai_t p;
28
+
29
+ v->n = 0;
30
+ do { // pass 1: standard SMEMs
31
+ x = mb_bwt_smem(bwt, len, seq, x, min_len, 1, &p);
32
+ if (p.size > 0) {
33
+ Kgrow(km, mb_sai_t, v->a, v->n, v->m);
34
+ v->a[v->n++] = p;
35
+ }
36
+ } while (x < len);
37
+
38
+ n_a0 = v->n;
39
+ for (i = 0; i < n_a0; ++i) { // pass 2: sub-SMEMs
40
+ int32_t sub_min_len;
41
+ uint32_t st = v->a[i].info>>32, en = (uint32_t)v->a[i].info;
42
+ if (en - st < min_len * 2 || v->a[i].size > max_sub_occ)
43
+ continue;
44
+ x = st;
45
+ sub_min_len = (en - st) / 2 > min_len? (en - st) / 2 : min_len;
46
+ do { // if two SMEMs have large overlaps, we may find the same sub intervals in both. A rare case not worth optimizing
47
+ x = mb_bwt_smem(bwt, en, seq, x, sub_min_len, v->a[i].size + 1, &p);
48
+ if (p.size > v->a[i].size) {
49
+ Kgrow(km, mb_sai_t, v->a, v->n, v->m);
50
+ v->a[v->n++] = p;
51
+ }
52
+ } while (x < en);
53
+ }
54
+ }
55
+
56
+ void mb_seed_intv_batch(void *km, const mb_bwt_t *bwt, int32_t n_seq, const int32_t *len, uint8_t *const* seq, int32_t min_len, int32_t max_sub_occ, mb_sai_v *v)
57
+ { // identical to mb_seed_intv() though the order of intervals is often different
58
+ const int max_batch_size = 50;
59
+ mb_smem_entry_t *s;
60
+ int32_t i, j, n_s, *nv;
61
+
62
+ // first pass: standard SMEMs
63
+ s = Kcalloc(km, mb_smem_entry_t, max_batch_size);
64
+ nv = Kcalloc(km, int32_t, n_seq);
65
+ for (i = 0; i < n_seq; ++i) v[i].n = 0;
66
+ for (i = 0; i < n_seq; i += max_batch_size) {
67
+ int32_t en = i + max_batch_size < n_seq? i + max_batch_size : n_seq;
68
+ for (j = i; j < en; ++j) {
69
+ mb_smem_entry_t *t = &s[j - i];
70
+ t->min_len = min_len;
71
+ t->min_occ = 1;
72
+ t->st = 0, t->en = len[j];
73
+ t->q = seq[j];
74
+ t->v = &v[j];
75
+ }
76
+ mb_bwt_smem_batch(km, bwt, en - i, s);
77
+ }
78
+
79
+ // second pass; sub-SMEMs
80
+ for (i = 0; i < n_seq; ++i) nv[i] = v[i].n;
81
+ for (i = n_s = 0; i < n_seq; ++i) {
82
+ for (j = 0; j < nv[i]; ++j) {
83
+ mb_smem_entry_t *t;
84
+ uint32_t st = v[i].a[j].info>>32, en = (uint32_t)v[i].a[j].info;
85
+ if (en - st < min_len * 2 || v[i].a[j].size > max_sub_occ)
86
+ continue;
87
+ t = &s[n_s++];
88
+ t->min_len = (en - st) / 2 > min_len? (en - st) / 2 : min_len;
89
+ t->min_occ = v[i].a[j].size + 1;
90
+ t->st = st, t->en = en;
91
+ t->q = seq[i];
92
+ t->v = &v[i];
93
+ if (n_s == max_batch_size) {
94
+ mb_bwt_smem_batch(km, bwt, n_s, s);
95
+ n_s = 0;
96
+ }
97
+ }
98
+ }
99
+ if (n_s > 0)
100
+ mb_bwt_smem_batch(km, bwt, n_s, s);
101
+ kfree(km, nv);
102
+ kfree(km, s);
103
+ }
104
+
105
+ /*****************************
106
+ * Seed/anchor deduplication *
107
+ *****************************/
108
+
109
+ static void mb_seed_sort_dedup(mb_sai_v *u)
110
+ {
111
+ int64_t i, i0, j;
112
+ if (u->n <= 1) return;
113
+ // sort by ::{x[0],size,info}
114
+ radix_sort_mb_sai0(u->a, u->a + u->n); // sort by ::x[0]
115
+ for (i = 1, i0 = 0; i <= u->n; ++i) {
116
+ if (i == u->n || u->a[i].x[0] != u->a[i0].x[0]) {
117
+ if (i - i0 > 1) {
118
+ int64_t k, k0, n1 = i - i0;
119
+ mb_sai_t *a1 = &u->a[i0];
120
+ radix_sort_mb_sais(&u->a[i0], &u->a[i]); // sort by ::size
121
+ kom_reverse(mb_sai_t, n1, a1);
122
+ for (k = i0 + 1, k0 = i0; k <= i; ++k) {
123
+ if (k == i || u->a[k0].size != u->a[k].size) {
124
+ if (k - k0 > 1)
125
+ radix_sort_mb_saii(&u->a[k0], &u->a[k]); // sort by ::info
126
+ k0 = k;
127
+ }
128
+ }
129
+ }
130
+ i0 = i;
131
+ }
132
+ }
133
+ // dedup
134
+ for (i = 1, j = 0; i < u->n; ++i)
135
+ if (!(u->a[i].x[0] == u->a[j].x[0] && u->a[i].size == u->a[j].size && u->a[i].info == u->a[j].info))
136
+ u->a[++j] = u->a[i];
137
+ u->n = j + 1;
138
+ }
139
+
140
+ /* Remove duplicated anchors. The two-round seeding algorithm may lead an
141
+ * anchor precisely contained in a longer anchor. This routine filters out the
142
+ * shorter anchor. This wouldn't happen to minimap2. */
143
+ static void mb_anchor_dedup(mb_anchor_v *v) // NB: assuming sorted by tpos
144
+ {
145
+ const int max_back = 100; // to avoid quadratic behavior in the worst case
146
+ int64_t i, j, k;
147
+ for (i = 1; i < v->n; ++i) {
148
+ mb_anchor_t *ai = &v->a[i];
149
+ int64_t tsj, tsi = ai->tpos + 1 - ai->len;
150
+ int32_t qsj, qsi = ai->qpos + 1 - ai->len;
151
+ for (j = i - 1, k = 0; j >= 0 && k < max_back; --j, ++k) {
152
+ mb_anchor_t *aj = &v->a[j];
153
+ if (aj->sid != ai->sid) break;
154
+ if (aj->tpos < tsi) break;
155
+ tsj = aj->tpos + 1 - aj->len;
156
+ qsj = aj->qpos + 1 - aj->len;
157
+ if (tsj >= tsi) { // then j is contained in i
158
+ if (tsj - tsi == qsj - qsi) aj->flt = 1;
159
+ } else if (ai->tpos == aj->tpos) { // then i is contained in j
160
+ if (ai->qpos == aj->qpos) ai->flt = 1;
161
+ }
162
+ }
163
+ }
164
+ for (i = j = 0; i < v->n; ++i)
165
+ if (!v->a[i].flt) v->a[j++] = v->a[i];
166
+ v->n = j;
167
+ }
168
+
169
+ /************************
170
+ * Get contig positions *
171
+ ************************/
172
+
173
+ typedef struct { int64_t st, en; } anchor_aux_t;
174
+ typedef struct { int64_t a, i; } sa_aux_t;
175
+
176
+ static void process_batch(void *km, const mb_idx_t *idx, const anchor_aux_t *aux, int32_t m, const sa_aux_t *b, uint64_t *a, int32_t qlen, l2b_meth_t mt, const mb_sai_v *u, mb_anchor_v *v)
177
+ {
178
+ int64_t j, k;
179
+ for (k = 0; k < m; ++k) a[k] = b[k].a;
180
+ mb_bwt_sa_batch(km, idx->bwt, m, a);
181
+ for (k = 0; k < m; ++k) {
182
+ const anchor_aux_t *p = &aux[b[k].i];
183
+ for (j = p->st; j < p->en; ++j) {
184
+ int32_t qs = u->a[j].info>>32, qe = (int32_t)u->a[j].info;
185
+ int32_t rev, len = qe - qs;
186
+ int64_t tid, cst;
187
+ l2b_meth_t mt_anchor;
188
+ const l2b_ctg_t *ctg;
189
+ mb_anchor_t *q;
190
+ if (mt != L2B_METH_NONE) {
191
+ tid = l2b_intv2cid_meth(idx->l2b, a[k], a[k] + len, &mt_anchor, &cst, &rev);
192
+ if (tid < 0) continue;
193
+ // R1(C2T): keep c2t_f(copy0) and g2a_r(copy2); R2(G2A): keep g2a_f(copy1) and c2t_r(copy3)
194
+ if ((mt_anchor == mt) != (rev == 0)) continue; // filter
195
+ } else {
196
+ tid = l2b_intv2cid(idx->l2b, a[k], a[k] + len, &cst, &rev);
197
+ if (tid < 0) continue;
198
+ }
199
+ rev = !!rev;
200
+ ctg = &idx->l2b->ctg[tid];
201
+ Kgrow(km, mb_anchor_t, v->a, v->n, v->m);
202
+ q = &v->a[v->n++];
203
+ memset(q, 0, sizeof(*q));
204
+ q->sid = tid << 1 | rev;
205
+ q->len = len;
206
+ q->qpos = rev? qlen - 1 - qs : qs + len - 1;
207
+ q->tpos = ctg->off * 2 + ctg->len * rev + cst + len - 1; // for sorting; will be adjusted later
208
+ }
209
+ }
210
+ }
211
+
212
+ static void mb_anchor_split_meth(void *km, const l2b_t *l2b, int32_t min_len, int32_t qlen, const uint8_t *qseq0, l2b_meth_t mt0, mb_anchor_v *v)
213
+ {
214
+ int64_t i, m_a = v->n * 2, n_a = 0;
215
+ int32_t max_len = 0;
216
+ mb_anchor_t *a;
217
+ uint8_t *tseq, *qseq2[2];
218
+
219
+ for (i = 0; i < v->n; ++i)
220
+ if (max_len < v->a[i].len)
221
+ max_len = v->a[i].len;
222
+ tseq = Kmalloc(km, uint8_t, max_len + qlen * 2);
223
+ qseq2[0] = tseq + max_len;
224
+ qseq2[1] = qseq2[0] + qlen;
225
+ memcpy(qseq2[0], qseq0, qlen);
226
+ for (i = 0; i < qlen; ++i)
227
+ qseq2[1][qlen - 1 - i] = qseq0[i] > 3? 4 : 3 - qseq0[i];
228
+
229
+ a = Kmalloc(km, mb_anchor_t, m_a);
230
+ for (i = 0; i < v->n; ++i) {
231
+ mb_anchor_t *p, *q = &v->a[i];
232
+ const l2b_ctg_t *ctg = &l2b->ctg[q->sid>>1];
233
+ int32_t rev = q->sid&1;
234
+ int64_t tpos = q->tpos - (ctg->off * 2 + ctg->len * rev); // NB: requiring concatenated ::tpos!!
235
+ int64_t ts = tpos + 1 - q->len;
236
+ int32_t qs = q->qpos + 1 - q->len, j, j0;
237
+ uint8_t t_allow, q_allow;
238
+ l2b_meth_t mt;
239
+ const uint8_t *qseq = qseq2[rev] + qs;
240
+ l2b_getseq(l2b, q->sid>>1, ts, ts + q->len, tseq);
241
+ mt = q->sid&1? l2b_meth_rev(mt0) : mt0;
242
+ t_allow = mt == L2B_METH_C2T? 1 : 2;
243
+ q_allow = mt == L2B_METH_C2T? 3 : 0;
244
+ for (j0 = j = 0; j <= q->len; ++j) {
245
+ if (j == q->len || tseq[j] == 4 || qseq[j] == 4 || (tseq[j] != qseq[j] && !(tseq[j] == t_allow && qseq[j] == q_allow))) {
246
+ if (j - j0 >= min_len) {
247
+ Kgrow(km, mb_anchor_t, a, n_a, m_a);
248
+ p = &a[n_a++];
249
+ *p = *q;
250
+ p->len = j - j0;
251
+ p->qpos = q->qpos - (q->len - j);
252
+ p->tpos = q->tpos - (q->len - j);
253
+ }
254
+ j0 = j + 1;
255
+ }
256
+ }
257
+ }
258
+ kfree(km, tseq);
259
+ Kgrow(km, mb_anchor_t, v->a, n_a, v->m);
260
+ memcpy(v->a, a, n_a * sizeof(mb_anchor_t));
261
+ v->n = n_a;
262
+ kfree(km, a);
263
+ }
264
+
265
+ /* Converting seed intervals to anchors. This function batches small SA
266
+ * intervals and calls mb_bwt_sa_batch() in process_batch(). With prefetch, the
267
+ * strategy noticeably improves the performance. */
268
+ double mb_anchor(void *km, const mb_idx_t *idx, mb_sai_v *u, int32_t min_len, int32_t qlen, const uint8_t *qseq, l2b_meth_t mt, int32_t max_occ, mb_anchor_v *v)
269
+ {
270
+ const int batch_size = 20;
271
+ int32_t n_aux, m, m_a;
272
+ int64_t i, i0, j, k;
273
+ uint64_t *a;
274
+ double seed_ratio = 1.0;
275
+ sa_aux_t *b;
276
+ anchor_aux_t *aux;
277
+
278
+ v->n = 0;
279
+ if (u->n == 0) return seed_ratio; // no anchors
280
+ mb_seed_sort_dedup(u);
281
+
282
+ for (i = 0, k = 0; i < u->n; ++i) // pre-calculate the size of v->a
283
+ k += u->a[i].size < max_occ? u->a[i].size : max_occ;
284
+ Kgrow(km, mb_anchor_t, v->a, k - 1, v->m); // preallocate
285
+
286
+ for (i = 1, i0 = 0, n_aux = 0; i <= u->n; ++i) // pre-compute n_aux
287
+ if (i == u->n || u->a[i].x[0] != u->a[i0].x[0] || u->a[i].size != u->a[i0].size)
288
+ ++n_aux, i0 = i;
289
+ aux = Kmalloc(km, anchor_aux_t, n_aux);
290
+ for (i = 1, i0 = 0, n_aux = 0; i <= u->n; ++i) // populate aux[]
291
+ if (i == u->n || u->a[i].x[0] != u->a[i0].x[0] || u->a[i].size != u->a[i0].size)
292
+ aux[n_aux].st = i0, aux[n_aux++].en = i, i0 = i;
293
+
294
+ m_a = max_occ > batch_size? max_occ : batch_size; // max size of a[] and b[]
295
+ a = Kmalloc(km, uint64_t, m_a);
296
+ b = Kmalloc(km, sa_aux_t, m_a);
297
+ for (i = 0, m = 0; i < n_aux; ++i) {
298
+ const anchor_aux_t *p = &aux[i];
299
+ const mb_sai_t *q = &u->a[p->st];
300
+ if (q->size + m > batch_size) {
301
+ process_batch(km, idx, aux, m, b, a, qlen, mt, u, v);
302
+ m = 0;
303
+ }
304
+ if (q->size <= max_occ) { // get SA for all of them
305
+ for (j = 0; j < q->size; ++j)
306
+ b[m].a = q->x[0] + j, b[m++].i = i;
307
+ } else { // sample up to max_occ
308
+ int32_t n = 0;
309
+ for (j = 0; j < q->size && n < max_occ; ++n) {
310
+ int32_t step = (q->size - j) / (max_occ - n);
311
+ if (step < 1) step = 1;
312
+ b[m].a = q->x[0] + j, b[m++].i = i;
313
+ j += step;
314
+ }
315
+ }
316
+ assert(m <= m_a); // shouldn't happen!
317
+ }
318
+ process_batch(km, idx, aux, m, b, a, qlen, mt, u, v);
319
+ kfree(km, b);
320
+ kfree(km, a);
321
+ kfree(km, aux);
322
+
323
+ if (mt != L2B_METH_NONE && v->n > 0) {
324
+ int64_t t0, t1;
325
+ for (i = 0, t0 = 0; i < v->n; ++i) t0 += v->a[i].len;
326
+ mb_anchor_split_meth(km, idx->l2b, min_len, qlen, qseq, mt, v);
327
+ for (i = 0, t1 = 0; i < v->n; ++i) t1 += v->a[i].len;
328
+ seed_ratio = (double)t1 / t0;
329
+ }
330
+
331
+ radix_sort_mb_anchor(v->a, v->a + v->n);
332
+ for (i = 0; i < v->n; ++i) { // adjust mb_anchor_t::tpos
333
+ mb_anchor_t *q = &v->a[i];
334
+ const l2b_ctg_t *ctg = &idx->l2b->ctg[q->sid>>1];
335
+ q->tpos -= ctg->off * 2 + ctg->len * (q->sid&1);
336
+ }
337
+ mb_anchor_dedup(v);
338
+ return seed_ratio;
339
+ }
340
+
341
+ void mb_anchor_sort(const l2b_t *l2b, int64_t n_a, mb_anchor_t *a)
342
+ {
343
+ int64_t i;
344
+ if (n_a <= 1) return;
345
+ for (i = 0; i < n_a; ++i) {
346
+ const l2b_ctg_t *ctg = &l2b->ctg[a[i].sid>>1];
347
+ a[i].tpos += ctg->off * 2 + ctg->len * (a[i].sid&1);
348
+ }
349
+ radix_sort_mb_anchor(a, a + n_a);
350
+ for (i = 0; i < n_a; ++i) {
351
+ const l2b_ctg_t *ctg = &l2b->ctg[a[i].sid>>1];
352
+ a[i].tpos -= ctg->off * 2 + ctg->len * (a[i].sid&1);
353
+ }
354
+ }
@@ -0,0 +1,67 @@
1
+ #include "minibwa.h"
2
+
3
+ VALUE rb_mMinibwa;
4
+ VALUE rb_cMinibwaOptions;
5
+ VALUE rb_cMinibwaIndex;
6
+ VALUE rb_cMinibwaBuffer;
7
+ VALUE rb_cMinibwaHit;
8
+ VALUE rb_eMinibwaError;
9
+
10
+ /*
11
+ * Document-module: Minibwa
12
+ *
13
+ * Ruby bindings for minibwa, a short-read aligner.
14
+ */
15
+
16
+ /*
17
+ * Document-class: Minibwa::Error
18
+ *
19
+ * Raised when minibwa cannot build, load, or use an index.
20
+ */
21
+
22
+ /* ------------------------------------------------------------------ */
23
+ /* TypedData unwrapping helpers */
24
+ /* ------------------------------------------------------------------ */
25
+
26
+ mb_opt_t *
27
+ rb_minibwa_get_opt(VALUE self)
28
+ {
29
+ mb_opt_t *opt;
30
+ TypedData_Get_Struct(self, mb_opt_t, &rb_minibwa_options_type, opt);
31
+ return opt;
32
+ }
33
+
34
+ mb_idx_t *
35
+ rb_minibwa_get_idx(VALUE self)
36
+ {
37
+ mb_idx_t *idx;
38
+ TypedData_Get_Struct(self, mb_idx_t, &rb_minibwa_idx_type, idx);
39
+ return idx;
40
+ }
41
+
42
+ mb_tbuf_t *
43
+ rb_minibwa_get_tbuf(VALUE self)
44
+ {
45
+ if (NIL_P(self)) return NULL;
46
+ mb_tbuf_t *b;
47
+ TypedData_Get_Struct(self, mb_tbuf_t, &rb_minibwa_buffer_type, b);
48
+ return b;
49
+ }
50
+
51
+ /* ------------------------------------------------------------------ */
52
+ /* Init */
53
+ /* ------------------------------------------------------------------ */
54
+
55
+ RUBY_FUNC_EXPORTED void
56
+ Init_minibwa(void)
57
+ {
58
+ rb_mMinibwa = rb_define_module("Minibwa");
59
+
60
+ rb_eMinibwaError = rb_define_class_under(rb_mMinibwa, "Error", rb_eStandardError);
61
+
62
+ rb_minibwa_init_options();
63
+ rb_minibwa_init_index();
64
+ rb_minibwa_init_buffer();
65
+ rb_minibwa_init_hit();
66
+ rb_minibwa_init_index_build();
67
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Shared header for the Ruby binding: module/class handles, the per-file
3
+ * initializers called from Init_minibwa(), and the helpers that cross
4
+ * translation units.
5
+ */
6
+
7
+ #ifndef RB_MINIBWA_H
8
+ /* NOT "MINIBWA_H": that is upstream's guard. Sharing it would make
9
+ * whichever header is included second expand to nothing. */
10
+ #define RB_MINIBWA_H 1
11
+
12
+ #include "ruby.h"
13
+ #include "ruby/thread.h"
14
+
15
+ /* Upstream public API. Spelled with the directory prefix because this file
16
+ * shares its basename with it, and a bare "minibwa.h" from extension sources
17
+ * resolves to this file first. */
18
+ #include "minibwa/minibwa.h"
19
+
20
+ /* Module, classes and exception, defined in minibwa.c */
21
+ extern VALUE rb_mMinibwa;
22
+ extern VALUE rb_cMinibwaOptions;
23
+ extern VALUE rb_cMinibwaIndex;
24
+ extern VALUE rb_cMinibwaBuffer;
25
+ extern VALUE rb_cMinibwaHit;
26
+ extern VALUE rb_eMinibwaError;
27
+
28
+ /* Per-file initializers, called in this order from Init_minibwa() */
29
+ void rb_minibwa_init_options(void);
30
+ void rb_minibwa_init_index(void);
31
+ void rb_minibwa_init_buffer(void);
32
+ void rb_minibwa_init_hit(void);
33
+ void rb_minibwa_init_index_build(void);
34
+
35
+ /* TypedData unwrapping. Each raises TypeError on a mismatched receiver;
36
+ * rb_minibwa_get_tbuf() maps Qnil to NULL, which mb_map() accepts. */
37
+ mb_opt_t *rb_minibwa_get_opt(VALUE self);
38
+ mb_idx_t *rb_minibwa_get_idx(VALUE self);
39
+ mb_tbuf_t *rb_minibwa_get_tbuf(VALUE self);
40
+
41
+ /* TypedData type descriptors, defined in their respective files */
42
+ extern const rb_data_type_t rb_minibwa_options_type;
43
+ extern const rb_data_type_t rb_minibwa_idx_type;
44
+ extern const rb_data_type_t rb_minibwa_buffer_type;
45
+
46
+ /* mb_hit.c: conversion to Ruby. Both take ownership -- they free hit->p
47
+ * for every hit, and rb_minibwa_hit_ary_new() also frees the array. */
48
+ VALUE rb_minibwa_hit_new(const mb_idx_t *idx, mb_hit_t *hit);
49
+ VALUE rb_minibwa_hit_ary_new(const mb_idx_t *idx, mb_hit_t *hits, int32_t n_hit);
50
+
51
+ #endif /* RB_MINIBWA_H */
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minibwa
4
+ # A single alignment of one query against the reference.
5
+ #
6
+ # The fields are filled in by mb_hit.c. This file reopens the class for the
7
+ # values that can be derived without touching C: CIGAR formatting, the
8
+ # soft-clipped CIGAR rebuilt from qs/qe -- upstream only reports the aligned
9
+ # region -- the strand character, and #inspect.
10
+ #
11
+ # @!attribute [r] tid
12
+ # @return [Integer] target contig ID.
13
+ # @!attribute [r] ts
14
+ # @return [Integer] zero-based target start.
15
+ # @!attribute [r] te
16
+ # @return [Integer] zero-based target end.
17
+ # @!attribute [r] qs
18
+ # @return [Integer] zero-based query start.
19
+ # @!attribute [r] qe
20
+ # @return [Integer] zero-based query end.
21
+ # @!attribute [r] score
22
+ # @return [Integer] alignment score.
23
+ # @!attribute [r] score0
24
+ # @return [Integer] best-chain score before post-processing.
25
+ # @!attribute [r] mlen
26
+ # @return [Integer] number of matching bases.
27
+ # @!attribute [r] blen
28
+ # @return [Integer] alignment block length.
29
+ # @!attribute [r] mapq
30
+ # @return [Integer] mapping quality.
31
+ # @!attribute [r] cnt
32
+ # @return [Integer] number of seed hits.
33
+ # @!attribute [r] n_sub
34
+ # @return [Integer] number of suboptimal hits.
35
+ # @!attribute [r] subsc
36
+ # @return [Integer] suboptimal alignment score.
37
+ # @!attribute [r] hash
38
+ # @return [Integer] upstream hash value for tie-breaking.
39
+ # @!attribute [r] rev
40
+ # @return [Boolean] whether the hit is on the reverse strand.
41
+ # @!attribute [r] proper_pair
42
+ # @return [Boolean] whether the hit is part of a proper pair.
43
+ # @!attribute [r] sam_pri
44
+ # @return [Boolean] whether the hit is primary in SAM output.
45
+ # @!attribute [r] flt
46
+ # @return [Boolean] whether the hit is filtered.
47
+ # @!attribute [r] inv
48
+ # @return [Boolean] whether the hit represents an inversion.
49
+ # @!attribute [r] split
50
+ # @return [Boolean] whether the hit is split.
51
+ # @!attribute [r] split_inv
52
+ # @return [Boolean] whether the split hit is inverted.
53
+ # @!attribute [r] rescued
54
+ # @return [Boolean] whether paired-end rescue found the hit.
55
+ # @!attribute [r] frac_high
56
+ # @return [Boolean] whether the high-frequency seed fraction is high.
57
+ # @!attribute [r] seed_ratio
58
+ # @return [Boolean] whether the seed ratio flag is set.
59
+ # @!attribute [r] ctg
60
+ # @return [String, nil] target contig name, when available.
61
+ # @!attribute [r] dp_score
62
+ # @return [Integer] dynamic-programming score.
63
+ # @!attribute [r] dp_max0
64
+ # @return [Integer] first dynamic-programming maximum.
65
+ # @!attribute [r] dp_max
66
+ # @return [Integer] dynamic-programming maximum.
67
+ # @!attribute [r] dp_max2
68
+ # @return [Integer] second dynamic-programming maximum.
69
+ # @!attribute [r] n_ambi
70
+ # @return [Integer] number of ambiguous reference bases.
71
+ # @!attribute [r] cs_flag
72
+ # @return [Integer] CS tag flags.
73
+ # @!attribute [r] cigar
74
+ # @return [Array<Array(Integer, Integer)>] CIGAR operations as [length, op] pairs.
75
+ class Hit
76
+ # CIGAR operation characters indexed by operation code.
77
+ CIGAR_STR = 'MIDNSHP=XB'
78
+
79
+ # Returns the CIGAR string for the aligned region (no soft/hard clips).
80
+ def cigar_str
81
+ @cigar.map { |len, op| "#{len}#{CIGAR_STR[op]}" }.join
82
+ end
83
+
84
+ # Returns the full CIGAR string including soft clips.
85
+ #
86
+ # The query length is required because the Hit only stores the aligned
87
+ # region (qs/qe); the clips are derived from the difference.
88
+ #
89
+ # hit.full_cigar_str(query.length) # => "2S35M3S"
90
+ def full_cigar_str(query_len)
91
+ Sam.build_full_cigar(self, query_len)
92
+ end
93
+
94
+ # Returns the strand as '+' or '-'.
95
+ def strand
96
+ rev ? '-' : '+'
97
+ end
98
+
99
+ # Returns the target (reference) span length.
100
+ def tlen
101
+ te - ts
102
+ end
103
+
104
+ # Returns a compact representation of the most commonly inspected fields.
105
+ def inspect
106
+ attrs = %i[tid ctg ts te qs qe strand score mapq mlen blen]
107
+ "#<#{self.class.name} #{attrs.map { |a| "#{a}=#{send(a).inspect}" }.join(' ')}>"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minibwa
4
+ # A minibwa index -- the FM-index plus the 2-bit reference -- and the object
5
+ # you map reads against.
6
+ #
7
+ # The class comes from mb_index.c and mb_index_build.c; this file reopens it
8
+ # for checking paths and permissions
9
+ # before calling in. That check is not cosmetic: upstream reports bad input
10
+ # to the index builder with kom_assert(), which abort()s the whole process
11
+ # instead of returning an error.
12
+ class Index
13
+ # IMPORTANT: alias the raw C methods BEFORE redefining them.
14
+ # Singleton methods (Index.load, etc.)
15
+ class << self
16
+ # @api private
17
+ alias _build build
18
+ # @api private
19
+ alias _load load
20
+ # @api private
21
+ alias _load_mmap load_mmap
22
+ private :_build, :_load, :_load_mmap
23
+ end
24
+ private_class_method :_build, :_load, :_load_mmap
25
+
26
+ # Instance methods (index.map, index.map_batch)
27
+ # @api private
28
+ alias _map map
29
+ # @api private
30
+ alias _map_batch map_batch
31
+ private :_map, :_map_batch
32
+
33
+ class << self
34
+ # Validates that the FASTA file exists and is readable before calling
35
+ # the C extension (which would abort() on bad input).
36
+ def build(fasta, prefix, **kwargs)
37
+ raise Error, "cannot read FASTA file: #{fasta}" unless File.readable?(fasta)
38
+
39
+ dir = File.dirname(prefix)
40
+ raise Error, "cannot write to directory: #{dir}" unless File.writable?(dir)
41
+
42
+ _build(fasta, prefix, **kwargs)
43
+ end
44
+
45
+ # Wraps Index.load with path validation.
46
+ def load(prefix, **kwargs)
47
+ check_prefix(prefix)
48
+ _load(prefix, **kwargs)
49
+ end
50
+
51
+ # Wraps Index.load_mmap with path validation.
52
+ def load_mmap(prefix, **kwargs)
53
+ check_prefix(prefix)
54
+ _load_mmap(prefix, **kwargs)
55
+ end
56
+
57
+ private
58
+
59
+ def check_prefix(prefix)
60
+ mbw = "#{prefix}.mbw"
61
+ return if File.readable?(mbw)
62
+
63
+ raise Error, "index not found: #{mbw}"
64
+ end
65
+ end
66
+
67
+ # Convenience method: map a single sequence with keyword arguments.
68
+ def map(seq, name: nil, opt: nil, buf: nil, meth: 0)
69
+ _map(seq, name: name, opt: opt, buf: buf, meth: meth)
70
+ end
71
+
72
+ # Convenience method: map a batch with keyword arguments.
73
+ def map_batch(seqs, names: nil, opt: nil, buf: nil)
74
+ _map_batch(seqs, names: names, opt: opt, buf: buf)
75
+ end
76
+ end
77
+ end