clevel/
lib.rs

1use std::cmp::Ordering;
2
3pub mod error;
4pub mod models;
5
6#[cfg(target_arch = "wasm32")]
7pub mod wasm;
8
9pub use error::ClevelError;
10pub use models::{Model, NormalizedModel};
11
12trait FloatIterExt {
13    fn float_min(&mut self) -> f64;
14    fn float_max(&mut self) -> f64;
15}
16
17impl<T> FloatIterExt for T
18where
19    T: Iterator<Item = f64>,
20{
21    fn float_max(&mut self) -> f64 {
22        self.fold(std::f64::NAN, f64::max)
23    }
24
25    fn float_min(&mut self) -> f64 {
26        self.fold(std::f64::NAN, f64::min)
27    }
28}
29
30lazy_static::lazy_static! {
31    pub static ref VERSION: &'static str = {
32        let mut version = env!("CARGO_PKG_VERSION").to_string();
33        if let Some(v) = option_env!("GIT_HASH") {
34            version.push_str("-");
35            version.push_str(v);
36        }
37        if cfg!(debug_assertions) {
38            version.push_str("-debug");
39        }
40        Box::leak(Box::new(version))
41    };
42}
43
44fn phase_data(crisp: u32, a: u32, b: u32) -> Option<f64> {
45    match Some(f64::default()) {
46        Some(_) if crisp <= a => Some(0.0),
47        Some(_) if a < crisp && crisp <= (a + b) / 2 => {
48            let first = (crisp - a) as f64;
49            let second = (b - a) as f64;
50            return Some(2.0 * f64::powf(first / second, 2.0));
51        }
52        Some(_) if (a + b) / 2 < crisp && crisp < b => {
53            let first = (b - crisp) as f64;
54            let second = (b - a) as f64;
55            return Some(1.0 - 2.0 * f64::powf(first / second, 2.0));
56        }
57        Some(_) if crisp >= b => Some(1.0),
58        _ => None,
59    }
60}
61
62fn phase_data_vec(models: &Vec<Model>) -> Vec<NormalizedModel> {
63    let mut normalized_models: Vec<NormalizedModel> = vec![];
64    for item in models {
65        let phased_crisps = item
66            .crisps
67            .iter()
68            .map(|num| phase_data(*num, item.a, item.b).unwrap())
69            .collect::<Vec<f64>>();
70        let phased_t = phase_data(item.t, item.a, item.b).unwrap();
71        normalized_models.push(NormalizedModel {
72            phased_crisps,
73            phased_t,
74        })
75    }
76    normalized_models
77}
78
79fn weighting_normalization(models: &Vec<Model>) -> Vec<f64> {
80    let pvec = models.iter().map(|model| model.p).collect::<Vec<u32>>();
81    let sum_of_pvec = pvec.iter().sum::<u32>();
82    pvec.iter()
83        .map(|p| *p as f64 / sum_of_pvec as f64)
84        .collect::<Vec<f64>>()
85}
86
87fn calc_desired_value(crisp: f64, min_crisp: f64, max_crisp: f64, t: f64) -> f64 {
88    let first = (t - crisp).abs();
89    let second: Vec<f64> = vec![t - min_crisp, max_crisp - t];
90    let max_second = second.iter().cloned().float_max();
91    1.0 - (first / max_second)
92}
93
94fn desired_values(models: &Vec<Model>) -> Vec<Vec<f64>> {
95    let mut vec_of_vec: Vec<Vec<f64>> = vec![];
96    let normalized_models = phase_data_vec(models);
97    for nm in normalized_models {
98        let vv: Vec<f64> = nm
99            .phased_crisps
100            .iter()
101            .map(|num: &f64| {
102                let max_val = nm.phased_crisps.iter().cloned().float_max();
103                let min_val = nm.phased_crisps.iter().cloned().float_min();
104                calc_desired_value(*num, min_val, max_val, nm.phased_t)
105            })
106            .collect();
107        vec_of_vec.push(vv)
108    }
109    vec_of_vec
110}
111
112fn simplify_desired_values(dv: Vec<Vec<f64>>) -> Vec<f64> {
113    let mut new_dv: Vec<f64> = vec![];
114
115    for v in dv {
116        for i in v {
117            new_dv.push(i)
118        }
119    }
120
121    new_dv
122}
123
124fn initialize_matrix(models: &Vec<Model>) -> simple_matrix::Matrix<f64> {
125    let dv = desired_values(&models);
126    let simple_dv = simplify_desired_values(dv);
127
128    let rows = models.len();
129    let cols = models[0].crisps.len();
130
131    let matrix: simple_matrix::Matrix<f64> =
132        simple_matrix::Matrix::from_iter(rows, cols, simple_dv.iter().cloned());
133
134    matrix
135}
136
137pub fn aggregated_score(models: &Vec<Model>) -> Vec<f64> {
138    let weights = weighting_normalization(&models);
139    let matrix = initialize_matrix(&models);
140
141    let mut temp: Vec<f64> = vec![];
142
143    for col in 0..matrix.cols() {
144        let get_col = matrix.get_col(col).unwrap().collect::<Vec<&f64>>();
145        let mut res: Vec<f64> = vec![];
146
147        for row in 0..matrix.rows() {
148            res.push(*get_col[row] * weights[row])
149        }
150
151        temp.push(res.iter().sum())
152    }
153
154    temp
155}
156
157pub fn find_head_from_models(models: Vec<Model>) -> (String, f64) {
158    let aggregation_estimates = aggregated_score(&models);
159    let maxf = aggregation_estimates.iter().cloned().float_max();
160    let mut expert: String = String::new();
161
162    for (index, item) in aggregation_estimates.iter().enumerate() {
163        if *item == maxf {
164            expert = format!("Expert {}", index + 1);
165        }
166    }
167
168    (expert, maxf)
169}
170
171pub fn ranking_of_experts(models: &Vec<Model>) -> Vec<f64> {
172    let aggregation_estimates = aggregated_score(&models);
173    let mut sorted_aggregation_estimates = aggregation_estimates.clone();
174    sorted_aggregation_estimates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater));
175
176    sorted_aggregation_estimates.iter().rev().cloned().collect()
177}