Skip to main content

pa_types/
cost.rs

1//! `i32` [`Cost`] and [`Score`], affine [`CostModel`] and [`ScoreModel`].
2use serde::{Deserialize, Serialize};
3
4use crate::I;
5
6/// The non-negative cost of an alignment.
7pub type Cost = i32;
8
9/// The score of an alignment. Can be positive or negative.
10pub type Score = i32;
11
12/// Different cost models.
13/// All values must be non-negative.
14#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
15#[cfg_attr(feature = "clap", derive(clap::Args))]
16pub struct CostModel {
17    /// Substitution cost, (> 0)
18    ///
19    /// TODO: Support `-infinity` to disallow substitutions entirely.
20    #[cfg_attr(feature = "clap", clap(long, default_value_t = 1, value_name = "COST"))]
21    pub sub: Cost,
22    /// Gap open cost (>= 0)
23    ///
24    /// When 0, gap cost is linear.
25    #[cfg_attr(feature = "clap", clap(long, default_value_t = 0, value_name = "COST"))]
26    pub open: Cost,
27    /// Gap extend cost (> 0)
28    #[cfg_attr(feature = "clap", clap(long, default_value_t = 1, value_name = "COST"))]
29    pub extend: Cost,
30}
31
32impl CostModel {
33    pub fn unit() -> Self {
34        Self {
35            sub: 1,
36            open: 0,
37            extend: 1,
38        }
39    }
40    pub fn is_unit(&self) -> bool {
41        self == &Self::unit()
42    }
43    pub fn linear(sub: Cost, indel: Cost) -> Self {
44        Self {
45            sub,
46            open: 0,
47            extend: indel,
48        }
49    }
50    pub fn is_linear(&self) -> bool {
51        self.open == 0
52    }
53    pub fn affine(sub: Cost, open: Cost, extend: Cost) -> Self {
54        Self { sub, open, extend }
55    }
56    pub fn is_affine(&self) -> bool {
57        self.open > 0
58    }
59
60    /// The cost of a substitution.
61    pub fn sub(&self) -> Cost {
62        self.sub
63    }
64    /// The cost of a substitution, or None if not allowed.
65    pub fn maybe_sub(&self) -> Option<Cost> {
66        Some(self.sub)
67    }
68    /// The cost of an insertion of given length.
69    pub fn ins(&self, len: I) -> Cost {
70        self.open + len * self.extend
71    }
72    /// The cost of a deletion of given length.
73    pub fn del(&self, len: I) -> Cost {
74        self.open + len * self.extend
75    }
76}
77
78#[derive(Debug, Eq, PartialEq, Clone, Copy)]
79pub struct ScoreModel {
80    /// > 0
81    pub r#match: Score,
82    /// < 0
83    pub sub: Score,
84    /// <= 0
85    pub open: Score,
86    /// < 0
87    pub extend: Score,
88
89    pub factor: i32,
90}
91
92impl ScoreModel {
93    const OFFSET: i32 = 1;
94
95    pub fn from_costs(cm: CostModel) -> Self {
96        let factor;
97        if cm.sub > 2 && cm.extend > 1 {
98            factor = 1;
99        } else if cm.sub == 1 {
100            factor = 3;
101        } else {
102            factor = 2;
103        }
104
105        Self {
106            r#match: Self::OFFSET * 2,
107            // < 0
108            sub: -cm.sub * factor + Self::OFFSET * 2,
109            // <= 0
110            open: -cm.open * factor,
111            // < 0
112            extend: -cm.extend * factor + Self::OFFSET,
113            factor,
114        }
115    }
116
117    pub fn global_cost(&self, score: Score, a_len: usize, b_len: usize) -> Cost {
118        let path_len = (a_len + b_len) as i32;
119        let s = -score + path_len * Self::OFFSET;
120        // assert!(
121        //     s % self.factor == 0,
122        //     "path_len {path_len}: Score {score} reduced to cost {s} which is not a multiple of {}",
123        //     self.factor
124        // );
125        s / self.factor
126    }
127}