arnalisa 0.6.8

Pipeline system for calculating values
Documentation
//! Pipeline system for calculating values.

#![deny(
    missing_docs,
    missing_debug_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

#[macro_use]
extern crate serde_derive;

use snafu;

pub mod bins;
pub mod error;

mod item;
mod run;
mod sortable_float;

pub use self::item::Item;
pub use crate::error::Error;
pub use crate::run::{run_bin, run_bin_with_calibration};
pub use crate::sortable_float::{Direction, HasDirection, HasReverse};
pub use decorum::{R32, R64};

use indexmap::IndexMap;
use snafu::OptionExt;
use std::fmt;

/// The scope of a bin.
#[derive(Clone)]
pub enum Scope {
    /// The top level of a scope, created in-memory.
    Base,
    /// Loaded from a file.
    File(String),
    /// Nested insid a parent scope.
    Sub {
        /// The parent scope.
        parent: Box<Scope>,
        /// The name of the child.
        name: String,
    },
}

impl Default for Scope {
    fn default() -> Self {
        Scope::Base
    }
}

impl Scope {
    /// Produce a child scope.
    pub fn enter<N: AsRef<str>>(&self, name: N) -> Self {
        Scope::Sub {
            parent: Box::new(self.clone()),
            name: name.as_ref().to_string(),
        }
    }
}

impl fmt::Debug for Scope {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Scope::Base => write!(f, "in-memory"),
            Scope::File(ref file) => write!(f, "file \"{}\"", file),
            Scope::Sub {
                ref parent,
                ref name,
            } => write!(f, "{:?}\"{}\"", parent, name),
        }
    }
}

/// The result type used throughout this crate.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// The source of a calibration.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "source", content = "data")]
pub enum CalibrationSource {
    /// The calibration gets loaded from a file.
    File {
        /// The path of the calibration definition file.
        path: String,
    },
    /// The calibration is directly defined in the definition.
    Embedded {
        /// The values of the calibration.
        curve: Vec<(f64, f64)>,
    },
    /// The calibration is found by an identifier.
    Identifier {
        /// The identifier of the calibration.
        id: String,
    },
}

/// An indicator on whether to stop or continue processing.
#[derive(PartialEq, Debug, Clone)]
#[must_use]
pub enum Proceed {
    /// Stop processing.
    Stop,
    /// Continue processing.
    Continue,
}

/// A trait for getting calibrations by a calibration source description.
pub trait GetCalibration {
    /// Get the calibration according to it's source description.
    fn calibration(
        &mut self,
        source: &CalibrationSource,
    ) -> Result<IndexMap<R64, R64>>;
}

/// A trait describing a single calibration.
pub trait IsCalibration {
    /// Get the calibration data.
    fn calibration(&self) -> Result<IndexMap<R64, R64>>;
}

impl<T: IsCalibration> GetCalibration for IndexMap<String, T> {
    fn calibration(
        &mut self,
        mode: &CalibrationSource,
    ) -> Result<IndexMap<R64, R64>> {
        match *mode {
            CalibrationSource::File { .. } => error::GetCalibration {
                message: "IndexMap of calibrations cannot \
                          get calibration from file"
                    .to_string(),
            }
            .fail(),
            CalibrationSource::Embedded { ref curve } => Ok(curve
                .iter()
                .map(|&(x, y)| (R64::from(x), R64::from(y)))
                .collect::<IndexMap<R64, R64>>()),
            CalibrationSource::Identifier { ref id } => Ok(self
                .get(id)
                .context(error::GetCalibration {
                    message: format!("Calibration {:?} not present.", id),
                })?
                .calibration()?),
        }
    }
}

type DateTime = ::chrono::DateTime<::chrono::offset::Utc>;

/// A single point inside a calibration.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CalibrationPoint {
    /// The coordinates of the point.
    pub coordinates: (f64, f64),
    /// The results associated with the point, e.g. calculated or added
    /// when the point was measured.
    ///
    /// This might be e.g. the weight that was applied to the measurement
    /// cell during calibration.
    ///
    /// It has no relevance for calculation of calibration values, but
    /// is only for informational purposes.
    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
    pub results: IndexMap<String, Item>,
}

/// A calibration.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Calibration {
    /// The points inside the calibration.
    pub points: Vec<CalibrationPoint>,
    /// The timestamp of the moment when the calibration was last modified.
    pub timestamp: DateTime,
}

impl Calibration {
    /// Sort the points by their x coordinate.
    pub fn sort_points(&mut self) {
        self.points.sort_by(|a, b| {
            a.coordinates.0.partial_cmp(&b.coordinates.0).unwrap()
        });
    }
}

impl IsCalibration for Calibration {
    fn calibration(&self) -> Result<IndexMap<R64, R64>> {
        let mut p = self
            .points
            .iter()
            .map(|p| {
                let (x, y) = p.coordinates;
                (R64::from(x), R64::from(y))
            })
            .collect::<IndexMap<R64, R64>>();
        p.sort_keys();
        Ok(p)
    }
}