1
0
Files
LCRConnector/kernel/lcrconn/src/dataset.rs

446 lines
13 KiB
Rust
Raw Normal View History

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<Self, LcrConnError> {
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<DatasetItem>,
}
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<I, S>(str_values: I) -> Result<Self, LcrConnError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
// Check string form value one by one
let mut items: Vec<DatasetItem> = Vec::new();
let mut seen: HashSet<f64> = 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<Self, LcrConnError> {
let lines: Vec<String> = 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<Path>) -> Result<Self, LcrConnError> {
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, LcrConnError> {
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, LcrConnError> {
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, LcrConnError> {
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::<Vec<_>>()
.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<Path>) -> 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<Item = f64> + '_ {
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<I1, S1, I2, S2, I3, S3>(
resistor: I1,
capacitor: I2,
inductor: I3,
) -> Result<Self, LcrConnError>
where
I1: IntoIterator<Item = S1>,
S1: Into<String>,
I2: IntoIterator<Item = S2>,
S2: Into<String>,
I3: IntoIterator<Item = S3>,
S3: Into<String>,
{
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<Self, LcrConnError> {
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<Path>,
capacitor: impl AsRef<Path>,
inductor: impl AsRef<Path>,
) -> Result<Self, LcrConnError> {
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<Self, LcrConnError> {
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<Path>,
capacitor: impl AsRef<Path>,
inductor: impl AsRef<Path>,
) -> 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<f64, LcrConnError> {
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::<f64>()
.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),
}
}