extern crate std;
use std::collections::BTreeMap;
use std::prelude::v1::*;
use super::family::{
FamilyGapDef, FamilyMemberDef, SelectorValue, TransferFamilyDef, default_family_max_knots,
};
use super::model::{DividerTopology, MODEL_INPUT_SCALE_DENOMINATOR, ModelDef};
use super::{
BoundaryDef, GenerationPolicy, ObservationGuardDef, PhysicalPoint, SourceProvenance,
TransferDef, default_boundary,
};
fn default_max_knots() -> usize {
256
}
#[derive(Clone, Debug)]
pub struct EvaluatedTruth {
domain_min: u16,
physical: Vec<f64>,
}
impl EvaluatedTruth {
pub fn new(domain_min: u16, physical: Vec<f64>) -> Self {
Self {
domain_min,
physical,
}
}
pub fn domain_min(&self) -> u16 {
self.domain_min
}
pub fn physical(&self) -> &[f64] {
&self.physical
}
}
#[derive(Clone, Debug)]
pub enum TransferSource {
EvaluatedTruth(EvaluatedTruth),
PrefittedKnots {
inputs: Vec<u16>,
outputs: Vec<i32>,
truth: EvaluatedTruth,
},
Points(Vec<PhysicalPoint>),
}
impl TransferSource {
pub fn evaluated_truth(domain_min: u16, physical: Vec<f64>) -> Self {
Self::EvaluatedTruth(EvaluatedTruth::new(domain_min, physical))
}
pub fn prefitted_knots_verified(
inputs: Vec<u16>,
outputs: Vec<i32>,
truth: EvaluatedTruth,
) -> Self {
Self::PrefittedKnots {
inputs,
outputs,
truth,
}
}
pub fn points(points: Vec<PhysicalPoint>) -> Self {
Self::Points(points)
}
pub fn inherit_provenance(self) -> TransferSourceOverlay {
TransferSourceOverlay::new(self, SourceProvenanceDisposition::Inherit)
}
pub fn with_provenance(self, provenance: SourceProvenance) -> TransferSourceOverlay {
TransferSourceOverlay::new(self, SourceProvenanceDisposition::Replace(provenance))
}
pub fn clear_provenance(self) -> TransferSourceOverlay {
TransferSourceOverlay::new(self, SourceProvenanceDisposition::Clear)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SourceProvenanceDisposition {
Inherit,
Replace(SourceProvenance),
Clear,
}
#[derive(Clone, Debug)]
pub struct TransferSourceOverlay {
source: TransferSource,
provenance: SourceProvenanceDisposition,
}
impl TransferSourceOverlay {
fn new(source: TransferSource, provenance: SourceProvenanceDisposition) -> Self {
Self { source, provenance }
}
pub fn source(&self) -> &TransferSource {
&self.source
}
pub fn provenance_disposition(&self) -> &SourceProvenanceDisposition {
&self.provenance
}
pub(crate) fn effective_def(&self, def: &TransferDef) -> Result<TransferDef, String> {
let mut effective = def.clone();
match &self.provenance {
SourceProvenanceDisposition::Inherit => {}
SourceProvenanceDisposition::Replace(provenance) => {
provenance.validate()?;
effective.provenance = Some(provenance.clone());
}
SourceProvenanceDisposition::Clear => effective.provenance = None,
}
Ok(effective)
}
}
#[derive(Clone, Debug)]
pub struct TransferSpec {
name: String,
input_unit: String,
output_unit: String,
output_scale: u32,
max_interpolation_error: u32,
max_knots: usize,
below: BoundaryDef,
above: BoundaryDef,
observation_guard: Option<ObservationGuardDef>,
provenance: Option<SourceProvenance>,
source: TransferSource,
}
impl TransferSpec {
pub fn new(
name: impl Into<String>,
input_unit: impl Into<String>,
output_unit: impl Into<String>,
output_scale: u32,
max_interpolation_error: u32,
source: TransferSource,
) -> Self {
Self {
name: name.into(),
input_unit: input_unit.into(),
output_unit: output_unit.into(),
output_scale,
max_interpolation_error,
max_knots: default_max_knots(),
below: default_boundary(),
above: default_boundary(),
observation_guard: None,
provenance: None,
source,
}
}
pub fn with_max_knots(mut self, max_knots: usize) -> Self {
self.max_knots = max_knots;
self
}
pub fn with_boundaries(mut self, below: BoundaryDef, above: BoundaryDef) -> Self {
self.below = below;
self.above = above;
self
}
pub fn with_observation_guard(mut self, guard: ObservationGuardDef) -> Self {
self.observation_guard = Some(guard);
self
}
pub fn with_provenance(mut self, provenance: SourceProvenance) -> Self {
self.provenance = Some(provenance);
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn input_unit(&self) -> &str {
&self.input_unit
}
pub fn output_unit(&self) -> &str {
&self.output_unit
}
pub fn output_scale(&self) -> u32 {
self.output_scale
}
pub fn max_interpolation_error(&self) -> u32 {
self.max_interpolation_error
}
pub fn max_knots(&self) -> usize {
self.max_knots
}
pub fn below(&self) -> BoundaryDef {
self.below
}
pub fn above(&self) -> BoundaryDef {
self.above
}
pub fn observation_guard(&self) -> Option<&ObservationGuardDef> {
self.observation_guard.as_ref()
}
pub fn provenance(&self) -> Option<&SourceProvenance> {
self.provenance.as_ref()
}
pub fn policy(&self) -> GenerationPolicy {
GenerationPolicy::new(
self.max_interpolation_error,
self.max_knots,
self.below,
self.above,
self.observation_guard.as_ref(),
)
}
pub fn source(&self) -> &TransferSource {
&self.source
}
pub(crate) fn into_parts(self) -> (String, TransferDef, TransferSource) {
let def = TransferDef {
input_unit: self.input_unit,
output_unit: self.output_unit,
output_scale: self.output_scale,
max_interpolation_error: self.max_interpolation_error,
max_knots: self.max_knots,
below: self.below,
above: self.above,
observation_guard: self.observation_guard,
provenance: self.provenance,
resolved_guard_provenance: None,
points: None,
formula: None,
model: None,
domain: None,
output_range: None,
};
(self.name, def, self.source)
}
}
#[derive(Clone, Debug)]
pub enum FamilySource {
Formula(String),
Points(Vec<PhysicalPoint>),
ScaledPolynomial {
coefficients: Vec<f64>,
},
NtcBetaDivider {
nominal_resistance_ohms: f64,
beta_kelvin: f64,
nominal_temperature_celsius: f64,
fixed_resistance_ohms: f64,
adc_max_code: u16,
topology: DividerTopology,
},
}
impl PartialEq for FamilySource {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Formula(left), Self::Formula(right)) => left == right,
(Self::Points(left), Self::Points(right)) => {
left.len() == right.len()
&& left.iter().zip(right).all(|(left, right)| {
left.input == right.input && left.output == right.output
})
}
(
Self::ScaledPolynomial { coefficients: left },
Self::ScaledPolynomial {
coefficients: right,
},
) => left == right,
(
Self::NtcBetaDivider {
nominal_resistance_ohms: left_nominal_resistance_ohms,
beta_kelvin: left_beta_kelvin,
nominal_temperature_celsius: left_nominal_temperature_celsius,
fixed_resistance_ohms: left_fixed_resistance_ohms,
adc_max_code: left_adc_max_code,
topology: left_topology,
},
Self::NtcBetaDivider {
nominal_resistance_ohms: right_nominal_resistance_ohms,
beta_kelvin: right_beta_kelvin,
nominal_temperature_celsius: right_nominal_temperature_celsius,
fixed_resistance_ohms: right_fixed_resistance_ohms,
adc_max_code: right_adc_max_code,
topology: right_topology,
},
) => {
left_nominal_resistance_ohms == right_nominal_resistance_ohms
&& left_beta_kelvin == right_beta_kelvin
&& left_nominal_temperature_celsius == right_nominal_temperature_celsius
&& left_fixed_resistance_ohms == right_fixed_resistance_ohms
&& left_adc_max_code == right_adc_max_code
&& left_topology == right_topology
}
_ => false,
}
}
}
impl FamilySource {
pub fn formula(text: impl Into<String>) -> Self {
Self::Formula(text.into())
}
pub fn points(points: Vec<PhysicalPoint>) -> Self {
Self::Points(points)
}
pub fn scaled_polynomial(coefficients: Vec<f64>) -> Self {
Self::ScaledPolynomial { coefficients }
}
pub fn ntc_beta_divider(
nominal_resistance_ohms: f64,
beta_kelvin: f64,
nominal_temperature_celsius: f64,
fixed_resistance_ohms: f64,
adc_max_code: u16,
topology: DividerTopology,
) -> Self {
Self::NtcBetaDivider {
nominal_resistance_ohms,
beta_kelvin,
nominal_temperature_celsius,
fixed_resistance_ohms,
adc_max_code,
topology,
}
}
pub(crate) fn from_family(family: &TransferFamilyDef) -> Option<Self> {
match (&family.formula, &family.points, &family.model) {
(Some(formula), None, None) => Some(Self::Formula(formula.clone())),
(None, Some(points), None) => Some(Self::Points(points.clone())),
(None, None, Some(ModelDef::ScaledPolynomial { coefficients, .. })) => {
Some(Self::ScaledPolynomial {
coefficients: coefficients.clone(),
})
}
(
None,
None,
Some(ModelDef::NtcBetaDivider {
nominal_resistance_ohms,
beta_kelvin,
nominal_temperature_celsius,
fixed_resistance_ohms,
adc_max_code,
topology,
}),
) => Some(Self::NtcBetaDivider {
nominal_resistance_ohms: *nominal_resistance_ohms,
beta_kelvin: *beta_kelvin,
nominal_temperature_celsius: *nominal_temperature_celsius,
fixed_resistance_ohms: *fixed_resistance_ohms,
adc_max_code: *adc_max_code,
topology: *topology,
}),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct FamilySpec {
name: String,
input_unit: String,
output_unit: String,
output_scale: u32,
max_interpolation_error: u32,
max_knots: usize,
max_total_knots: Option<usize>,
max_table_bytes: Option<usize>,
below: BoundaryDef,
above: BoundaryDef,
observation_guard: Option<ObservationGuardDef>,
provenance: SourceProvenance,
source: FamilySource,
selector_axes: Option<BTreeMap<String, Vec<SelectorValue>>>,
expected_selectors: Option<Vec<BTreeMap<String, SelectorValue>>>,
members: Vec<FamilyMemberDef>,
gaps: Vec<FamilyGapDef>,
}
impl FamilySpec {
pub fn new(
name: impl Into<String>,
input_unit: impl Into<String>,
output_unit: impl Into<String>,
output_scale: u32,
max_interpolation_error: u32,
source: FamilySource,
provenance: SourceProvenance,
) -> Self {
Self {
name: name.into(),
input_unit: input_unit.into(),
output_unit: output_unit.into(),
output_scale,
max_interpolation_error,
max_knots: default_family_max_knots(),
max_total_knots: None,
max_table_bytes: None,
below: default_boundary(),
above: default_boundary(),
observation_guard: None,
provenance,
source,
selector_axes: None,
expected_selectors: None,
members: Vec::new(),
gaps: Vec::new(),
}
}
pub fn with_max_knots(mut self, max_knots: usize) -> Self {
self.max_knots = max_knots;
self
}
pub fn with_boundaries(mut self, below: BoundaryDef, above: BoundaryDef) -> Self {
self.below = below;
self.above = above;
self
}
pub fn with_observation_guard(mut self, guard: ObservationGuardDef) -> Self {
self.observation_guard = Some(guard);
self
}
pub fn with_max_total_knots(mut self, max_total_knots: usize) -> Self {
self.max_total_knots = Some(max_total_knots);
self
}
pub fn with_max_table_bytes(mut self, max_table_bytes: usize) -> Self {
self.max_table_bytes = Some(max_table_bytes);
self
}
pub fn with_selector_axes(mut self, axes: BTreeMap<String, Vec<SelectorValue>>) -> Self {
self.selector_axes = Some(axes);
self
}
pub fn with_expected_selectors(
mut self,
identities: Vec<BTreeMap<String, SelectorValue>>,
) -> Self {
self.expected_selectors = Some(identities);
self
}
pub fn with_members(mut self, members: Vec<FamilyMemberDef>) -> Self {
self.members = members;
self
}
pub fn with_gaps(mut self, gaps: Vec<FamilyGapDef>) -> Self {
self.gaps = gaps;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn input_unit(&self) -> &str {
&self.input_unit
}
pub fn output_unit(&self) -> &str {
&self.output_unit
}
pub fn output_scale(&self) -> u32 {
self.output_scale
}
pub fn max_interpolation_error(&self) -> u32 {
self.max_interpolation_error
}
pub fn max_knots(&self) -> usize {
self.max_knots
}
pub fn max_total_knots(&self) -> Option<usize> {
self.max_total_knots
}
pub fn max_table_bytes(&self) -> Option<usize> {
self.max_table_bytes
}
pub fn below(&self) -> BoundaryDef {
self.below
}
pub fn above(&self) -> BoundaryDef {
self.above
}
pub fn observation_guard(&self) -> Option<&ObservationGuardDef> {
self.observation_guard.as_ref()
}
pub fn provenance(&self) -> &SourceProvenance {
&self.provenance
}
pub fn policy(&self) -> GenerationPolicy {
GenerationPolicy::new(
self.max_interpolation_error,
self.max_knots,
self.below,
self.above,
self.observation_guard.as_ref(),
)
}
pub fn source(&self) -> &FamilySource {
&self.source
}
pub fn members(&self) -> &[FamilyMemberDef] {
&self.members
}
pub fn gaps(&self) -> &[FamilyGapDef] {
&self.gaps
}
pub(crate) fn into_family(self) -> (String, TransferFamilyDef) {
let (formula, points, model) = match self.source {
FamilySource::Formula(text) => (Some(text), None, None),
FamilySource::Points(points) => (None, Some(points), None),
FamilySource::ScaledPolynomial { coefficients } => (
None,
None,
Some(ModelDef::ScaledPolynomial {
coefficients,
scale: None,
denominator: MODEL_INPUT_SCALE_DENOMINATOR as u32,
}),
),
FamilySource::NtcBetaDivider {
nominal_resistance_ohms,
beta_kelvin,
nominal_temperature_celsius,
fixed_resistance_ohms,
adc_max_code,
topology,
} => (
None,
None,
Some(ModelDef::NtcBetaDivider {
nominal_resistance_ohms,
beta_kelvin,
nominal_temperature_celsius,
fixed_resistance_ohms,
adc_max_code,
topology,
}),
),
};
let family = TransferFamilyDef {
input_unit: self.input_unit,
output_unit: self.output_unit,
output_scale: self.output_scale,
max_interpolation_error: self.max_interpolation_error,
max_knots: self.max_knots,
max_total_knots: self.max_total_knots,
max_table_bytes: self.max_table_bytes,
below: self.below,
above: self.above,
observation_guard: self.observation_guard,
provenance: Some(self.provenance),
points,
formula,
model,
selector_axes: self.selector_axes,
expected_selectors: self.expected_selectors,
members: self.members,
gaps: self.gaps,
};
(self.name, family)
}
}