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}