Skip to main content

parasail_rs/alignment/
table.rs

1use bitflags::bitflags;
2use std::fmt::{self, Display};
3
4/// A view into a score table from an alignment result.
5///
6/// # Layout
7/// - Rows represent positions in the query sequence (0..query_len)
8/// - Columns represent positions in the reference sequence (0..ref_len)
9/// - Data is stored as a flattened 1D array: `data[row * cols + col]`
10///
11/// # Example
12/// ```rust,no_run
13/// use parasail_rs::prelude::Aligner;
14///
15/// let query = b"ACGT";
16/// let reference = b"ACGT";
17/// let aligner = Aligner::new().use_table().build();
18/// let result = aligner.align(Some(query), reference)?;
19///
20/// let table = result.get_score_table()?;
21/// println!("Table dimensions: {} rows x {} cols", table.rows(), table.cols());
22///
23/// // Access specific cell
24/// if let Some(score) = table.get(0, 0) {
25///     println!("Score at (0, 0): {}", score);
26/// }
27///
28/// // Get final alignment score
29/// println!("Final score: {}", table.last());
30/// # Ok::<(), Box<dyn std::error::Error>>(())
31/// ```
32#[derive(Debug)]
33pub struct Table<'a> {
34    inner: &'a [i32],
35    rows: usize,
36    cols: usize,
37}
38
39impl<'a> Table<'a> {
40    /// Create a new Table view from a flat 1D array.
41    ///
42    /// # Panics
43    /// Panics in debug mode if data.len() != rows * cols
44    pub(crate) fn new(data: &'a [i32], rows: usize, cols: usize) -> Self {
45        debug_assert_eq!(
46            data.len(),
47            rows * cols,
48            "Table size mismatch: expected {} elements ({}x{}), got {}",
49            rows * cols,
50            rows,
51            cols,
52            data.len()
53        );
54        Self {
55            inner: data,
56            rows,
57            cols,
58        }
59    }
60
61    /// Get the value at the given row and column index.
62    ///
63    /// Returns `None` if indices are out of bounds.
64    ///
65    /// # Example
66    /// ```rust,no_run
67    /// # use parasail_rs::prelude::Aligner;
68    /// # let query = b"ACGT";
69    /// # let reference = b"ACGT";
70    /// # let aligner = Aligner::new().use_table().build();
71    /// # let result = aligner.align(Some(query), reference)?;
72    /// let table = result.get_score_table()?;
73    /// if let Some(score) = table.get(2, 3) {
74    ///     println!("Score at (2, 3): {}", score);
75    /// }
76    /// # Ok::<(), Box<dyn std::error::Error>>(())
77    /// ```
78    pub fn get(&self, row: usize, col: usize) -> Option<i32> {
79        if row < self.rows && col < self.cols {
80            Some(self.inner[row * self.cols + col])
81        } else {
82            None
83        }
84    }
85
86    /// Get the number of rows (query length).
87    pub fn rows(&self) -> usize {
88        self.rows
89    }
90
91    /// Get the number of columns (reference length).
92    pub fn cols(&self) -> usize {
93        self.cols
94    }
95
96    /// Get the raw underlying data as a 1D slice.
97    ///
98    /// The data is stored in row-major order, so element at (row, col)
99    /// can be accessed at index `row * cols + col`.
100    pub fn as_slice(&self) -> &[i32] {
101        self.inner
102    }
103
104    /// Get the value at the last cell (bottom-right of DP table).
105    pub fn last(&self) -> i32 {
106        self.inner[self.inner.len() - 1]
107    }
108}
109
110impl fmt::Display for Table<'_> {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        writeln!(f, "Table ({}x{}):", self.rows, self.cols)?;
113        for row in 0..self.rows {
114            write!(f, "[")?;
115            for col in 0..self.cols {
116                if col > 0 {
117                    write!(f, ", ")?;
118                }
119                write!(f, "{}", self.get(row, col).unwrap())?;
120            }
121            writeln!(f, "]")?;
122        }
123        Ok(())
124    }
125}
126
127bitflags! {
128    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129    pub struct TraceFlags: i32 {
130        const ZERO_MASK = 120;
131        const E_MASK = 103;
132        const F_MASK = 31;
133        const ZERO = 0;
134        const INS = 1;
135        const DEL = 2;
136        const DIAG = 4;
137        const DIAG_E= 8;
138        const INS_E= 16;
139        const DIAG_F= 32;
140        const DEL_F = 64;
141    }
142}
143
144impl Display for TraceFlags {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        let mut flags = Vec::new();
147        if self.contains(TraceFlags::INS) {
148            flags.push("INS");
149        }
150        if self.contains(TraceFlags::DEL) {
151            flags.push("DEL");
152        }
153        if self.contains(TraceFlags::DIAG) {
154            flags.push("DIAG");
155        }
156        if self.contains(TraceFlags::INS_E) {
157            flags.push("INS_E");
158        }
159        if self.contains(TraceFlags::DEL_F) {
160            flags.push("DEL_F");
161        }
162        if self.contains(TraceFlags::DIAG_E) {
163            flags.push("DIAG_E");
164        }
165        if self.contains(TraceFlags::DIAG_F) {
166            flags.push("DIAG_F");
167        }
168        write!(f, "{}", flags.join("|"))
169    }
170}
171
172/// A view into a tracback table from an alignment result.
173///
174/// # Layout
175/// - Rows represent positions in the query sequence (0..query_len)
176/// - Columns represent positions in the reference sequence (0..ref_len)
177/// - Data is stored as a flattened 1D array: `data[row * cols + col]`
178///
179/// # Example
180/// ```rust,no_run
181/// use parasail_rs::prelude::Aligner;
182///
183/// let query = b"ACGT";
184/// let reference = b"ACGT";
185/// let aligner = Aligner::new().use_trace().build();
186/// let result = aligner.align(Some(query), reference)?;
187///
188/// let table = result.get_trace_table()?;
189/// println!("Table dimensions: {} rows x {} cols", table.rows(), table.cols());
190///
191/// // Access specific cell traceback flags
192/// if let Some(flags) = table.get(0, 0) {
193///     println!("Traceback flags at (0, 0): {}", flags);
194/// }
195/// # Ok::<(), Box<dyn std::error::Error>>(())
196/// ```
197pub struct TracebackTable<'a> {
198    inner: &'a [i8],
199    rows: usize,
200    cols: usize,
201}
202
203impl<'a> TracebackTable<'a> {
204    /// Create a new Table view from a flat 1D array.
205    ///
206    /// # Panics
207    /// Panics in debug mode if data.len() != rows * cols
208    pub(crate) fn new(data: &'a [i8], rows: usize, cols: usize) -> Self {
209        debug_assert_eq!(
210            data.len(),
211            rows * cols,
212            "Table size mismatch: expected {} elements ({}x{}), got {}",
213            rows * cols,
214            rows,
215            cols,
216            data.len()
217        );
218        Self {
219            inner: data,
220            rows,
221            cols,
222        }
223    }
224
225    /// Get the value at the given row and column index.
226    ///
227    /// Returns `None` if indices are out of bounds.
228    ///
229    /// # Example
230    /// ```rust,no_run
231    /// # use parasail_rs::prelude::Aligner;
232    /// # let query = b"ACGT";
233    /// # let reference = b"ACGT";
234    /// # let aligner = Aligner::new().use_table().build();
235    /// # let result = aligner.align(Some(query), reference)?;
236    /// let table = result.get_trace_table()?;
237    /// if let Some(flags) = table.get(2, 3) {
238    ///     println!("Traceback flags at (2, 3): {}", flags);
239    /// }
240    /// # Ok::<(), Box<dyn std::error::Error>>(())
241    /// ```
242    pub fn get(&self, row: usize, col: usize) -> Option<TraceFlags> {
243        if row < self.rows && col < self.cols {
244            let bits = self.inner[row * self.cols + col];
245            let flags = TraceFlags::from_bits_truncate(bits as i32);
246            // convert to simple flags (only DIAG, INS, DEL)
247            let h_flags =
248                flags & TraceFlags::DIAG | flags & TraceFlags::INS | flags & TraceFlags::DEL;
249            Some(h_flags)
250        } else {
251            None
252        }
253    }
254
255    /// Get the detailed traceback flags, including those from E and F matrices,
256    /// at the given row and column index.
257    ///
258    /// Returns `None` if indices are out of bounds.
259    ///
260    /// # Example
261    /// ```rust,no_run
262    /// # use parasail_rs::prelude::Aligner;
263    /// # let query = b"ACGT";
264    /// # let reference = b"ACGT";
265    /// # let aligner = Aligner::new().use_table().build();
266    /// # let result = aligner.align(Some(query), reference)?;
267    /// let table = result.get_trace_table()?;
268    /// if let Some(flags) = table.get_detailed(2, 3) {
269    ///     println!("Traceback flags at (2, 3): {}", flags);
270    /// }
271    /// # Ok::<(), Box<dyn std::error::Error>>(())
272    /// ```
273    pub fn get_detailed(&self, row: usize, col: usize) -> Option<TraceFlags> {
274        if row < self.rows && col < self.cols {
275            let bits = self.inner[row * self.cols + col];
276            let flags = TraceFlags::from_bits_truncate(bits as i32);
277            Some(flags)
278        } else {
279            None
280        }
281    }
282
283    /// Get the number of rows (query length).
284    pub fn rows(&self) -> usize {
285        self.rows
286    }
287
288    /// Get the number of columns (reference length).
289    pub fn cols(&self) -> usize {
290        self.cols
291    }
292
293    /// Get the raw underlying data as a 1D slice.
294    ///
295    /// The data is stored in row-major order, so element at (row, col)
296    /// can be accessed at index `row * cols + col`.
297    pub fn as_slice(&self) -> &[i8] {
298        self.inner
299    }
300}
301
302impl fmt::Display for TracebackTable<'_> {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        writeln!(f, "Table ({}x{}):", self.rows, self.cols)?;
305        for row in 0..self.rows {
306            write!(f, "[")?;
307            for col in 0..self.cols {
308                if col > 0 {
309                    write!(f, ", ")?;
310                }
311                write!(f, "{}", self.get(row, col).unwrap())?;
312            }
313            writeln!(f, "]")?;
314        }
315        Ok(())
316    }
317}
318
319impl fmt::Debug for TracebackTable<'_> {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        writeln!(f, "Table ({}x{}):", self.rows, self.cols)?;
322        for row in 0..self.rows {
323            write!(f, "[")?;
324            for col in 0..self.cols {
325                if col > 0 {
326                    write!(f, ", ")?;
327                }
328                write!(f, "{}", self.get_detailed(row, col).unwrap())?;
329            }
330            writeln!(f, "]")?;
331        }
332        Ok(())
333    }
334}