arnalisa 0.6.8

Pipeline system for calculating values
Documentation
//! A bin that passes through all input values.
//!
//! ```text
//!   ┌────[passthrough]────┐
//!  ⇒│<input 0>  <output 0>│
//!  ⇒│<input 1>  <output 1>│
//!  ⇒│<input 2>  <output 2>│
//!  ⇒┊…                    ┊
//!  ⇒│<input n>  <output n>│
//!   └─────────────────────┘
//! ```

use super::{
    BinBuildEnvironment, BinDescription, Calculator, FetchItem,
    GetCalibration, Item, Iteration, Result, Scope, SinkBin, SinkNames,
    SourceBin, SourceId, SourceNames, SourceSinkBinDescription,
    WriteDotSimple,
};
use crate::error;
use indexmap::{IndexMap, IndexSet};
use snafu::OptionExt;

static BIN_TYPE: &str = "passthrough";

/// A bin that passes through all input values.
#[derive(Debug)]
pub struct Bin {
    scope: Scope,
    inputs: IndexMap<String, Box<dyn FetchItem>>,
    outputs: IndexMap<String, Item>,
}

impl SinkBin for Bin {}

impl SourceBin for Bin {
    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
        self.outputs
            .get(&source.id)
            .context(error::MissingSourceName {
                scope: self.scope.clone(),
                name: source.id.to_string(),
                bin_type: BIN_TYPE.to_string(),
            })
            .map(Clone::clone)
    }
}

impl Calculator for Bin {
    fn calculate(&mut self, _iteration: &Iteration) -> Result<()> {
        self.outputs = self
            .inputs
            .iter()
            .map(|(s, ds)| {
                ds.fetch_item(&self.scope).map(|d| (s.to_string(), d))
            })
            .collect::<Result<IndexMap<String, Item>>>()?;
        Ok(())
    }
}

/// Description of the passthrough bin.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Description {
    /// The set of sourcs that gets passed through.
    pub inputs: IndexSet<String>,
}

impl BinDescription for Description {
    type Bin = Bin;

    fn check_validity(
        &self,
        _scope: &Scope,
        _get_calibration: &mut dyn GetCalibration,
    ) -> Result<()> {
        Ok(())
    }

    fn bin_type(&self) -> &'static str {
        BIN_TYPE
    }
}

impl SinkNames for Description {
    fn sink_names(&self) -> IndexSet<String> {
        self.inputs.clone()
    }
}

impl SourceNames for Description {
    fn source_names(&self) -> Result<IndexSet<String>> {
        Ok(self.inputs.clone())
    }
}

impl SourceSinkBinDescription for Description {
    fn build_bin(
        &self,
        scope: &Scope,
        env: &mut dyn BinBuildEnvironment,
    ) -> Result<Self::Bin> {
        let inputs = self
            .inputs
            .iter()
            .map(|input| {
                env.resolve(input).map(|sink| (input.to_string(), sink))
            })
            .collect::<Result<IndexMap<String, Box<dyn FetchItem>>>>()?;
        let outputs = self
            .inputs
            .iter()
            .map(|k| (k.to_string(), Item::Nothing))
            .collect();
        Ok(Bin {
            scope: scope.clone(),
            inputs,
            outputs,
        })
    }
}

impl WriteDotSimple for Description {}

#[cfg(test)]
mod tests {
    use super::Description;
    use crate::bins::{directsource, verificationsink};
    use crate::Item as I;
    use crate::{run_bin, Result};
    use indexmap::indexset;

    #[test]
    fn simulate() -> Result<()> {
        use crate::Item::*;

        let rows = vec![
            vec![Nothing, Nothing],
            vec![Nothing, I::from(1f64)],
            vec![I::from(1f64), Nothing],
            vec![I::from(1f64), I::from(1f64)],
            vec![I::from(0f64), I::from(1f64)],
            vec![I::from(1f64), I::from(0f64)],
            vec![I::from(0f64), I::from(0f64)],
            vec![I::from(2f64), I::from(0f64)],
            vec![I::from(1f64), I::from(2f64)],
            vec![I::from(2f64), I::from(1f64)],
            vec![I::from(2f64), I::from(2f64)],
        ];

        let input = directsource::Description {
            columns: indexset!["a".to_string(), "b".to_string()],
            rows: rows.clone().into(),
        };
        let verification = verificationsink::Description {
            columns: indexset!["a".to_string(), "b".to_string()],
            expected: rows.clone().into(),
        };

        run_bin(
            &input,
            &Description {
                inputs: indexset! {"a".to_string(),"b".to_string()},
            },
            &verification,
        )
    }
}