use super::{
sink_names_input, source_names_output, BinBuildEnvironment,
BinDescription, Calculator, CalibrationSource, FetchItem,
GetCalibration, Item, Iteration, Result, Scope, SinkBin, SinkNames,
SourceBin, SourceId, SourceNames, SourceSinkBinDescription,
WriteDotSimple, SINK_INPUT, SOURCE_OUTPUT,
};
use crate::sortable_float::{HasDirection, HasReverse};
use crate::{error, R64};
use indexmap::{IndexMap, IndexSet};
static BIN_TYPE: &str = "calibration";
#[derive(Debug)]
pub struct Bin {
scope: Scope,
source_input: Box<dyn FetchItem>,
curve: IndexMap<R64, R64>,
result_output: Item,
}
impl SinkBin for Bin {}
impl SourceBin for Bin {
fn get_source_data(&self, source: &SourceId) -> Result<Item> {
if source.id == SOURCE_OUTPUT {
Ok(self.result_output.clone())
} else {
error::MissingSourceName {
scope: self.scope.clone(),
name: source.id.to_string(),
bin_type: BIN_TYPE.to_string(),
}
.fail()
}
}
}
impl Calculator for Bin {
fn calculate(&mut self, _iteration: &Iteration) -> Result<()> {
let input = self.source_input.fetch_item(&self.scope)?;
self.result_output = if let Ok(sv) = input.to_float() {
use std::iter::FromIterator;
let curve =
std::collections::BTreeMap::from_iter(self.curve.iter());
let mut before = curve.range(..sv);
let mut after = curve.range(sv..);
let (a, b) = match (before.next_back(), after.next()) {
(None, None) => error::InvalidCalibration {
scope: self.scope.clone(),
}
.fail()?,
(Some(b), None) => {
let a = before.next_back();
if let Some(a) = a {
(a, b)
} else {
error::InvalidCalibration {
scope: self.scope.clone(),
}
.fail()?
}
}
(None, Some(a)) => {
let b = after.next();
if let Some(b) = b {
(a, b)
} else {
error::InvalidCalibration {
scope: self.scope.clone(),
}
.fail()?
}
}
(Some(a), Some(b)) => (a, b),
};
let a: (f64, f64) =
((*a.0).clone().into(), (*a.1).clone().into());
let b: (f64, f64) =
((*b.0).clone().into(), (*b.1).clone().into());
calculate_intersection(sv, a, b)
} else {
Item::Nothing
};
Ok(())
}
}
fn calculate_intersection(
input: R64,
a: (f64, f64),
b: (f64, f64),
) -> Item {
let (ax, ay) = a;
let (bx, by) = b;
let (dx, dy) = ((bx - ax), (by - ay));
let offset = input - ax;
let value = R64::from(ay) + offset / dx * dy;
Item::from(value)
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Description {
pub details: CalibrationSource,
pub reversed: bool,
}
impl BinDescription for Description {
type Bin = Bin;
fn check_validity(
&self,
scope: &Scope,
get_calibration: &mut dyn GetCalibration,
) -> Result<()> {
let calibration = get_calibration.calibration(&self.details)?;
if self.reversed {
calibration.check_direction(scope, &self.details)
} else {
Ok(())
}
}
fn bin_type(&self) -> &'static str {
BIN_TYPE
}
}
impl SinkNames for Description {
fn sink_names(&self) -> IndexSet<String> {
sink_names_input()
}
}
impl SourceNames for Description {
fn source_names(&self) -> Result<IndexSet<String>> {
Ok(source_names_output())
}
}
impl SourceSinkBinDescription for Description {
fn build_bin(
&self,
scope: &Scope,
env: &mut dyn BinBuildEnvironment,
) -> Result<Self::Bin> {
let curve = if self.reversed {
env.calibration(&self.details)?.reversed()
} else {
env.calibration(&self.details)?
};
Ok(Bin {
scope: scope.clone(),
source_input: env.resolve(SINK_INPUT)?,
curve,
result_output: Item::Nothing,
})
}
}
impl WriteDotSimple for Description {}