spitzenfinder 0.4.4

A program to find peaks and plot fourier.out files of the VIBES program.
Documentation
///This module gives access to the contents of a *fourier.out* file generated by the VIBES program.
pub mod data {
    pub mod errors;
    pub mod slope2d;

    use crate::data::slope2d::Slope2D;
    use std::cmp;
    use std::collections::HashMap;
    use std::error::Error;
    use std::fs::File;
    use std::io::{self, BufRead};


    use self::errors::NoDataError;
    /// Wrapper function for lt.
    pub fn is_less<T: PartialOrd>(a: T, b: &T) -> bool {
        a.lt(b)
    }
    /// Wrapper function for gt.
    pub fn is_more<T: PartialOrd>(a: T, b: &T) -> bool {
        a.gt(b)
    }

    pub fn find_inflection_point<T: PartialOrd + Copy>(
        data: &[T],
        cmp_func: fn(T, &T) -> bool,
    ) -> Result<(usize, T), NoDataError> {
        if data.len() == 1 {
            println!("Length of data is one -> There is only one maximum.");
            Ok((0, data[0]))
        } else if  data.is_empty() {
            Err(NoDataError)
        } else {
            let mut next_index = 0;
            let mut old = &data[next_index];
            next_index += 1;
            let mut new = &data[next_index];
            while cmp_func(*old, new) {
                next_index += 1;
                match data.get(next_index) {
                    Some(x) => {
                        old = new;
                        new = x
                    }
                    None => {
                        println!("End of data reached at: {}", next_index);
                        break;
                    }
                };
            }
            Ok((next_index - 1, *old))
        }
    }

    #[derive(Clone, Copy, Debug)]
    /// Represents the desired content on a line of a *fourier.out* file.
    pub struct Line {
        /// The calculated intensity.
        pub intensity: f32,
        /// The corresponding energy.
        pub energy: f32,
        /// The index of the grid point.
        grid_point: usize,
    }

    impl Line {
        /// Returns a Line struct with the given intensity, energy at grid point.
        ///
        /// # Arguments
        ///
        /// * `intensity` - The calculated intensity at grid point `grid_point`
        /// * `energy` - The calculated energy at grid point `grid_point`
        /// * `grid_point` - The index of the grid point.
        ///   
        fn new(intensity: f32, energy: f32, grid_point: usize) -> Line {
            Line {
                intensity,
                energy,
                grid_point,
            }
        }
    }

    impl Default for Line {
        fn default() -> Self {
            Line {
                intensity: -10.0_f32,
                energy: -10e5_f32,
                grid_point: 0,
            }
        }
    }

    impl PartialEq for Line {
        fn eq(&self, other: &Self) -> bool {
            self.intensity == other.intensity
        }
    }

    impl PartialOrd for Line {
        fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
            self.intensity.partial_cmp(&other.intensity)
        }
    }

    impl std::fmt::Display for Line {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "intensity: {}  energy: {}  Line: {}",
                self.intensity, self.energy, self.grid_point
            )
        }
    }

    /// Represents the data in a *fourier.out* file generated by the VIBES program.
    #[derive(Clone)]
    pub struct FourierData {
        /// A Vector containing all the lines read from file.
        pub data: Vec<Line>,
        /// The maximal intensity read from the computed data.
        pub max_intensity: Line,
        /// The largest intensity increase for the read data.
        pub max_intensity_increase: Slope2D,
        data_index: usize,
    }

    impl FourierData {
        /// Returns FourierData struct generated by reading the *fourier.out* file.
        /// # Arguments
        ///
        /// * `fourier_file` - The *fourier.out* file generated by the VIBES program.
        /// * `intensity_threshold` - A threshold to discard intensities.
        ///
        /// # Examples
        ///
        /// ```
        /// # use std::io;
        /// use std::fs::File;
        /// use peak_finder::data::FourierData;
        /// # fn main() -> io::Result<()> {
        /// let fourier_file = File::open("tests/file_setup/test_fourier.out")?;
        /// let vibes_data = FourierData::new(fourier_file, 0.0); // Consider all data
        /// let fourier_file = File::open("tests/file_setup/test_fourier.out")?;
        /// // Consider data only if the intensity is greater than 10e5_f32
        /// let vibes_data = FourierData::new(fourier_file, 10e5_f32);
        /// # Ok(())}
        /// ```
        pub fn new(fourier_file: File, intensity_threshold: f32) -> Result<Self, Box<dyn Error>> {
            // Create a buffered reader for the file and read every line.
            let line_iter = io::BufReader::new(fourier_file).lines().map(|l| l.unwrap());
            let mut fourier_data: Vec<Line> = Vec::new();
            let mut max_intensity: f32 = -1.0_f32;
            let mut line_with_max_intensity = Line::default();
            for line in line_iter.enumerate() {
                let mut split_by_ws = line.1.split(' ');
                let energy: f32 = split_by_ws.nth(1).unwrap().parse::<f32>()?;
                let intensity: f32 = split_by_ws.next().unwrap().parse::<f32>()?;
                if intensity > intensity_threshold {
                    fourier_data.push(Line::new(intensity, energy, line.0));
                    if intensity > max_intensity {
                        line_with_max_intensity = Line::new(intensity, energy, line.0);
                        max_intensity = intensity;
                    };
                }
            }
            Ok(FourierData {
                data: fourier_data,
                max_intensity: line_with_max_intensity,
                data_index: 0,
                max_intensity_increase: Slope2D::default(),
            })
        }

        /// Returns a hash map filled with all local maxima present in data stored in FourierData.
        ///
        /// # Arguments
        ///
        /// * `fourier_data` - A FourierData struct to process.
        ///
        pub fn all_local_maxima(
            fourier_data: &Self,
        ) -> Result<HashMap<usize, (f32, f32)>, NoDataError> {
            let mut local_maxima = HashMap::new();
            let mut inflection: Line;
            // We can skip the first element, since I think it is per definition not an extremum.
            // Same should the be true for the last value.
            let mut start_index: usize = 1;
            let mut slice_index: usize;
            loop {
                match find_inflection_point(&fourier_data.data[start_index..], is_less) {
                Ok(t) => { (slice_index, inflection) = t },
                Err(e) => { return Err(e); }
                }
                    
                if let Some(x) = local_maxima.insert(
                    inflection.grid_point,
                    (inflection.energy, inflection.intensity),
                ) {
                    panic!(
                        "Duplicate found for: {} {} {}",
                        inflection.grid_point, x.0, x.1
                    )
                };
                start_index += slice_index + 1;
                match find_inflection_point(&fourier_data.data[start_index..], is_more) {
                Ok(t) => { slice_index = t.0 },
                Err(e) => { return Err(e); }
                }
                start_index += slice_index + 1;
                if start_index == fourier_data.data.len() {
                    break;
                }
            }
            Ok(local_maxima)
        }

        fn sum_intensities(fourier_data: Self) -> f32 {
            let mut sum = 0.0_f32;
            for (_e, i) in fourier_data {
                sum += i;
            }
            sum
        }

        pub fn mean_intensities(fourier_data: &Self) -> f32 {
            let sum = Self::sum_intensities(fourier_data.clone());
            sum / fourier_data.data.len() as f32
        }

        pub fn variance_intensities(fourier_data: Self, mean_fourier_data: f32) -> f32 {
            let mut variance = 0.0_f32;
            let number_elements = fourier_data.data.len() as f32;
            for (_e, i) in fourier_data {
                variance += (i - mean_fourier_data).powi(2);
            }
            variance / number_elements
        }

        pub fn mean_abs_error_intensities(fourier_data: Self, mean_fourier_data: f32) -> f32 {
            let mut mad = 0.0_f32;
            let number_elements = fourier_data.data.len() as f32;
            for (_e, i) in fourier_data {
                mad += (i - mean_fourier_data).abs();
            }
            mad / number_elements
        }

        pub fn shift_energies(fourier_data: &mut Self, shift_to_add: f32) {
            for mut line in fourier_data.data.iter_mut() {
                line.energy += shift_to_add;
            }
        }
    }

    impl Iterator for FourierData {
        type Item = (f32, f32);
        fn next(&mut self) -> Option<Self::Item> {
            if self.data_index < self.data.len() {
                let next = Some((
                    self.data[self.data_index].energy,
                    self.data[self.data_index].intensity,
                ));
                self.data_index += 1;
                next
            } else {
                None
            }
        }
    }
}