446 lines
13 KiB
Rust
446 lines
13 KiB
Rust
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),
|
|
}
|
|
}
|