use std::collections::HashSet; use std::path::Path; use crate::common::LcrConnError; /// An item in the dataset. #[derive(Clone, Debug)] pub struct DatasetItem { /// The actual value of this item. pub value: f64, /// The string form of this value given from original input for re-saving. pub str_value: String, } impl DatasetItem { /// Create a new dataset item with validation. /// /// # Errors /// /// Returns [`LcrConnError::InvalidDatasetValue`] if the value is not greater than 0. /// Returns [`LcrConnError::EmptyDatasetItem`] if the string value is empty. pub fn new(value: f64, str_value: String) -> Result { if value <= 0.0 { return Err(LcrConnError::InvalidDatasetValue(value)); } if str_value.is_empty() { return Err(LcrConnError::EmptyDatasetItem); } Ok(Self { value, str_value }) } } /// A list holding available standard values for resistor, capacitor or inductor. /// /// Standard values is a collection of all possible values of specific device manufactured /// by electronic factory. In reality, it also can be replaced by all possible values of /// specific device provided by your laboratory. For example, your laboratory only provide /// resistor with 100 Ohm and 4.7k Ohm. This list will only contain 100 and 4.7k. pub struct Dataset { /// A list of available device gauge values. items: Vec, } impl Dataset { /// Create a dataset from an iterable of stringified values. /// /// # Errors /// /// Returns [`LcrConnError::DuplicateDatasetItem`] if duplicate values are found. /// Returns [`LcrConnError::EmptyDataset`] if the iterable produces no items. /// Returns [`LcrConnError::InvalidHumanReadableValue`] if a value cannot be parsed. pub fn from_iterable(str_values: I) -> Result where I: IntoIterator, S: Into, { // Check string form value one by one let mut items: Vec = Vec::new(); let mut seen: HashSet = HashSet::new(); for str_value_raw in str_values { let str_value = str_value_raw.into(); // Try parsing value let value = from_human_readable_value(&str_value)?; // Check and update set if !seen.insert(value) { return Err(LcrConnError::DuplicateDatasetItem(str_value)); } // Add into result items.push(DatasetItem::new(value, str_value)?); } // Check empty case if items.is_empty() { return Err(LcrConnError::EmptyDataset); } // Ok, assign it Ok(Self { items }) } /// Load a dataset from a block of text. /// /// Each non-empty line (after trimming whitespace) is treated as a value. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn from_text(text: &str) -> Result { let lines: Vec = text .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) .collect(); Self::from_iterable(lines) } /// Load a dataset from a file. /// /// # Errors /// /// Returns [`LcrConnError::Io`] if the file cannot be read. /// See [`Dataset::from_iterable`] for other errors. pub fn from_file(path: impl AsRef) -> Result { let text = std::fs::read_to_string(path)?; Self::from_text(&text) } /// The preset dataset for resistors. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn resistor_preset() -> Result { Self::from_iterable([ "100", "220", "270", "390", "470", "680", "1k", "1.2k", "1.5k", "2.2k", "3.3k", "4.7k", "6.8k", "10k", "47k", "100k", "1M", ]) } /// The preset dataset for capacitors. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn capacitor_preset() -> Result { Self::from_iterable([ "10p", "22p", "33p", "47p", "68p", "100p", "150p", "220p", "330p", "470p", "560p", "1u", "2.2u", "3.3u", "4.7u", "10u", "22u", "47u", "100u", "220u", "470u", ]) } /// The preset dataset for inductors. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn inductor_preset() -> Result { Self::from_iterable([ "0.1u", "0.15u", "0.47u", "0.68u", "1u", "1.5u", "2.2u", "3.3u", "4.7u", "6.8u", "8.2u", "10u", "15u", "22u", "33u", "47u", "68u", "100u", ]) } /// Get the string form of all values joined by newlines. pub fn save_text(&self) -> String { self.items .iter() .map(|i| i.str_value.as_str()) .collect::>() .join("\n") } /// Save all values to a file. /// /// # Errors /// /// Returns [`LcrConnError::Io`] if the file cannot be written. pub fn save_file(&self, path: impl AsRef) -> Result<(), LcrConnError> { std::fs::write(path, self.save_text())?; Ok(()) } /// Get the available standard values as an iterator of `f64`. pub fn values(&self) -> impl Iterator + '_ { self.items.iter().map(|i| i.value) } /// Get the underlying dataset items as a slice. pub fn items(&self) -> &[DatasetItem] { &self.items } } /// The collection holding all standard values for resistor, capacitor and inductor respectively. pub struct DatasetCollection { /// A list of available device gauge values for resistor. resistor: Dataset, /// A list of available device gauge values for capacitor. capacitor: Dataset, /// A list of available device gauge values for inductor. inductor: Dataset, } impl DatasetCollection { pub fn new(resistor: Dataset, capacitor: Dataset, inductor: Dataset) -> Self { Self { resistor, capacitor, inductor, } } /// Load the standard values for resistor, capacitor and inductor respectively from iterables. /// /// # Arguments /// /// * `resistor` - The iterable to load available standard values for resistor. /// * `capacitor` - The iterable to load available standard values for capacitor. /// * `inductor` - The iterable to load available standard values for inductor. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn from_iterable( resistor: I1, capacitor: I2, inductor: I3, ) -> Result where I1: IntoIterator, S1: Into, I2: IntoIterator, S2: Into, I3: IntoIterator, S3: Into, { Ok(Self { resistor: Dataset::from_iterable(resistor)?, capacitor: Dataset::from_iterable(capacitor)?, inductor: Dataset::from_iterable(inductor)?, }) } /// Load the standard values from strings. /// /// # Arguments /// /// * `resistor` - The string to load available standard values for resistor. /// * `capacitor` - The string to load available standard values for capacitor. /// * `inductor` - The string to load available standard values for inductor. /// /// # Errors /// /// See [`Dataset::from_text`]. pub fn from_text(resistor: &str, capacitor: &str, inductor: &str) -> Result { Ok(Self { resistor: Dataset::from_text(resistor)?, capacitor: Dataset::from_text(capacitor)?, inductor: Dataset::from_text(inductor)?, }) } /// Load the standard values from files. /// /// # Arguments /// /// * `resistor` - The file to load available standard values for resistor. /// * `capacitor` - The file to load available standard values for capacitor. /// * `inductor` - The file to load available standard values for inductor. /// /// # Errors /// /// See [`Dataset::from_file`]. pub fn from_file( resistor: impl AsRef, capacitor: impl AsRef, inductor: impl AsRef, ) -> Result { Ok(Self { resistor: Dataset::from_file(resistor)?, capacitor: Dataset::from_file(capacitor)?, inductor: Dataset::from_file(inductor)?, }) } /// The preset dataset collection for all devices. /// /// # Errors /// /// See [`Dataset::from_iterable`]. pub fn devices_preset() -> Result { Ok(Self { resistor: Dataset::resistor_preset()?, capacitor: Dataset::capacitor_preset()?, inductor: Dataset::inductor_preset()?, }) } /// Get the string form of all values. /// /// # Returns /// /// A tuple of strings for resistor, capacitor and inductor respectively. pub fn save_text(&self) -> (String, String, String) { ( self.resistor.save_text(), self.capacitor.save_text(), self.inductor.save_text(), ) } /// Save all values to files. /// /// # Arguments /// /// * `resistor` - The file to save available standard values for resistor. /// * `capacitor` - The file to save available standard values for capacitor. /// * `inductor` - The file to save available standard values for inductor. /// /// # Errors /// /// Returns [`LcrConnError::Io`] if any file cannot be written. pub fn save_file( &self, resistor: impl AsRef, capacitor: impl AsRef, inductor: impl AsRef, ) -> Result<(), LcrConnError> { self.resistor.save_file(resistor)?; self.capacitor.save_file(capacitor)?; self.inductor.save_file(inductor)?; Ok(()) } /// Get the dataset for resistor. pub fn resistor(&self) -> &Dataset { &self.resistor } /// Get the dataset for capacitor. pub fn capacitor(&self) -> &Dataset { &self.capacitor } /// Get the dataset for inductor. pub fn inductor(&self) -> &Dataset { &self.inductor } } /// Convert human readable value to float. /// /// # Arguments /// /// * `strl` - The human readable value. /// /// # Returns /// /// The parsed float value. /// /// # Errors /// /// Returns [`LcrConnError::InvalidHumanReadableValue`] if the input string is not a valid number. pub fn from_human_readable_value(strl: &str) -> Result { let strl = strl.trim(); let (num_part, multiplier) = if let Some(stripped) = strl.strip_suffix('n') { (stripped, 1e-12) } else if let Some(stripped) = strl.strip_suffix('p') { (stripped, 1e-9) } else if let Some(stripped) = strl.strip_suffix('u') { (stripped, 1e-6) } else if let Some(stripped) = strl.strip_suffix('m') { (stripped, 1e-3) } else if let Some(stripped) = strl.strip_suffix('k') { (stripped, 1e3) } else if let Some(stripped) = strl.strip_suffix('M') { (stripped, 1e6) } else if let Some(stripped) = strl.strip_suffix('G') { (stripped, 1e9) } else { (strl, 1.0) }; num_part .parse::() .map(|v| v * multiplier) .map_err(|_| LcrConnError::InvalidHumanReadableValue(strl.to_string())) } /// The unit scale for human readable value. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnitScale { NanoLower, Nano, Micro, Milli, None, Kilo, Mega, Giga, GigaHigher, } /// Get the unit scale of human readable value. /// /// # Arguments /// /// * `v` - The value. /// /// # Returns /// /// The unit scale. pub fn get_human_readable_value_scale(v: f64) -> UnitScale { let v = v.abs(); if v < 1e-12 { UnitScale::NanoLower } else if v < 1e-9 { UnitScale::Nano } else if v < 1e-6 { UnitScale::Micro } else if v < 1e-3 { UnitScale::Milli } else if v < 1e3 { UnitScale::None } else if v < 1e6 { UnitScale::Kilo } else if v < 1e9 { UnitScale::Mega } else if v < 1e12 { UnitScale::Giga } else { UnitScale::GigaHigher } } /// Convert float value to human readable value. /// /// # Arguments /// /// * `v` - The float value. /// /// # Returns /// /// The human readable value. pub fn to_human_readable_value(v: f64) -> String { let scale = get_human_readable_value_scale(v); match scale { UnitScale::NanoLower => format!("{:+.4e} n", v / 1e-12), UnitScale::Nano => format!("{:+.4f} p", v / 1e-9), UnitScale::Micro => format!("{:+.4f} u", v / 1e-6), UnitScale::Milli => format!("{:+.4f} m", v / 1e-3), UnitScale::None => { // YYC MARK: // The space of this format string is by design // for keeping the same style with other format strings. format!("{:+.4} ", v) } UnitScale::Kilo => format!("{:+.4f} k", v / 1e3), UnitScale::Mega => format!("{:+.4f} M", v / 1e6), UnitScale::Giga => format!("{:+.4f} G", v / 1e9), UnitScale::GigaHigher => format!("{:+.4e} G", v / 1e9), } }