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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +119 -0
- data/ext/minibwa/extconf.rb +105 -0
- data/ext/minibwa/mb_buffer.c +113 -0
- data/ext/minibwa/mb_hit.c +192 -0
- data/ext/minibwa/mb_index.c +462 -0
- data/ext/minibwa/mb_index_build.c +174 -0
- data/ext/minibwa/mb_options.c +301 -0
- data/ext/minibwa/minibwa/LICENSE.txt +37 -0
- data/ext/minibwa/minibwa/align.c +930 -0
- data/ext/minibwa/minibwa/bseq.h +45 -0
- data/ext/minibwa/minibwa/bwt.c +715 -0
- data/ext/minibwa/minibwa/bwt.h +86 -0
- data/ext/minibwa/minibwa/cs.c +161 -0
- data/ext/minibwa/minibwa/format.c +356 -0
- data/ext/minibwa/minibwa/index.c +342 -0
- data/ext/minibwa/minibwa/kalloc.c +224 -0
- data/ext/minibwa/minibwa/kalloc.h +54 -0
- data/ext/minibwa/minibwa/ketopt.h +123 -0
- data/ext/minibwa/minibwa/kommon.c +374 -0
- data/ext/minibwa/minibwa/kommon.h +85 -0
- data/ext/minibwa/minibwa/kseq.h +256 -0
- data/ext/minibwa/minibwa/ksort.h +163 -0
- data/ext/minibwa/minibwa/ksw2.h +220 -0
- data/ext/minibwa/minibwa/ksw2_extd2_sse.c +403 -0
- data/ext/minibwa/minibwa/ksw2_extz2_sse.c +296 -0
- data/ext/minibwa/minibwa/ksw2_ll_sse.c +341 -0
- data/ext/minibwa/minibwa/kthread.h +15 -0
- data/ext/minibwa/minibwa/l2bit.c +479 -0
- data/ext/minibwa/minibwa/l2bit.h +72 -0
- data/ext/minibwa/minibwa/lchain.c +231 -0
- data/ext/minibwa/minibwa/libsais.c +6985 -0
- data/ext/minibwa/minibwa/libsais.h +106 -0
- data/ext/minibwa/minibwa/libsais64.c +7064 -0
- data/ext/minibwa/minibwa/libsais64.h +81 -0
- data/ext/minibwa/minibwa/map-algo.c +769 -0
- data/ext/minibwa/minibwa/mbpriv.h +148 -0
- data/ext/minibwa/minibwa/minibwa.h +176 -0
- data/ext/minibwa/minibwa/options.c +116 -0
- data/ext/minibwa/minibwa/pe.c +559 -0
- data/ext/minibwa/minibwa/s2n-lite.h +59 -0
- data/ext/minibwa/minibwa/seed.c +354 -0
- data/ext/minibwa/minibwa.c +67 -0
- data/ext/minibwa/minibwa.h +51 -0
- data/lib/minibwa/hit.rb +110 -0
- data/lib/minibwa/index.rb +77 -0
- data/lib/minibwa/options.rb +235 -0
- data/lib/minibwa/sam.rb +85 -0
- data/lib/minibwa/version.rb +6 -0
- data/lib/minibwa.rb +11 -0
- 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
|
+
}
|