1
use std::ops::Range;
2

            
3
use color_eyre::{eyre::{WrapErr, eyre}, Result};
4
use ndarray::{s, Array2, Axis};
5
use polars::prelude::*;
6
use serde::{Deserialize, Serialize};
7

            
8
476
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
9
pub struct Coord {
10
    pub contig: String,
11
    pub pos: i64,
12
    pub reference: String,
13
    pub alt: String,
14
    pub phase: bool,
15
}
16

            
17
15924
#[derive(Debug, Default, Clone, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
18
pub struct HapVariant {
19
    pub contig: String,
20
    pub pos: i64,
21
    pub reference: String,
22
    pub alt: String,
23
    pub gt: u8,
24
}
25

            
26
impl HapVariant {
27
832
    pub fn genotype(&self) -> &String {
28
832
        match self.gt {
29
798
            0 => &self.reference,
30
34
            1 => &self.alt,
31
            _ => panic!("Please normalize the vcf using bcftools norm"),
32
        }
33
832
    }
34
}
35

            
36
impl std::fmt::Display for HapVariant {
37
12
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
38
12
        let line = format!(
39
12
            "{}_{}_{}_{}",
40
12
            self.contig, self.pos, self.reference, self.alt
41
12
        );
42
12
        write!(f, "{line}")
43
12
    }
44
}
45

            
46
impl PartialEq<HapVariant> for Coord {
47
15426
    fn eq(&self, other: &HapVariant) -> bool {
48
15426
        self.contig == other.contig
49
15426
            && self.pos == other.pos
50
648
            && self.reference == other.reference
51
642
            && self.alt == other.alt
52
15426
    }
53
}
54

            
55
impl PartialEq<Coord> for HapVariant {
56
240768
    fn eq(&self, other: &Coord) -> bool {
57
240768
        self.contig == other.contig
58
358872
            && self.pos == other.pos
59
24744
            && self.reference == other.reference
60
24996
            && self.alt == other.alt
61
305934
    }
62
}
63

            
64
impl std::fmt::Display for Coord {
65
1
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66
1
        let line = format!(
67
1
            "{}_{}_{}_{}",
68
1
            self.contig, self.pos, self.reference, self.alt
69
1
        );
70
1
        write!(f, "{line}")
71
1
    }
72
}
73

            
74
pub type ClinicalData = (f64, f64);
75

            
76
985
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, PartialOrd)]
77
pub struct HaploNode {
78
    pub indexes: Vec<usize>,
79
    pub indexes_len: usize,
80
    pub samples: Vec<String>,
81
    pub pos: usize,
82
    pub haplotype: Vec<HapVariant>,
83
    pub decoys: Option<Vec<String>>,
84
}
85

            
86
impl HaploNode {
87
24
    pub fn haplotype(&self) -> String {
88
192
        self.haplotype.iter().fold(String::new(), |string, v| {
89
192
            format!("{string}{}", v.genotype())
90
192
        })
91
24
    }
92
}
93

            
94
impl std::fmt::Display for HaploNode {
95
1315
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
96
1315
        let samples = self
97
1315
            .samples
98
1315
            .iter()
99
1315
            .enumerate()
100
1315
            .fold(String::new(), |string, (i, v)| {
101
8198
                if i != 0 && i % 15 == 0 {
102
162
                    format!("{string} {v}\n")
103
                } else {
104
8036
                    format!("{string} {v}")
105
                }
106
8198
            });
107
1315
        let line = format!("{}:{samples}", self.indexes.len());
108
1315
        write!(f, "{line}")
109
1315
    }
110
}
111

            
112
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
113
pub struct SharedLength {
114
    pub index: usize,
115
    pub start: Option<usize>,
116
    pub end: Option<usize>,
117
    pub start_pos: i64,
118
    pub end_pos: i64,
119
}
120

            
121
// TODO: fix unwrappings
122
impl SharedLength {
123
7908
    pub fn end(&self) -> usize {
124
7908
        self.end.unwrap()
125
7908
    }
126
7908
    pub fn start(&self) -> usize {
127
7908
        self.start.unwrap()
128
7908
    }
129
}
130

            
131
impl AsRef<SharedLength> for SharedLength {
132
12570
    fn as_ref(&self) -> &Self {
133
12570
        self
134
12570
    }
135
}
136

            
137
//TODO: Most of these fields, if not all, should be private to ensure the correctness of
138
// for example common selects and slicings performed by the user
139
// Slice indexing matrices is also an anti-pattern in Polars
140
#[derive(Debug, Default, Clone, PartialEq)]
141
pub struct PhasedMatrix {
142
    pub variant_idx: usize,
143
    pub matrix: Array2<u8>,
144
    samples: Vec<String>,
145
    coords: Vec<Coord>,
146
    clinical_data: Option<DataFrame>,
147
}
148

            
149
impl PhasedMatrix {
150
336
    pub fn new(
151
336
        variant_idx: usize,
152
336
        matrix: Array2<u8>,
153
336
        samples: Vec<String>,
154
336
        coords: Vec<Coord>,
155
336
    ) -> Self {
156
336
        Self {
157
336
            variant_idx,
158
336
            matrix,
159
336
            samples,
160
336
            coords,
161
336
            clinical_data: None,
162
336
        }
163
336
    }
164

            
165
2160
    pub fn nsamples(&self) -> usize {
166
2160
        self.samples.len()
167
2160
    }
168

            
169
402
    pub fn ncoords(&self) -> usize {
170
402
        self.coords.len()
171
402
    }
172

            
173
72
    pub fn samples(&self) -> &Vec<String> {
174
72
        &self.samples
175
72
    }
176

            
177
2982
    pub fn coords(&self) -> &Vec<Coord> {
178
2982
        &self.coords
179
2982
    }
180

            
181
54
    pub fn coords_mut(&mut self) -> &mut Vec<Coord> {
182
54
        &mut self.coords
183
54
    }
184

            
185
48
    pub fn set_coords(&mut self, coords: Vec<Coord>) {
186
48
        self.coords = coords;
187
48
    }
188

            
189
29532
    pub fn variant_idx(&self) -> usize {
190
29532
        self.variant_idx
191
29532
    }
192

            
193
12
    pub fn set_variant_idx(&mut self, variant_idx: usize) {
194
12
        self.variant_idx = variant_idx;
195
12
    }
196

            
197
2754
    pub fn variant_idx_pos(&self) -> i64 {
198
2754
        self.coords[self.variant_idx].pos
199
2754
    }
200

            
201
19992
    pub fn get_pos(&self, idx: usize) -> i64 {
202
19992
        self.coords[idx].pos
203
19992
    }
204

            
205
2256
    pub fn get_contig(&self) -> &String {
206
2256
        &self.coords[0].contig
207
2256
    }
208

            
209
    pub fn get_nearest_idx_by_pos(&self, pos: i64) -> usize {
210
1236
        if let Some(idx) = self.coords.iter().position(|c| c.pos >= pos) {
211
18
            if idx == 0 { return idx }
212
18
            if self.get_pos(idx) == pos { return idx }
213
18

            
214
18
            let one_before_idx = idx - 1;
215
18
            let before = self.get_pos(one_before_idx);
216
18
            let after = self.get_pos(idx);
217
18
            if pos - before > after - pos {
218
12
                idx
219
            } else {
220
6
                one_before_idx
221
            }
222
        } else {
223
            // return last element if no element is larger 
224
18
            self.coords.len()
225
        }
226
36
    }
227

            
228
6
    pub fn idx_by_coord(&self, coord: &Coord) -> Option<usize> {
229
30
        self.coords.iter().position(|c| c == coord)
230
6
    }
231

            
232
12
    pub fn idx_by_hapvariant(&self, hap: &HapVariant) -> Option<usize> {
233
384
        self.coords.iter().position(|coord| coord == hap)
234
12
    }
235

            
236
42
    pub fn matrix(&self) -> &Array2<u8> {
237
42
        &self.matrix
238
42
    }
239

            
240
29238
    pub fn get_sample_name(&self, index: usize) -> String {
241
29238
        self.samples.get(index).unwrap().clone()
242
29238
    }
243

            
244
2388
    pub fn get_sample_names(&self, indexes: &[usize]) -> Vec<String> {
245
2388
        indexes
246
2388
            .iter()
247
16128
            .map(|i| self.samples.get(*i).unwrap())
248
2388
            .cloned()
249
2388
            .collect()
250
2388
    }
251

            
252
    // Sort inside select_rows to fend from misuse and bugs down the line
253
150
    pub fn select_rows(&mut self, mut to_keep: Vec<usize>) {
254
150
        to_keep.sort();
255
150
        self.matrix = self.matrix.select(Axis(0), &to_keep);
256
150

            
257
150
        self.samples = to_keep
258
150
            .iter()
259
2034
            .map(|index| self.samples[*index].clone())
260
150
            .collect();
261
150
    }
262

            
263
6
    pub fn select_columns_by_idx(&mut self, to_keep: &mut [usize]) {
264
6
        to_keep.sort();
265
6
        self.matrix = self.matrix.select(Axis(1), to_keep);
266
6

            
267
6
        self.coords = to_keep
268
6
            .iter()
269
18
            .map(|index| self.coords[*index].clone())
270
6
            .collect();
271
6
    }
272

            
273
12
    pub fn select_columns_by_range(&mut self, range: Range<usize>) {
274
12
        // Take the first element of the range and subtract from variant_idx
275
12
        self.variant_idx -= range.clone().next().unwrap();
276
12
        self.matrix = self.matrix.slice(s![.., range.clone()]).to_owned();
277
12
        self.coords = self.coords[range].to_vec();
278
12
    }
279

            
280
2328
    pub fn find_haplotype_for_sample(
281
2328
        &self,
282
2328
        contig: &str,
283
2328
        range: Range<usize>,
284
2328
        sample: usize,
285
2328
    ) -> Vec<HapVariant> {
286
2328
        range
287
34890
            .map(|index| HapVariant {
288
34890
                contig: contig.to_string(),
289
34890
                pos: self.coords[index].pos,
290
34890
                alt: self.coords[index].alt.clone(),
291
34890
                reference: self.coords[index].reference.clone(),
292
34890
                gt: *self.matrix.slice(ndarray::s![sample, index]).into_scalar(),
293
34890
            })
294
2328
            .collect()
295
2328
    }
296

            
297
18
    pub fn set_variable_data(&mut self, df: DataFrame) -> Result<()> {
298
18
        let raw_utf8 = df["id"].utf8().wrap_err(eyre!("Make sure variable file has an id column"))?;
299
18
        let clinical_samples: Vec<_> = raw_utf8.into_iter().flatten().collect();
300

            
301
522
        for sample in &self.samples {
302
504
           if !clinical_samples.contains(&sample.as_str()) {
303
36
               tracing::warn!("Sample {sample} is not present in clinical data");
304
468
           }
305
        }
306
18
        let names = lit(Series::from_iter(self.samples.clone()));
307

            
308
18
        let df = df
309
18
            .clone()
310
18
            .lazy()
311
18
            .filter(col("id").is_in(names))
312
18
            .collect()?;
313

            
314
18
        self.clinical_data = Some(df);
315
18
        Ok(())
316
18
    }
317

            
318
60
    pub fn get_variable_data_mean(&self, indexes: &[usize], column_names: &[String]) -> Result<Option<Vec<f64>>> {
319
60
        let names = self.get_sample_names(indexes);
320
60
        let names = lit(Series::from_iter(names));
321

            
322
60
        if let Some(df) = &self.clinical_data {
323
60
            let mut means = Vec::with_capacity(column_names.len());
324
126
            for column_name in column_names {
325
72
                let mean = df
326
72
                    .clone()
327
72
                    .lazy()
328
72
                    .filter(col("id").is_in(names.clone()))
329
72
                    .select([col(column_name).mean()])
330
72
                    .collect()?;
331

            
332
72
                let mean = mean 
333
72
                    .column(column_name)?
334
72
                    .sum::<f64>()
335
72
                    .ok_or(eyre::eyre!("A value {mean:?} cannot be parsed as an f64 in the column {column_name}. This could also mean all values are NA in the column."))?;
336
66
                means.push(mean)
337
            }
338
54
            Ok(Some(means))
339
        } else {
340
            Ok(None)
341
        }
342
60
    }
343

            
344
18
    pub fn get_variable_data_vecs(&self, indexes: &[usize], column_names: &[String]) -> Result<Option<Vec<Vec<f64>>>> {
345
18
        let names = self.get_sample_names(indexes);
346
18
        let names = lit(Series::from_iter(names));
347

            
348
18
        if let Some(df) = &self.clinical_data {
349
18
            let mut vecs = Vec::with_capacity(column_names.len());
350
42
            for column_name in column_names {
351
24
                let df = df
352
24
                    .clone()
353
24
                    .lazy()
354
24
                    .filter(col("id").is_in(names.clone()))
355
24
                    .select([col(column_name)])
356
24
                    .collect()?;
357

            
358
24
                if let Ok(vec) = df[column_name.as_str()].f64() {
359
                    let vec: Vec<f64> = vec.into_iter().flatten().collect();
360
                    vecs.push(vec)
361
24
                } else if let Ok(vec) = df[column_name.as_str()].i64() {
362
180
                    let vec: Vec<f64> = vec.into_iter().flatten().map(|v| v as f64).collect();
363
24
                    vecs.push(vec)
364
                }
365

            
366

            
367
            }
368
18
            Ok(Some(vecs))
369
        } else {
370
            Ok(None)
371
        }
372
18
    }
373
}
374

            
375
#[cfg(test)]
376
mod tests {
377
    use super::*;
378

            
379
1
    #[test]
380
1
    fn test_haplonode_displays() {
381
1
        let hn = HaploNode { 
382
1
            samples: vec!["S1".to_string(), "S2".to_string()],
383
1
            ..Default::default()
384
1
        };
385
1

            
386
1
        assert_eq!("0: S1 S2".to_string() ,format!("{hn}"))
387
1
    }
388

            
389
1
    #[test]
390
1
    fn test_coords_displays() {
391
1
        let coord  = Coord { 
392
1
            contig: "chr9".to_string(),
393
1
            pos: 25,
394
1
            reference: "G".to_string(),
395
1
            alt: "T".to_string(),
396
1
            phase: false,
397
1
        };
398
1

            
399
1
        assert_eq!("chr9_25_G_T".to_string() ,format!("{coord}"))
400
1
    }
401

            
402
1
    #[test]
403
1
    fn test_hapvariant_genotype_getter() {
404
1
        let mut hv  = HapVariant { 
405
1
            contig: "chr9".to_string(),
406
1
            pos: 25,
407
1
            reference: "G".to_string(),
408
1
            alt: "T".to_string(),
409
1
            gt: 1,
410
1
        };
411
1

            
412
1
        assert_eq!(&"T".to_string(), hv.genotype());
413

            
414
1
        hv.gt = 0;
415
1
        assert_eq!(&"G".to_string(), hv.genotype());
416

            
417
1
    }
418

            
419
}