Skip to main content

parasail_rs/alignment/
mod.rs

1//! Alignment result handling.
2
3mod error;
4pub mod table;
5
6use libparasail_sys::{
7    parasail_cigar_decode, parasail_cigar_free, parasail_cigar_t, parasail_matrix_t,
8    parasail_result_free, parasail_result_get_cigar, parasail_result_get_end_query,
9    parasail_result_get_end_ref, parasail_result_get_length, parasail_result_get_length_col,
10    parasail_result_get_length_row, parasail_result_get_length_table, parasail_result_get_matches,
11    parasail_result_get_matches_col, parasail_result_get_matches_row,
12    parasail_result_get_matches_table, parasail_result_get_score, parasail_result_get_score_col,
13    parasail_result_get_score_row, parasail_result_get_score_table, parasail_result_get_similar,
14    parasail_result_get_similar_col, parasail_result_get_similar_row,
15    parasail_result_get_similar_table, parasail_result_get_trace_table,
16    parasail_result_get_traceback, parasail_result_is_banded, parasail_result_is_blocked,
17    parasail_result_is_diag, parasail_result_is_nw, parasail_result_is_rowcol,
18    parasail_result_is_saturated, parasail_result_is_scan, parasail_result_is_sg,
19    parasail_result_is_stats, parasail_result_is_stats_rowcol, parasail_result_is_stats_table,
20    parasail_result_is_striped, parasail_result_is_sw, parasail_result_is_table,
21    parasail_result_is_trace, parasail_result_ssw_free, parasail_result_ssw_t, parasail_result_t,
22    parasail_traceback_generic,
23};
24use std::ffi::CString;
25use std::slice;
26
27use crate::alignment::table::TracebackTable;
28use crate::prelude::Result;
29pub use error::Error;
30pub use table::Table;
31
32/// CIGAR string for sequence alignment.
33struct CigarString {
34    inner: *mut parasail_cigar_t,
35}
36
37#[doc(hidden)]
38impl Drop for CigarString {
39    fn drop(&mut self) {
40        unsafe {
41            parasail_cigar_free(self.inner);
42        }
43    }
44}
45
46/// Traceback for sequence alignment.
47pub struct Traceback {
48    pub query: String,
49    pub comparison: String,
50    pub reference: String,
51}
52
53/// Sequence alignment result.
54#[derive(Debug, Clone)]
55pub struct Alignment {
56    pub(crate) inner: *mut parasail_result_t,
57    pub(crate) matrix: *const parasail_matrix_t,
58    pub(crate) query_len: i32,
59    pub(crate) ref_len: i32,
60}
61
62impl Alignment {
63    /// Get alignment score.
64    pub fn get_score(&self) -> i32 {
65        unsafe { parasail_result_get_score(self.inner) }
66    }
67
68    /// Get end position of query sequence.
69    pub fn get_end_query(&self) -> i32 {
70        unsafe { parasail_result_get_end_query(self.inner) }
71    }
72
73    /// Get end position of the reference sequence.
74    pub fn get_end_ref(&self) -> i32 {
75        unsafe { parasail_result_get_end_ref(self.inner) }
76    }
77
78    /// Get number of matches in the alignment.
79    pub fn get_matches(&self) -> Result<i32> {
80        if self.is_stats() {
81            unsafe { Ok(parasail_result_get_matches(self.inner)) }
82        } else {
83            Err(Error::NoStats(String::from("get_matches()")))?
84        }
85    }
86
87    pub fn get_similar(&self) -> i32 {
88        unsafe { parasail_result_get_similar(self.inner) }
89    }
90
91    /// Get alignment length.
92    pub fn get_length(&self) -> Result<i32> {
93        if self.is_stats() {
94            unsafe { Ok(parasail_result_get_length(self.inner)) }
95        } else {
96            Err(Error::NoStats(String::from("get_length()")))?
97        }
98    }
99
100    /// Get the score table.
101    ///
102    /// The table has dimensions (query_len, ref_len) and contains alignment scores
103    /// for all positions in the dynamic programming table.
104    ///
105    /// # Example
106    /// ```rust,no_run
107    /// # use parasail_rs::prelude::Aligner;
108    /// # let query = b"ACGT";
109    /// # let reference = b"ACGT";
110    /// # let aligner = Aligner::new().use_table().build();
111    /// let result = aligner.align(Some(query), reference)?;
112    /// let table = result.get_score_table()?;
113    ///
114    /// // Access specific cell
115    /// if let Some(score) = table.get(0, 0) {
116    ///     println!("Score at (0, 0): {}", score);
117    /// }
118    ///
119    /// // Get final score
120    /// println!("Final score: {}", table.last());
121    /// # Ok::<(), Box<dyn std::error::Error>>(())
122    /// ```
123    pub fn get_score_table(&self) -> Result<Table<'_>> {
124        if self.is_table() || self.is_stats_table() {
125            unsafe {
126                let table_ptr = parasail_result_get_score_table(self.inner);
127                let table_size = (self.query_len * self.ref_len) as usize;
128                let data = slice::from_raw_parts(table_ptr, table_size);
129                Ok(Table::new(
130                    data,
131                    self.query_len as usize,
132                    self.ref_len as usize,
133                ))
134            }
135        } else {
136            Err(Error::NoTable(String::from("get_score_table()")))?
137        }
138    }
139
140    /// Get the matches table.
141    pub fn get_matches_table(&self) -> Result<Table<'_>> {
142        if self.is_stats_table() {
143            unsafe {
144                let table_ptr = parasail_result_get_matches_table(self.inner);
145                let table_size = (self.query_len * self.ref_len) as usize;
146                let data = slice::from_raw_parts(table_ptr, table_size);
147                Ok(Table::new(
148                    data,
149                    self.query_len as usize,
150                    self.ref_len as usize,
151                ))
152            }
153        } else {
154            Err(Error::NoStatsTable(String::from("get_matches_table()")))?
155        }
156    }
157
158    /// Get the similar table.
159    pub fn get_similar_table(&self) -> Result<Table<'_>> {
160        if self.is_stats_table() {
161            unsafe {
162                let table_ptr = parasail_result_get_similar_table(self.inner);
163                let table_size = (self.query_len * self.ref_len) as usize;
164                let data = slice::from_raw_parts(table_ptr, table_size);
165                Ok(Table::new(
166                    data,
167                    self.query_len as usize,
168                    self.ref_len as usize,
169                ))
170            }
171        } else {
172            Err(Error::NoStatsTable(String::from("get_similar_table()")))?
173        }
174    }
175
176    /// Get the length table.
177    pub fn get_length_table(&self) -> Result<Table<'_>> {
178        if self.is_stats_table() {
179            unsafe {
180                let table_ptr = parasail_result_get_length_table(self.inner);
181                let table_size = (self.query_len * self.ref_len) as usize;
182                let data = slice::from_raw_parts(table_ptr, table_size);
183                Ok(Table::new(
184                    data,
185                    self.query_len as usize,
186                    self.ref_len as usize,
187                ))
188            }
189        } else {
190            Err(Error::NoStatsTable(String::from("get_length_table()")))?
191        }
192    }
193
194    /// Get score row.
195    pub fn get_score_row(&self) -> Result<&[i32]> {
196        if self.is_rowcol() || self.is_stats_rowcol() {
197            unsafe {
198                let rptr = parasail_result_get_score_row(self.inner);
199                Ok(slice::from_raw_parts(rptr, self.ref_len as usize))
200            }
201        } else {
202            Err(Error::NoRowCol(String::from("get_score_row()")))?
203        }
204    }
205
206    /// Get matches row.
207    pub fn get_matches_row(&self) -> Result<&[i32]> {
208        if self.is_stats_rowcol() {
209            unsafe {
210                let rptr = parasail_result_get_matches_row(self.inner);
211                Ok(slice::from_raw_parts(rptr, self.ref_len as usize))
212            }
213        } else {
214            Err(Error::NoRowCol(String::from("get_matches_row()")))?
215        }
216    }
217
218    /// Get similar row.
219    pub fn get_similar_row(&self) -> Result<&[i32]> {
220        if self.is_stats_rowcol() {
221            unsafe {
222                let rptr = parasail_result_get_similar_row(self.inner);
223                Ok(slice::from_raw_parts(rptr, self.ref_len as usize))
224            }
225        } else {
226            Err(Error::NoRowCol(String::from("get_similar_row()")))?
227        }
228    }
229
230    /// Get length row.
231    pub fn get_length_row(&self) -> Result<&[i32]> {
232        if self.is_stats_rowcol() {
233            unsafe {
234                let rptr = parasail_result_get_length_row(self.inner);
235                Ok(slice::from_raw_parts(rptr, self.ref_len as usize))
236            }
237        } else {
238            Err(Error::NoRowCol(String::from("get_length_row()")))?
239        }
240    }
241
242    /// Get score column.
243    pub fn get_score_col(&self) -> Result<&[i32]> {
244        if self.is_rowcol() || self.is_stats_rowcol() {
245            unsafe {
246                let cptr = parasail_result_get_score_col(self.inner);
247                Ok(slice::from_raw_parts(cptr, self.query_len as usize))
248            }
249        } else {
250            Err(Error::NoRowCol(String::from("get_score_col()")))?
251        }
252    }
253
254    /// Get matches column.
255    pub fn get_matches_col(&self) -> Result<&[i32]> {
256        if self.is_stats_rowcol() {
257            unsafe {
258                let cptr = parasail_result_get_matches_col(self.inner);
259                Ok(slice::from_raw_parts(cptr, self.query_len as usize))
260            }
261        } else {
262            Err(Error::NoRowCol(String::from("get_matches_col()")))?
263        }
264    }
265
266    /// Get similar column.
267    pub fn get_similar_col(&self) -> Result<&[i32]> {
268        if self.is_stats_rowcol() {
269            unsafe {
270                let cptr = parasail_result_get_similar_col(self.inner);
271                Ok(slice::from_raw_parts(cptr, self.query_len as usize))
272            }
273        } else {
274            Err(Error::NoRowCol(String::from("get_similar_col()")))?
275        }
276    }
277
278    /// Get length column
279    pub fn get_length_col(&self) -> Result<&[i32]> {
280        if self.is_stats_rowcol() {
281            unsafe {
282                let cptr = parasail_result_get_length_col(self.inner);
283                Ok(slice::from_raw_parts(cptr, self.query_len as usize))
284            }
285        } else {
286            Err(Error::NoRowCol(String::from("get_length_col()")))?
287        }
288    }
289
290    /// Get the trace table.
291    pub fn get_trace_table(&self) -> Result<TracebackTable<'_>> {
292        if self.is_trace() {
293            unsafe {
294                let table_ptr = parasail_result_get_trace_table(self.inner) as *const i8;
295                let table_size = (self.query_len * self.ref_len) as usize;
296                let data = slice::from_raw_parts(table_ptr, table_size);
297
298                Ok(TracebackTable::new(
299                    data,
300                    self.query_len as usize,
301                    self.ref_len as usize,
302                ))
303            }
304        } else {
305            Err(Error::NoTrace(String::from("get_trace_table()")))?
306        }
307    }
308
309    /// Get alignment strings and statistics
310    pub fn print_traceback(&self, query: &[u8], reference: &[u8]) {
311        if self.is_trace() {
312            let query_len = query.len() as i32;
313            let ref_len = reference.len() as i32;
314            let query = CString::new(query).unwrap();
315            let reference = CString::new(reference).unwrap();
316            let query_str = CString::new("Query:").unwrap();
317            let ref_str = CString::new("Target:").unwrap();
318            let match_char = CString::new("|").unwrap();
319            let mismatch_char = CString::new(" ").unwrap();
320            let width = 80;
321            let name_width = 7;
322            let use_stats = 1;
323            unsafe {
324                parasail_traceback_generic(
325                    query.as_ptr(),
326                    query_len,
327                    reference.as_ptr(),
328                    ref_len,
329                    query_str.as_ptr(),
330                    ref_str.as_ptr(),
331                    self.matrix,
332                    self.inner,
333                    *match_char.as_ptr(),
334                    *mismatch_char.as_ptr(),
335                    *mismatch_char.as_ptr(),
336                    width,
337                    name_width,
338                    use_stats,
339                );
340            }
341        } else {
342            println!("Alignment string is not available without traceback enabled. Consider using the `use_trace` method on AlignerBuilder.");
343        }
344    }
345
346    /// Get alignment strings.
347    pub fn get_traceback_strings(&self, query: &[u8], reference: &[u8]) -> Result<Traceback> {
348        if self.is_trace() {
349            let query_len = query.len() as i32;
350            let ref_len = reference.len() as i32;
351            let query = CString::new(query).map_err(Error::InteriorNulByte)?;
352            let reference = CString::new(reference).map_err(Error::InteriorNulByte)?;
353            let match_char = CString::new("|").map_err(Error::InteriorNulByte)?;
354            let mismatch_char = CString::new(" ").map_err(Error::InteriorNulByte)?;
355            unsafe {
356                let alignment = parasail_result_get_traceback(
357                    self.inner,
358                    query.as_ptr(),
359                    query_len,
360                    reference.as_ptr(),
361                    ref_len,
362                    self.matrix,
363                    *match_char.as_ptr(),
364                    *mismatch_char.as_ptr(),
365                    *mismatch_char.as_ptr(),
366                );
367
368                let query_traceback = CString::from_raw((*alignment).query)
369                    .into_string()
370                    .map_err(Error::InvalidUTF8String)?;
371                let comparison_traceback = CString::from_raw((*alignment).comp)
372                    .into_string()
373                    .map_err(Error::InvalidUTF8String)?;
374                let reference_traceback = CString::from_raw((*alignment).ref_)
375                    .into_string()
376                    .map_err(Error::InvalidUTF8String)?;
377
378                Ok(Traceback {
379                    query: query_traceback.clone(),
380                    comparison: comparison_traceback.clone(),
381                    reference: reference_traceback.clone(),
382                })
383            }
384        } else {
385            Err(Error::NoTrace(String::from("get_traceback_strings()")))?
386        }
387    }
388
389    /// Get CIGAR string.
390    pub fn get_cigar(&self, query: &[u8], reference: &[u8]) -> Result<String> {
391        if self.is_trace() {
392            let query_len = query.len() as i32;
393            let query = CString::new(query).map_err(Error::InteriorNulByte)?;
394            let ref_len = reference.len() as i32;
395            let reference = CString::new(reference).map_err(Error::InteriorNulByte)?;
396
397            let cigar: String;
398            unsafe {
399                let cigar_encoded = CigarString {
400                    inner: parasail_result_get_cigar(
401                        self.inner,
402                        query.as_ptr(),
403                        query_len,
404                        reference.as_ptr(),
405                        ref_len,
406                        self.matrix,
407                    ),
408                };
409
410                cigar = CString::from_raw(parasail_cigar_decode(cigar_encoded.inner))
411                    .into_string()
412                    .map_err(Error::InvalidUTF8String)?;
413            }
414
415            Ok(cigar)
416        } else {
417            Err(Error::NoTrace(String::from("get_cigar()")).into())
418        }
419    }
420
421    /// Check if the alignment mode is global.
422    pub fn is_global(&self) -> bool {
423        unsafe { parasail_result_is_nw(self.inner) != 0 }
424    }
425
426    /// Check if the alignment mode is semi-global.
427    pub fn is_semi_global(&self) -> bool {
428        unsafe { parasail_result_is_sg(self.inner) != 0 }
429    }
430
431    /// Check if the alignment mode is local.
432    pub fn is_local(&self) -> bool {
433        unsafe { parasail_result_is_sw(self.inner) != 0 }
434    }
435
436    /// Check if the solution width is saturated (i.e., using 8-bit solution width first and
437    /// falling back to 16-bit if necessary).
438    pub fn is_saturated(&self) -> bool {
439        unsafe { parasail_result_is_saturated(self.inner) != 0 }
440    }
441
442    /// Check if banded alignment is used.
443    pub fn is_banded(&self) -> bool {
444        unsafe { parasail_result_is_banded(self.inner) != 0 }
445    }
446
447    /// Check if vector strategy is scan.
448    pub fn is_scan(&self) -> bool {
449        unsafe { parasail_result_is_scan(self.inner) != 0 }
450    }
451
452    /// Check if vector strategy is striped.
453    pub fn is_striped(&self) -> bool {
454        unsafe { parasail_result_is_striped(self.inner) != 0 }
455    }
456
457    /// Check if vector strategy is diagonal.
458    pub fn is_diag(&self) -> bool {
459        unsafe { parasail_result_is_diag(self.inner) != 0 }
460    }
461
462    pub fn is_blocked(&self) -> bool {
463        unsafe { parasail_result_is_blocked(self.inner) != 0 }
464    }
465
466    /// Check if statistics are returned from alignment.
467    pub fn is_stats(&self) -> bool {
468        unsafe { parasail_result_is_stats(self.inner) != 0 }
469    }
470
471    /// Check if result is a stats table
472    pub fn is_stats_table(&self) -> bool {
473        unsafe { parasail_result_is_stats_table(self.inner) != 0 }
474    }
475
476    /// Check if result is a table
477    pub fn is_table(&self) -> bool {
478        unsafe { parasail_result_is_table(self.inner) != 0 }
479    }
480
481    /// Check if result is a last row and column of table
482    pub fn is_rowcol(&self) -> bool {
483        unsafe { parasail_result_is_rowcol(self.inner) != 0 }
484    }
485
486    /// Check if result is a row and column of table with additional statistics.
487    pub fn is_stats_rowcol(&self) -> bool {
488        unsafe { parasail_result_is_stats_rowcol(self.inner) != 0 }
489    }
490
491    /// Check if result is trace enabled.
492    pub fn is_trace(&self) -> bool {
493        unsafe { parasail_result_is_trace(self.inner) != 0 }
494    }
495}
496
497#[doc(hidden)]
498impl Drop for Alignment {
499    fn drop(&mut self) {
500        unsafe {
501            parasail_result_free(self.inner);
502        }
503    }
504}
505
506/// SSW alignment result.
507pub struct SSWResult {
508    pub(crate) inner: *mut parasail_result_ssw_t,
509}
510
511impl SSWResult {
512    /// Get primary alignment score.
513    pub fn score(&self) -> u16 {
514        unsafe { (*self.inner).score1 }
515    }
516
517    /// Get beginning location of alignment on the reference sequence.
518    pub fn ref_start(&self) -> i32 {
519        unsafe { (*self.inner).ref_begin1 }
520    }
521
522    /// Get ending location of alignment on the reference sequence.
523    pub fn ref_end(&self) -> i32 {
524        unsafe { (*self.inner).ref_end1 }
525    }
526
527    /// Get beginning location of alignment on the query sequence.
528    pub fn query_start(&self) -> i32 {
529        unsafe { (*self.inner).read_begin1 }
530    }
531
532    /// Get ending location of alignment on the query sequence.
533    pub fn query_end(&self) -> i32 {
534        unsafe { (*self.inner).read_end1 }
535    }
536
537    pub fn cigar(&self) -> *mut u32 {
538        unsafe { (*self.inner).cigar }
539    }
540
541    pub fn cigar_len(&self) -> i32 {
542        unsafe { (*self.inner).cigarLen }
543    }
544}
545
546#[doc(hidden)]
547impl Drop for SSWResult {
548    fn drop(&mut self) {
549        unsafe { parasail_result_ssw_free(self.inner) }
550    }
551}