1use std::cmp::Ordering;
9
10use crate::signature_def::MotifId;
11use crate::strand::Strand;
12
13pub fn format_double_java(d: f64) -> String {
16 if d == 0.0 {
17 return "0.0".to_string();
18 }
19 if d.is_nan() {
20 return "NaN".to_string();
21 }
22 if d.is_infinite() {
23 return if d < 0.0 {
24 "-Infinity".to_string()
25 } else {
26 "Infinity".to_string()
27 };
28 }
29 let abs = d.abs();
30 if abs >= 1e-3 && abs < 1e7 {
31 format!("{}", d)
32 } else {
33 format!("{:e}", d).replace('e', "E")
34 }
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct Motif {
40 pub id: MotifId,
42 pub protein_sequence_id: String,
44 pub position: u64,
46 pub protein_sequence: String,
48 pub pvalue: f64,
50 pub score: f64,
52 pub dna_sequence_id: Option<String>,
54 pub dna_start: u64,
56 pub dna_end: u64,
58 pub strand: Strand,
60 pub frame: u8,
62 pub dna_parameters_set: bool,
64}
65
66impl Motif {
67 pub fn new_protein(
69 id: MotifId,
70 protein_sequence_id: String,
71 position: u64,
72 protein_sequence: String,
73 pvalue: f64,
74 ) -> Self {
75 Motif {
76 id,
77 protein_sequence_id,
78 position,
79 protein_sequence,
80 pvalue,
81 score: 0.0,
82 dna_sequence_id: None,
83 dna_start: 0,
84 dna_end: 0,
85 strand: Strand::Forward,
86 frame: 0,
87 dna_parameters_set: false,
88 }
89 }
90
91 pub fn set_dna(
103 &mut self,
104 dna_sequence_id: String,
105 offset: u64,
106 fragment_length: u64,
107 frame: u8,
108 strand: Strand,
109 ) {
110 let len = self.protein_sequence.len() as u64;
112 let pos_minus_1 = self.position.saturating_sub(1);
113 let (start, end) = match strand {
114 Strand::Forward => {
115 let s = pos_minus_1 * 3 + frame as u64 + offset;
116 let e = (self.position + len - 1) * 3 + frame as u64 + offset;
117 (s, e)
118 }
119 Strand::Reverse => {
120 let s = offset + fragment_length - ((self.position + len - 1) * 3 + frame as u64);
121 let e = offset + fragment_length - (pos_minus_1 * 3 + frame as u64);
122 (s, e)
123 }
124 };
125 self.dna_sequence_id = Some(dna_sequence_id);
126 self.dna_start = start;
127 self.dna_end = end;
128 self.strand = strand;
129 self.frame = frame;
130 self.dna_parameters_set = true;
131 }
132
133 #[inline]
135 pub fn has_stop(&self) -> bool {
136 self.protein_sequence.contains('*')
137 }
138
139 pub fn export_string(&self) -> String {
141 let mut s = format!(
142 "{}\t{}\t{}\t{}\t{}\t{}",
143 crate::signature_def::motif_id_str(self.id),
144 self.protein_sequence_id,
145 self.position,
146 self.protein_sequence,
147 format_double_java(self.pvalue),
148 format_double_java(self.score)
149 );
150 if self.dna_parameters_set {
151 s.push_str(&format!(
152 "\t{}\t{}\t{}\t{}\t{}",
153 self.dna_sequence_id.as_deref().unwrap_or(""),
154 self.dna_start,
155 self.dna_end,
156 self.strand.symbol(),
157 self.frame
158 ));
159 } else {
160 s.push_str("\t\t\t\t\t");
161 }
162 s
163 }
164
165 pub fn from_export_line(line: &str) -> Option<Self> {
167 let cols: Vec<&str> = line.split('\t').collect();
168 if cols.len() < 6 {
169 return None;
170 }
171 let id = cols[0]
173 .trim_start_matches("motif_")
174 .parse::<MotifId>()
175 .ok()?;
176 let position: u64 = cols[2].parse().ok()?;
177 let pvalue: f64 = cols[4].parse().ok()?;
178 let score: f64 = cols[5].parse().ok()?;
179 let mut m = Motif::new_protein(
180 id,
181 cols[1].to_string(),
182 position,
183 cols[3].to_string(),
184 pvalue,
185 );
186 m.score = score;
187 if cols.len() >= 11 && !cols[6].is_empty() {
189 m.dna_sequence_id = Some(cols[6].to_string());
190 m.dna_start = cols[7].parse().ok()?;
191 m.dna_end = cols[8].parse().ok()?;
192 m.strand = if cols[9] == "-" {
193 Strand::Reverse
194 } else {
195 Strand::Forward
196 };
197 m.frame = cols[10].parse().ok()?;
198 m.dna_parameters_set = true;
199 }
200 Some(m)
201 }
202}
203
204impl PartialOrd for Motif {
207 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
208 Some(self.cmp(other))
209 }
210}
211
212impl Eq for Motif {}
213
214impl Ord for Motif {
215 fn cmp(&self, other: &Self) -> Ordering {
222 if self.dna_parameters_set && other.dna_parameters_set {
223 let self_id = self.dna_sequence_id.as_deref().unwrap_or("");
224 let other_id = other.dna_sequence_id.as_deref().unwrap_or("");
225 if self_id.eq_ignore_ascii_case(other_id) {
226 match (self.strand, other.strand) {
228 (Strand::Forward, Strand::Reverse) => Ordering::Less,
229 (Strand::Reverse, Strand::Forward) => Ordering::Greater,
230 _ => match self.strand {
231 Strand::Forward => self.dna_start.cmp(&other.dna_start),
232 Strand::Reverse => other.dna_start.cmp(&self.dna_start),
233 },
234 }
235 } else {
236 self.protein_sequence_id.cmp(&other.protein_sequence_id)
238 }
239 } else {
240 if self.protein_sequence_id.eq_ignore_ascii_case(&other.protein_sequence_id) {
242 self.position.cmp(&other.position)
243 } else {
244 self.protein_sequence_id.cmp(&other.protein_sequence_id)
245 }
246 }
247 }
248}