1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
mod matrix_ops;
mod qhat;
mod util;

use ndarray::prelude::*;
use matrix_ops::calc_diff_matrix;
use qhat::{get_qhat_values, qhat_values};
use rand::prelude::SliceRandom;
use util::{argmax, get_windows, maximum};

const DEFAULT_PVALUE: f64 = 0.01;
const DEFAULT_PERMUTATIONS: usize = 100;

pub struct EDivisive {
    pvalue: f64,
    n_permutations: usize,
}

#[derive(PartialEq, Copy, Clone, Debug)]
struct ChangePoint {
    index: usize,
    qhat: f64,
}

fn get_best_change_point(
    diff_matrix: &ArrayView2<f64>,
    known_change_points: &Vec<ChangePoint>,
) -> ChangePoint {
    let series_len = diff_matrix.nrows();
    let mut change_points: Vec<ChangePoint> = vec![];

    let boundaries: Vec<usize> = get_windows(&cp_indexes(&known_change_points), series_len);
    for bounds in boundaries.windows(2) {
        let a = bounds[0];
        let b = bounds[1];

        let qhats = qhat_values(&diff_matrix.slice(s!(a..b, a..b)));
        let max_idx = argmax(&qhats);
        change_points.push(ChangePoint{index: max_idx + a, qhat: qhats[max_idx]});
    }

    let max_index = argmax(&change_points.iter().map(|cp| cp.qhat).collect());

    change_points[max_index]
}

fn cp_indexes(change_points: &Vec<ChangePoint>) -> Vec<usize> {
    change_points.iter().map(|cp| cp.index).collect()
}

impl EDivisive {
    pub fn default() -> EDivisive {
        EDivisive {
            pvalue: DEFAULT_PVALUE,
            n_permutations: DEFAULT_PERMUTATIONS,
        }
    }

    pub fn new(pvalue: f64, n_permutations: usize) -> EDivisive {
        EDivisive {
            pvalue,
            n_permutations,
        }
    }

    pub fn get_change_points(&self, series: &Vec<f64>) -> Vec<usize> {
        let diff_matrix = calc_diff_matrix(series);
        let mut change_points: Vec<ChangePoint> = vec![];

        let mut best_candidate = get_best_change_point(&diff_matrix.view(), &change_points);
        let mut windows = get_windows(&cp_indexes(&change_points), series.len());
        while self.is_significant(&best_candidate, series, &windows) {
            if change_points.contains(&best_candidate) {
                break;
            }
            change_points.push(best_candidate);
            windows = get_windows(&cp_indexes(&change_points), series.len());
            best_candidate = get_best_change_point(&diff_matrix.view(), &change_points);
        }

        cp_indexes(&change_points)
    }

    fn is_significant(
        &self,
        candidate: &ChangePoint,
        series: &Vec<f64>,
        windows: &Vec<usize>,
    ) -> bool {
        if candidate.qhat < 1e-9 {
            return false;
        }
        let permutes_with_higher = (0..self.n_permutations)
            .map(|_| permutation_test(series, windows))
            .filter(|v| v > &candidate.qhat)
            .count();
        let probability = permutes_with_higher as f64 / (self.n_permutations + 1) as f64;

        probability <= self.pvalue
    }
}

fn permutation_test(series: &Vec<f64>, windows: &Vec<usize>) -> f64 {
    let mut rng = rand::thread_rng();
    let mut permuted_qhat_values: Vec<f64> = vec![];

    for bounds in windows.windows(2) {
        let a = bounds[0];
        let b = bounds[1];

        let mut window: Vec<f64> = vec![0.; b - a];
        window.copy_from_slice(&series[a..b]);
        window.shuffle(&mut rng);

        let q_list = get_qhat_values(&window);
        let (_, max_qhat) = maximum(&q_list);
        permuted_qhat_values.push(max_qhat);
    }

    let (_, max_value) = maximum(&permuted_qhat_values);
    max_value
}