1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use crate::error::{Error, Result};
use minibwa_sys as sys;
use std::ffi::CString;
/// Alignment options, wrapping minibwa's `mb_opt_t`.
#[derive(Clone, Debug)]
pub struct Opts(sys::mb_opt_t);
impl Opts {
/// Default options (minibwa's `mb_opt_init`, "adap" preset).
pub fn new() -> Self {
// SAFETY: mb_opt_init fully initializes a zeroed mb_opt_t.
let mut raw: sys::mb_opt_t = unsafe { std::mem::zeroed() };
unsafe { sys::mb_opt_init(&mut raw) };
Opts(raw)
}
/// Options initialized from a preset: `"sr"`, `"adap"`, or `"lr"`.
///
/// # Errors
///
/// Returns [`Error::InvalidOpts`] if the preset name is unknown or contains
/// a NUL byte.
pub fn with_preset(preset: &str) -> Result<Self> {
let mut opts = Opts::new();
let c = CString::new(preset).map_err(|_| Error::InvalidOpts("preset has NUL".into()))?;
// SAFETY: valid opt ptr + NUL-terminated preset string.
let rc = unsafe { sys::mb_opt_preset(&mut opts.0, c.as_ptr()) };
if rc != 0 {
return Err(Error::InvalidOpts(format!("unknown preset {preset:?}")));
}
Ok(opts)
}
fn set_flag(&mut self, flag: u64, on: bool) {
if on {
self.0.flag |= flag;
} else {
self.0.flag &= !flag;
}
}
/// Enable/disable paired-end mode (`MB_F_PE`).
pub fn set_paired(mut self, on: bool) -> Self {
self.set_flag(sys::MB_F_PE as u64, on);
self
}
/// Enable/disable methylation mode (`MB_F_METH`).
pub fn set_methylation(mut self, on: bool) -> Self {
self.set_flag(sys::MB_F_METH as u64, on);
self
}
/// Minimum seed length.
pub fn set_min_seed_len(mut self, v: i32) -> Self {
self.0.min_len = v;
self
}
/// Max number of secondary alignments to output.
pub fn set_out_n(mut self, v: i32) -> Self {
self.0.out_n = v;
self
}
/// Max number of secondary hits reported in the `XA` tag.
///
/// A hit is eligible for `XA` only if there are at most this many
/// suboptimal hits scoring above [`set_xa_ratio`](Self::set_xa_ratio) of
/// the best hit's score. minibwa's default is `5`.
///
/// Setting `v <= 0` disables `XA`-tag output entirely (equivalent to bwa's
/// `-h 0`); the value is otherwise passed through to minibwa unchecked.
pub fn set_xa_max(mut self, v: i32) -> Self {
self.0.xa_max = v;
self
}
/// Score-ratio threshold for `XA`-tag inclusion.
///
/// A suboptimal hit is emitted to the `XA` tag only if its score is at
/// least `ratio * best_score`. minibwa's default is `0.8`. `ratio` is a
/// fraction of the best hit's score, so only values in `[0.0, 1.0]` are
/// meaningful: `0.0` includes every suboptimal hit and `1.0` only those
/// tied with the best. Non-finite values (`NaN`/`inf`) make the comparison
/// never hold and silently suppress all `XA` hits. The value is passed
/// through to minibwa unchecked.
pub fn set_xa_ratio(mut self, ratio: f32) -> Self {
self.0.xa_ratio = ratio;
self
}
/// Match score (Smith-Waterman `a` parameter).
pub fn set_match_score(mut self, score: i32) -> Self {
self.0.a = score;
self
}
/// Mismatch penalty (Smith-Waterman `b` parameter).
pub fn set_mismatch_penalty(mut self, penalty: i32) -> Self {
self.0.b = penalty;
self
}
/// Gap-open penalty (`q` parameter).
pub fn set_gap_open(mut self, open: i32) -> Self {
self.0.q = open;
self
}
/// Gap-extend penalty (`e` parameter).
pub fn set_gap_extend(mut self, extend: i32) -> Self {
self.0.e = extend;
self
}
/// Paired-end insert-size parameters.
///
/// * `avg` — expected insert-size mean
/// * `std` — expected insert-size standard deviation
/// * `lo` — lower insert-size bound for proper-pair classification
/// * `hi` — upper insert-size bound for proper-pair classification
pub fn set_pe_insert_size(mut self, avg: i32, std: i32, lo: i32, hi: i32) -> Self {
self.0.pe_avg = avg;
self.0.pe_std = std;
self.0.pe_lo = lo;
self.0.pe_hi = hi;
self
}
pub fn is_paired(&self) -> bool {
self.0.flag & sys::MB_F_PE as u64 != 0
}
pub fn is_methylation(&self) -> bool {
self.0.flag & sys::MB_F_METH as u64 != 0
}
/// A copy of the raw options with paired-end (`MB_F_PE`) forced on.
pub(crate) fn paired_copy(&self) -> minibwa_sys::mb_opt_t {
let mut o = self.0;
o.flag |= minibwa_sys::MB_F_PE as u64;
o
}
pub(crate) fn as_ptr(&self) -> *const sys::mb_opt_t {
&self.0
}
}
impl Default for Opts {
fn default() -> Self {
Opts::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preset_and_flags() {
let o = Opts::with_preset("sr").unwrap();
let o = o.set_paired(true).set_methylation(true);
assert!(o.is_paired());
assert!(o.is_methylation());
assert!(Opts::with_preset("nope").is_err());
}
#[test]
fn preset_nul_byte_returns_err() {
assert!(matches!(
Opts::with_preset("sr\0x"),
Err(crate::Error::InvalidOpts(_))
));
}
#[test]
fn all_presets_succeed() {
assert!(Opts::with_preset("adap").is_ok());
assert!(Opts::with_preset("lr").is_ok());
assert!(Opts::with_preset("sr").is_ok());
// "lr" is a long-read preset — paired-end is irrelevant and should be off by default.
assert!(!Opts::with_preset("lr").unwrap().is_paired());
}
#[test]
fn xa_defaults_and_setters() {
// minibwa's mb_opt_init defaults (options.c): xa_max = 5, xa_ratio = 0.8.
let o = Opts::new();
assert_eq!(o.0.xa_max, 5);
assert!((o.0.xa_ratio - 0.8).abs() < 1e-6);
let o = o.set_xa_max(12).set_xa_ratio(0.5);
assert_eq!(o.0.xa_max, 12);
assert!((o.0.xa_ratio - 0.5).abs() < 1e-6);
}
#[test]
fn flags_toggle_off() {
let o = Opts::new().set_paired(true).set_methylation(true);
assert!(o.is_paired());
assert!(o.is_methylation());
let o = o.set_paired(false).set_methylation(false);
assert!(!o.is_paired());
assert!(!o.is_methylation());
}
}