Skip to main content

rustalign_aligner/
constraint.rs

1//! Constraint system for seed-based alignment
2
3use rustalign_common::Score;
4use std::fmt;
5
6/// Constraints on alignment edits
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Constraint {
9    /// Number of edits permitted
10    pub edits: i32,
11
12    /// Mismatches permitted
13    pub mms: i32,
14
15    /// Insertions permitted
16    pub ins: i32,
17
18    /// Deletions permitted
19    pub dels: i32,
20
21    /// Total penalty permitted
22    pub penalty: Score,
23
24    /// Ceiling values (maximum allowed)
25    /// Maximum edits permitted
26    pub edits_ceil: i32,
27    /// Maximum mismatches permitted
28    pub mms_ceil: i32,
29    /// Maximum insertions permitted
30    pub ins_ceil: i32,
31    /// Maximum deletions permitted
32    pub dels_ceil: i32,
33    /// Maximum penalty permitted
34    pub penalty_ceil: Score,
35}
36
37impl Constraint {
38    /// Create a new constraint with all values at zero
39    pub fn new() -> Self {
40        Self {
41            edits: 0,
42            mms: 0,
43            ins: 0,
44            dels: 0,
45            penalty: 0,
46            edits_ceil: i32::MAX,
47            mms_ceil: i32::MAX,
48            ins_ceil: i32::MAX,
49            dels_ceil: i32::MAX,
50            penalty_ceil: Score::MAX,
51        }
52    }
53
54    /// Check if constraint is satisfied
55    pub fn satisfied(&self) -> bool {
56        self.edits <= self.edits_ceil
57            && self.mms <= self.mms_ceil
58            && self.ins <= self.ins_ceil
59            && self.dels <= self.dels_ceil
60            && self.penalty <= self.penalty_ceil
61    }
62
63    /// Add a mismatch
64    pub fn add_mismatch(&mut self, penalty: Score) {
65        self.edits += 1;
66        self.mms += 1;
67        self.penalty += penalty;
68    }
69
70    /// Add an insertion
71    pub fn add_insertion(&mut self, penalty: Score) {
72        self.edits += 1;
73        self.ins += 1;
74        self.penalty += penalty;
75    }
76
77    /// Add a deletion
78    pub fn add_deletion(&mut self, penalty: Score) {
79        self.edits += 1;
80        self.dels += 1;
81        self.penalty += penalty;
82    }
83}
84
85impl Default for Constraint {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91/// Seed types for alignment
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum SeedType {
94    /// Exact match seed
95    Exact,
96
97    /// 1-mismatch seed (left to right)
98    OneMismatchLeftToRight,
99
100    /// 1-mismatch seed (right to left)
101    OneMismatchRightToLeft,
102
103    /// Multi-mismatch seed (inside out)
104    MultiMismatchInsideOut,
105}
106
107impl fmt::Display for SeedType {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            SeedType::Exact => write!(f, "Exact"),
111            SeedType::OneMismatchLeftToRight => write!(f, "1mm_L2R"),
112            SeedType::OneMismatchRightToLeft => write!(f, "1mm_R2L"),
113            SeedType::MultiMismatchInsideOut => write!(f, "MultiMM_IO"),
114        }
115    }
116}
117
118/// Seed policy configuration
119#[derive(Debug, Clone)]
120pub struct SeedPolicy {
121    /// Seed length
122    pub seed_len: usize,
123
124    /// Seed spacing interval
125    pub seed_interval: usize,
126
127    /// Maximum seed hits per seed
128    pub max_hits: usize,
129
130    /// Seed type
131    pub seed_type: SeedType,
132
133    /// Constraints per zone
134    pub constraints: [Constraint; 3],
135}
136
137impl SeedPolicy {
138    /// Create default seed policy
139    pub fn new() -> Self {
140        Self {
141            // Use 22 for better specificity
142            // 10bp is too short and matches too many positions in repetitive genomes
143            seed_len: 22,
144            seed_interval: 10,
145            max_hits: 100,
146            seed_type: SeedType::Exact,
147            constraints: [Constraint::new(), Constraint::new(), Constraint::new()],
148        }
149    }
150
151    /// Check if a seed position is valid
152    pub fn is_valid_seed_pos(&self, read_len: usize, pos: usize) -> bool {
153        pos + self.seed_len <= read_len
154    }
155}
156
157impl Default for SeedPolicy {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_constraint_new() {
169        let c = Constraint::new();
170        assert_eq!(c.edits, 0);
171        assert!(c.satisfied());
172    }
173
174    #[test]
175    fn test_constraint_add_mismatch() {
176        let mut c = Constraint::new();
177        c.add_mismatch(2);
178        assert_eq!(c.edits, 1);
179        assert_eq!(c.mms, 1);
180        assert_eq!(c.penalty, 2);
181    }
182
183    #[test]
184    fn test_seed_policy() {
185        let policy = SeedPolicy::new();
186        assert_eq!(policy.seed_len, 22);
187        assert!(policy.is_valid_seed_pos(100, 50));
188        assert!(policy.is_valid_seed_pos(100, 10)); // 10 + 22 = 32 <= 100
189        assert!(!policy.is_valid_seed_pos(25, 10)); // 10 + 22 = 32 > 25, invalid
190    }
191}