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,462 @@
1
+ /*
2
+ * Minibwa::Index -- wraps mb_idx_t, the loaded FM-index and 2-bit reference,
3
+ * and is where mapping is driven from.
4
+ *
5
+ * Opening (mb_idx_load, mb_idx_load_mmap) and mapping (mb_map, mb_map_batch)
6
+ * are long-running and CPU-bound, so all of them release the GVL. Query
7
+ * sequences and names are kept alive via Ruby references for the duration
8
+ * of the call; callers must not mutate them while the call is in flight.
9
+ *
10
+ * mb_idx_load() reports a missing or corrupt index by returning NULL, which
11
+ * becomes Minibwa::Error. Contig lookups are range-checked upstream (NULL or
12
+ * -1 for an out-of-range tid) and become IndexError.
13
+ *
14
+ * Freed with mb_idx_destroy().
15
+ */
16
+
17
+ #include "minibwa.h"
18
+
19
+ static VALUE
20
+ rb_minibwa_kw(VALUE kwargs, const char *name)
21
+ {
22
+ if (NIL_P(kwargs)) return Qnil;
23
+ return rb_hash_aref(kwargs, ID2SYM(rb_intern(name)));
24
+ }
25
+
26
+ static int
27
+ rb_minibwa_kw_bool(VALUE kwargs, const char *name)
28
+ {
29
+ return RTEST(rb_minibwa_kw(kwargs, name));
30
+ }
31
+
32
+ static mb_opt_t *
33
+ rb_minibwa_kw_opt(VALUE kwargs, mb_opt_t *default_opt)
34
+ {
35
+ VALUE opt_val = rb_minibwa_kw(kwargs, "opt");
36
+ if (!NIL_P(opt_val)) return rb_minibwa_get_opt(opt_val);
37
+
38
+ mb_opt_init(default_opt);
39
+ return default_opt;
40
+ }
41
+
42
+ static mb_tbuf_t *
43
+ rb_minibwa_kw_tbuf(VALUE kwargs)
44
+ {
45
+ return rb_minibwa_get_tbuf(rb_minibwa_kw(kwargs, "buf"));
46
+ }
47
+
48
+ static void
49
+ rb_minibwa_idx_free(void *ptr)
50
+ {
51
+ mb_idx_t *idx = (mb_idx_t *)ptr;
52
+ if (idx) mb_idx_destroy(idx);
53
+ }
54
+
55
+ static size_t
56
+ rb_minibwa_idx_memsize(const void *ptr)
57
+ {
58
+ /* mb_idx_t is an opaque type; report the pointer size only. */
59
+ (void)ptr;
60
+ return 0;
61
+ }
62
+
63
+ const rb_data_type_t rb_minibwa_idx_type = {
64
+ .wrap_struct_name = "Minibwa::Index",
65
+ .function = {
66
+ NULL,
67
+ rb_minibwa_idx_free,
68
+ rb_minibwa_idx_memsize,
69
+ },
70
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
71
+ };
72
+
73
+ /* ------------------------------------------------------------------ */
74
+ /* Construction: .load, .load_mmap */
75
+ /* ------------------------------------------------------------------ */
76
+
77
+ struct idx_load_args {
78
+ const char *prefix;
79
+ int32_t is_meth;
80
+ int preload; /* only for mmap variant */
81
+ int mmap; /* 0 = load, 1 = load_mmap */
82
+ mb_idx_t *result;
83
+ };
84
+
85
+ static void *
86
+ rb_minibwa_idx_load_body(void *ptr)
87
+ {
88
+ struct idx_load_args *args = (struct idx_load_args *)ptr;
89
+ if (args->mmap)
90
+ args->result = mb_idx_load_mmap(args->prefix, args->is_meth, args->preload);
91
+ else
92
+ args->result = mb_idx_load(args->prefix, args->is_meth);
93
+ return NULL;
94
+ }
95
+
96
+ /*
97
+ * call-seq:
98
+ * Index.load(prefix, meth: false) -> Index
99
+ *
100
+ * Loads an index from disk. +prefix+ is the path prefix of the index files
101
+ * (e.g. <tt>"ref"</tt> loads <tt>ref.l2b</tt> and <tt>ref.mbw</tt>).
102
+ * Set +meth:+ to +true+ for methylation mode.
103
+ *
104
+ * Raises Minibwa::Error if the index cannot be loaded.
105
+ */
106
+ static VALUE
107
+ rb_minibwa_idx_load(int argc, VALUE *argv, VALUE klass)
108
+ {
109
+ VALUE prefix_val, kwargs;
110
+ rb_scan_args(argc, argv, "1:", &prefix_val, &kwargs);
111
+
112
+ const char *prefix = StringValueCStr(prefix_val);
113
+
114
+ struct idx_load_args args = { prefix, rb_minibwa_kw_bool(kwargs, "meth"), 0, 0, NULL };
115
+ rb_thread_call_without_gvl(rb_minibwa_idx_load_body, &args, NULL, NULL);
116
+
117
+ if (!args.result)
118
+ rb_raise(rb_eMinibwaError, "failed to load index from \"%s\"", prefix);
119
+
120
+ VALUE obj = TypedData_Wrap_Struct(klass, &rb_minibwa_idx_type, args.result);
121
+ return obj;
122
+ }
123
+
124
+ /*
125
+ * call-seq:
126
+ * Index.load_mmap(prefix, meth: false, preload: false) -> Index
127
+ *
128
+ * Loads an index using memory-mapped I/O. Set +preload:+ to +true+ to
129
+ * preload the index into memory.
130
+ */
131
+ static VALUE
132
+ rb_minibwa_idx_load_mmap(int argc, VALUE *argv, VALUE klass)
133
+ {
134
+ VALUE prefix_val, kwargs;
135
+ rb_scan_args(argc, argv, "1:", &prefix_val, &kwargs);
136
+
137
+ const char *prefix = StringValueCStr(prefix_val);
138
+
139
+ struct idx_load_args args = {
140
+ prefix, rb_minibwa_kw_bool(kwargs, "meth"),
141
+ rb_minibwa_kw_bool(kwargs, "preload"), 1, NULL
142
+ };
143
+ rb_thread_call_without_gvl(rb_minibwa_idx_load_body, &args, NULL, NULL);
144
+
145
+ if (!args.result)
146
+ rb_raise(rb_eMinibwaError, "failed to load index from \"%s\"", prefix);
147
+
148
+ VALUE obj = TypedData_Wrap_Struct(klass, &rb_minibwa_idx_type, args.result);
149
+ return obj;
150
+ }
151
+
152
+ /* ------------------------------------------------------------------ */
153
+ /* Contig queries */
154
+ /* ------------------------------------------------------------------ */
155
+
156
+ /*
157
+ * call-seq:
158
+ * ctg_name(tid) -> String or nil
159
+ *
160
+ * Returns the contig name for the given target ID, or +nil+ if +tid+ is out
161
+ * of range.
162
+ */
163
+ static VALUE
164
+ rb_minibwa_idx_ctg_name(VALUE self, VALUE tid_val)
165
+ {
166
+ mb_idx_t *idx = rb_minibwa_get_idx(self);
167
+ int32_t tid = NUM2INT(tid_val);
168
+ const char *name = mb_idx_ctg_name(idx, tid);
169
+ if (!name) return Qnil;
170
+ return rb_str_new_cstr(name);
171
+ }
172
+
173
+ /*
174
+ * call-seq:
175
+ * ctg_len(tid) -> Integer or nil
176
+ *
177
+ * Returns the contig length for the given target ID, or +nil+ if +tid+ is
178
+ * out of range.
179
+ */
180
+ static VALUE
181
+ rb_minibwa_idx_ctg_len(VALUE self, VALUE tid_val)
182
+ {
183
+ mb_idx_t *idx = rb_minibwa_get_idx(self);
184
+ int32_t tid = NUM2INT(tid_val);
185
+ int64_t len = mb_idx_ctg_len(idx, tid);
186
+ if (len < 0) return Qnil;
187
+ return LL2NUM(len);
188
+ }
189
+
190
+ /* ------------------------------------------------------------------ */
191
+ /* Mapping: single sequence */
192
+ /* ------------------------------------------------------------------ */
193
+
194
+ struct map_args {
195
+ const mb_opt_t *opt;
196
+ const mb_idx_t *idx;
197
+ int32_t qlen;
198
+ const char *seq;
199
+ int32_t mt;
200
+ mb_tbuf_t *b;
201
+ const char *qname;
202
+ mb_hit_t *result;
203
+ int32_t n_hit;
204
+ };
205
+
206
+ static void *
207
+ rb_minibwa_map_body(void *ptr)
208
+ {
209
+ struct map_args *args = (struct map_args *)ptr;
210
+ args->result = mb_map(args->opt, args->idx, args->qlen, args->seq,
211
+ args->mt, &args->n_hit, args->b, args->qname);
212
+ return NULL;
213
+ }
214
+
215
+ /*
216
+ * call-seq:
217
+ * map(seq, name: nil, opt: nil, buf: nil, meth: 0) -> Array of Hit
218
+ *
219
+ * Aligns one query sequence against the index.
220
+ *
221
+ * +seq+:: query sequence as a String (ASCII bases).
222
+ * +name+:: optional query name.
223
+ * +opt+:: an Options object; uses defaults if omitted.
224
+ * +buf+:: a Buffer for scratch space; allocates internally if +nil+.
225
+ * +meth+:: methylation type: 0 (none), 1 (read1 C-to-T), 2 (read2 G-to-A).
226
+ *
227
+ * Returns an Array of Minibwa::Hit objects.
228
+ */
229
+ static VALUE
230
+ rb_minibwa_idx_map(int argc, VALUE *argv, VALUE self)
231
+ {
232
+ VALUE seq_val, kwargs;
233
+ rb_scan_args(argc, argv, "1:", &seq_val, &kwargs);
234
+
235
+ mb_idx_t *idx = rb_minibwa_get_idx(self);
236
+
237
+ /* --- options --- */
238
+ mb_opt_t default_opt;
239
+ mb_opt_t *opt = rb_minibwa_kw_opt(kwargs, &default_opt);
240
+ mb_tbuf_t *b = rb_minibwa_kw_tbuf(kwargs);
241
+
242
+ /* --- methylation --- */
243
+ int32_t mt = 0;
244
+ VALUE mt_val = rb_minibwa_kw(kwargs, "meth");
245
+ if (!NIL_P(mt_val)) mt = NUM2INT(mt_val);
246
+
247
+ /* --- query name --- */
248
+ const char *qname = NULL;
249
+ VALUE name_val = rb_minibwa_kw(kwargs, "name");
250
+ if (!NIL_P(name_val)) qname = StringValueCStr(name_val);
251
+
252
+ /* --- sequence --- */
253
+ const char *seq = StringValueCStr(seq_val);
254
+ int32_t qlen = (int32_t)RSTRING_LEN(seq_val);
255
+
256
+ struct map_args args = { opt, idx, qlen, seq, mt, b, qname, NULL, 0 };
257
+ rb_thread_call_without_gvl(rb_minibwa_map_body, &args, NULL, NULL);
258
+
259
+ return rb_minibwa_hit_ary_new(idx, args.result, args.n_hit);
260
+ }
261
+
262
+ /* ------------------------------------------------------------------ */
263
+ /* Mapping: batch */
264
+ /* ------------------------------------------------------------------ */
265
+
266
+ struct batch_args {
267
+ const mb_opt_t *opt;
268
+ const mb_idx_t *idx;
269
+ int32_t n_seq;
270
+ const int32_t *qlen;
271
+ const char **seq;
272
+ mb_tbuf_t *b;
273
+ const char **qname;
274
+ mb_hit_t **result;
275
+ int32_t *n_hit;
276
+ };
277
+
278
+ static void *
279
+ rb_minibwa_map_batch_body(void *ptr)
280
+ {
281
+ struct batch_args *args = (struct batch_args *)ptr;
282
+ args->result = mb_map_batch(args->opt, args->idx, args->n_seq,
283
+ args->qlen, args->seq, args->n_hit,
284
+ args->b, args->qname);
285
+ return NULL;
286
+ }
287
+
288
+ /*
289
+ * call-seq:
290
+ * map_batch(seqs, names: nil, opt: nil, buf: nil) -> Array of Array of Hit
291
+ *
292
+ * Aligns multiple query sequences in one batch.
293
+ *
294
+ * +seqs+:: an Array of query sequence Strings.
295
+ * +names+:: optional Array of query names (same length as +seqs+).
296
+ * +opt+:: an Options object; uses defaults if omitted.
297
+ * +buf+:: a Buffer for scratch space; allocates internally if +nil+.
298
+ *
299
+ * Returns an Array of Arrays of Minibwa::Hit, one per input sequence.
300
+ */
301
+
302
+ struct map_batch_ctx {
303
+ VALUE seqs_val;
304
+ VALUE names_val;
305
+ int32_t n_seq;
306
+ const mb_opt_t *opt;
307
+ mb_idx_t *idx;
308
+ mb_tbuf_t *b;
309
+ int32_t *qlen;
310
+ char **seq;
311
+ char **qname;
312
+ int32_t *n_hit;
313
+ mb_hit_t **result; /* set by mb_map_batch; NULL if not yet called */
314
+ VALUE pins;
315
+ };
316
+
317
+ static VALUE
318
+ rb_minibwa_map_batch_work(VALUE ptr)
319
+ {
320
+ struct map_batch_ctx *ctx = (struct map_batch_ctx *)ptr;
321
+
322
+ /* --- build C arrays --- */
323
+ ctx->qlen = ALLOC_N(int32_t, ctx->n_seq);
324
+ ctx->seq = ALLOC_N(char *, ctx->n_seq);
325
+ if (!NIL_P(ctx->names_val))
326
+ ctx->qname = ALLOC_N(char *, ctx->n_seq);
327
+ ctx->n_hit = ALLOC_N(int32_t, ctx->n_seq);
328
+
329
+ /* Keep converted Strings alive across the GVL-free call. A Ruby
330
+ * array on the C stack is pinned by conservative scanning. */
331
+ ctx->pins = rb_ary_new_capa(ctx->n_seq);
332
+
333
+ for (int32_t i = 0; i < ctx->n_seq; i++) {
334
+ VALUE s = rb_ary_entry(ctx->seqs_val, i);
335
+ StringValue(s);
336
+ rb_ary_push(ctx->pins, s);
337
+ ctx->qlen[i] = (int32_t)RSTRING_LEN(s);
338
+ ctx->seq[i] = (char *)StringValueCStr(s); /* NUL-terminated, safe */
339
+ if (ctx->qname) {
340
+ VALUE nm = rb_ary_entry(ctx->names_val, i);
341
+ if (!NIL_P(nm)) {
342
+ StringValue(nm);
343
+ rb_ary_push(ctx->pins, nm);
344
+ ctx->qname[i] = (char *)StringValueCStr(nm);
345
+ } else {
346
+ ctx->qname[i] = NULL;
347
+ }
348
+ }
349
+ }
350
+
351
+ struct batch_args args = { ctx->opt, ctx->idx, ctx->n_seq, ctx->qlen,
352
+ (const char **)ctx->seq, ctx->b,
353
+ (const char **)ctx->qname, NULL, ctx->n_hit };
354
+ rb_thread_call_without_gvl(rb_minibwa_map_batch_body, &args, NULL, NULL);
355
+
356
+ /* mb_map_batch() returns NULL when it cannot run at all (e.g. meth
357
+ * mode requested on a non-meth index). Detect this before touching
358
+ * args.result[] or n_hit[]. */
359
+ if (!args.result) {
360
+ rb_raise(rb_eMinibwaError,
361
+ "mb_map_batch failed (methylation mode mismatch?)");
362
+ }
363
+
364
+ ctx->result = args.result;
365
+
366
+ /* --- convert results --- */
367
+ VALUE ary = rb_ary_new_capa(ctx->n_seq);
368
+ for (int32_t i = 0; i < ctx->n_seq; i++) {
369
+ VALUE hits = rb_minibwa_hit_ary_new(ctx->idx, ctx->result[i], ctx->n_hit[i]);
370
+ rb_ary_push(ary, hits);
371
+ }
372
+
373
+ /* args.result comes from upstream's calloc → free(). */
374
+ free(ctx->result);
375
+ ctx->result = NULL;
376
+
377
+ return ary;
378
+ }
379
+
380
+ static VALUE
381
+ rb_minibwa_map_batch_cleanup(VALUE ptr)
382
+ {
383
+ struct map_batch_ctx *ctx = (struct map_batch_ctx *)ptr;
384
+ /* These are ruby_xmalloc → xfree(). Safe to call even on the
385
+ * success path (xfree(NULL) is a no-op). */
386
+ xfree(ctx->qlen);
387
+ xfree(ctx->seq);
388
+ xfree(ctx->qname);
389
+ xfree(ctx->n_hit);
390
+ /* If the work function raised after mb_map_batch() succeeded but
391
+ * before all hits were converted, free the result array. */
392
+ free(ctx->result);
393
+ return Qnil;
394
+ }
395
+
396
+ static VALUE
397
+ rb_minibwa_idx_map_batch(int argc, VALUE *argv, VALUE self)
398
+ {
399
+ VALUE seqs_val, kwargs;
400
+ rb_scan_args(argc, argv, "1:", &seqs_val, &kwargs);
401
+
402
+ mb_idx_t *idx = rb_minibwa_get_idx(self);
403
+ Check_Type(seqs_val, T_ARRAY);
404
+
405
+ int32_t n_seq = (int32_t)RARRAY_LEN(seqs_val);
406
+ if (n_seq == 0) return rb_ary_new();
407
+
408
+ /* --- options --- */
409
+ mb_opt_t default_opt;
410
+ mb_opt_t *opt = rb_minibwa_kw_opt(kwargs, &default_opt);
411
+ mb_tbuf_t *b = rb_minibwa_kw_tbuf(kwargs);
412
+
413
+ /* --- names --- */
414
+ VALUE names_val = rb_minibwa_kw(kwargs, "names");
415
+ if (!NIL_P(names_val)) {
416
+ Check_Type(names_val, T_ARRAY);
417
+ if (RARRAY_LEN(names_val) != n_seq)
418
+ rb_raise(rb_eArgError, "names length (%ld) must match seqs length (%d)",
419
+ RARRAY_LEN(names_val), n_seq);
420
+ }
421
+
422
+ struct map_batch_ctx ctx = {
423
+ .seqs_val = seqs_val,
424
+ .names_val = names_val,
425
+ .n_seq = n_seq,
426
+ .opt = opt,
427
+ .idx = idx,
428
+ .b = b,
429
+ .qlen = NULL,
430
+ .seq = NULL,
431
+ .qname = NULL,
432
+ .n_hit = NULL,
433
+ .result = NULL,
434
+ .pins = Qnil
435
+ };
436
+
437
+ return rb_ensure(rb_minibwa_map_batch_work, (VALUE)&ctx,
438
+ rb_minibwa_map_batch_cleanup, (VALUE)&ctx);
439
+ }
440
+
441
+ /* ------------------------------------------------------------------ */
442
+ /* Registration */
443
+ /* ------------------------------------------------------------------ */
444
+
445
+ void
446
+ rb_minibwa_init_index(void)
447
+ {
448
+ VALUE klass = rb_define_class_under(rb_mMinibwa, "Index", rb_cObject);
449
+ rb_cMinibwaIndex = klass;
450
+
451
+ /* Index is only ever created via .load / .load_mmap (which use
452
+ * TypedData_Wrap_Struct directly), so Index.new is not supported. */
453
+ rb_undef_alloc_func(klass);
454
+
455
+ rb_define_singleton_method(klass, "load", rb_minibwa_idx_load, -1);
456
+ rb_define_singleton_method(klass, "load_mmap", rb_minibwa_idx_load_mmap, -1);
457
+
458
+ rb_define_method(klass, "ctg_name", rb_minibwa_idx_ctg_name, 1);
459
+ rb_define_method(klass, "ctg_len", rb_minibwa_idx_ctg_len, 1);
460
+ rb_define_method(klass, "map", rb_minibwa_idx_map, -1);
461
+ rb_define_method(klass, "map_batch", rb_minibwa_idx_map_batch, -1);
462
+ }
@@ -0,0 +1,174 @@
1
+ /*
2
+ * Minibwa::Index.build -- FM-index construction.
3
+ *
4
+ * This is the one part of minibwa with no public API: upstream exposes index
5
+ * construction only through main_index(argc, argv) in index.c, and its worker
6
+ * mb_bwt_libsais() is static. Rather than synthesise an argv and inherit the
7
+ * CLI's usage messages and exit paths, this file #includes index.c to reach
8
+ * that static function and reimplements only the libsais branch
9
+ * (l2b_import -> l2b_save -> mb_bwt_libsais -> mb_bwt_save). extconf.rb must
10
+ * therefore keep index.c out of the source list, or every symbol in it would
11
+ * be defined twice.
12
+ *
13
+ * The low-memory branch is intentionally not offered: it needs bwtgen.c and
14
+ * QSufSort.c, which are GPL'd and are not shipped with this MIT gem.
15
+ *
16
+ * Construction runs with the GVL released. Note that upstream signals bad
17
+ * input with kom_assert(), which abort()s the process, so inputs are
18
+ * validated on the Ruby side before reaching here.
19
+ */
20
+
21
+ #include "minibwa.h"
22
+ #include <stdlib.h>
23
+ #include <string.h>
24
+
25
+ /*
26
+ * Include index.c to reach the static mb_bwt_libsais(). We must suppress
27
+ * main() and the other entry points. index.c has no include guard, so we
28
+ * rely on extconf.rb keeping it out of $srcs.
29
+ *
30
+ * The static functions we need:
31
+ * l2b_c2t, l2b_g2a, sa_to_bwt, mb_bwt_libsais
32
+ * The public functions we call:
33
+ * l2b_import, l2b_save, mb_bwt_save, mb_bwt_init_from_raw
34
+ */
35
+ #include "minibwa/index.c"
36
+
37
+ static char *
38
+ rb_minibwa_prefixed_path(const char *prefix, const char *suffix)
39
+ {
40
+ size_t prefix_len = strlen(prefix);
41
+ size_t suffix_len = strlen(suffix);
42
+ char *path = malloc(prefix_len + suffix_len + 1);
43
+ if (!path) return NULL;
44
+
45
+ memcpy(path, prefix, prefix_len);
46
+ memcpy(path + prefix_len, suffix, suffix_len + 1);
47
+ return path;
48
+ }
49
+
50
+ /* ------------------------------------------------------------------ */
51
+ /* Build body (runs without GVL) */
52
+ /* ------------------------------------------------------------------ */
53
+
54
+ struct build_args {
55
+ const char *fn_fa;
56
+ const char *path_l2b;
57
+ const char *path_bwt;
58
+ int sa_bit;
59
+ int n_thread;
60
+ uint64_t seed;
61
+ int is_meth;
62
+ int error;
63
+ };
64
+
65
+ static void *
66
+ rb_minibwa_index_build_body(void *ptr)
67
+ {
68
+ struct build_args *args = (struct build_args *)ptr;
69
+
70
+ args->error = 0;
71
+
72
+ /* Step 1: FASTA → l2b (2-bit encoding) */
73
+ l2b_t *l2b = l2b_import(args->fn_fa, args->seed);
74
+ if (!l2b) { args->error = 1; return NULL; }
75
+
76
+ l2b_save(args->path_l2b, l2b);
77
+
78
+ /* Step 2: l2b → BWT via libsais */
79
+ mb_bwt_t *bwt = mb_bwt_libsais(l2b, args->sa_bit, 1, args->is_meth, args->n_thread);
80
+ l2b_destroy(l2b);
81
+
82
+ if (!bwt) { args->error = 2; return NULL; }
83
+
84
+ /* Upstream names the BWT file "<prefix>.mbw" (see main_index in
85
+ * index.c); mb_idx_load() looks for exactly that. */
86
+ mb_bwt_save(args->path_bwt, bwt);
87
+ mb_bwt_destroy(bwt);
88
+
89
+ return NULL;
90
+ }
91
+
92
+ /* ------------------------------------------------------------------ */
93
+ /* Ruby method */
94
+ /* ------------------------------------------------------------------ */
95
+
96
+ /*
97
+ * Document-method: Minibwa::Index.build
98
+ *
99
+ * call-seq:
100
+ * Index.build(fasta, prefix, sa_bit: 4, n_thread: 4, seed: 11, meth: false) -> true
101
+ *
102
+ * Builds a minibwa index from a FASTA file.
103
+ *
104
+ * +fasta+:: path to the input FASTA file.
105
+ * +prefix+:: output path prefix (writes +prefix+.l2b and +prefix+.mbw).
106
+ * +sa_bit+:: suffix array sample rate (1/(1<<sa_bit)).
107
+ * +n_thread+:: number of threads for libsais (requires OpenMP).
108
+ * +seed+:: random seed for hash table in l2b_import.
109
+ * +meth+:: build methylation index (forward C→T and G→A strands).
110
+ *
111
+ * Returns +true+ on success. Raises Minibwa::Error on failure.
112
+ *
113
+ * NOTE: upstream signals bad input with kom_assert(), which abort()s the
114
+ * process. Validate inputs on the Ruby side before calling this method.
115
+ */
116
+ static VALUE
117
+ rb_minibwa_index_build(int argc, VALUE *argv, VALUE klass)
118
+ {
119
+ VALUE fasta_val, prefix_val, kwargs;
120
+ rb_scan_args(argc, argv, "2:", &fasta_val, &prefix_val, &kwargs);
121
+
122
+ const char *fn_fa = StringValueCStr(fasta_val);
123
+ const char *prefix = StringValueCStr(prefix_val);
124
+
125
+ int sa_bit = 4;
126
+ int n_thread = 4;
127
+ uint64_t seed = 11;
128
+ int is_meth = 0;
129
+
130
+ if (!NIL_P(kwargs)) {
131
+ VALUE v;
132
+ v = rb_hash_aref(kwargs, ID2SYM(rb_intern("sa_bit")));
133
+ if (!NIL_P(v)) sa_bit = NUM2INT(v);
134
+ v = rb_hash_aref(kwargs, ID2SYM(rb_intern("n_thread")));
135
+ if (!NIL_P(v)) n_thread = NUM2INT(v);
136
+ v = rb_hash_aref(kwargs, ID2SYM(rb_intern("seed")));
137
+ if (!NIL_P(v)) seed = NUM2ULL(v);
138
+ v = rb_hash_aref(kwargs, ID2SYM(rb_intern("meth")));
139
+ if (RTEST(v)) is_meth = 1;
140
+ }
141
+
142
+ char *path_l2b = rb_minibwa_prefixed_path(prefix, ".l2b");
143
+ char *path_bwt = rb_minibwa_prefixed_path(prefix, ".mbw");
144
+ if (!path_l2b || !path_bwt) {
145
+ free(path_l2b);
146
+ free(path_bwt);
147
+ rb_memerror();
148
+ }
149
+
150
+ struct build_args args = {
151
+ fn_fa, path_l2b, path_bwt, sa_bit, n_thread, seed, is_meth, 0
152
+ };
153
+ rb_thread_call_without_gvl(rb_minibwa_index_build_body, &args, NULL, NULL);
154
+
155
+ free(path_l2b);
156
+ free(path_bwt);
157
+
158
+ if (args.error)
159
+ rb_raise(rb_eMinibwaError, "index build failed for \"%s\" (error %d)", fn_fa, args.error);
160
+
161
+ return Qtrue;
162
+ }
163
+
164
+ /* ------------------------------------------------------------------ */
165
+ /* Registration */
166
+ /* ------------------------------------------------------------------ */
167
+
168
+ void
169
+ rb_minibwa_init_index_build(void)
170
+ {
171
+ /* Index.build is a singleton method on Minibwa::Index */
172
+ rb_define_singleton_method(rb_cMinibwaIndex, "build",
173
+ rb_minibwa_index_build, -1);
174
+ }