diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index cf3a1d9..4619af4 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "clap" version = "4.6.1" @@ -98,6 +104,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "heck" version = "0.5.0" @@ -110,10 +122,21 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "lcrconn" version = "1.0.0" dependencies = [ + "itertools", + "ordered-float", "strum", "strum_macros", "thiserror", @@ -128,12 +151,30 @@ dependencies = [ "thiserror", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + [[package]] name = "proc-macro2" version = "1.0.106" diff --git a/kernel/lcrconn/Cargo.toml b/kernel/lcrconn/Cargo.toml index 9a58fcc..a204f42 100644 --- a/kernel/lcrconn/Cargo.toml +++ b/kernel/lcrconn/Cargo.toml @@ -5,5 +5,7 @@ edition = "2024" [dependencies] thiserror = { workspace = true } +ordered-float = "=5.3.0" +itertools = "0.15.0" strum = "=0.28.0" strum_macros = "=0.28.0" diff --git a/kernel/lcrconn/src/dataset.rs b/kernel/lcrconn/src/dataset.rs index fefd42d..8257836 100644 --- a/kernel/lcrconn/src/dataset.rs +++ b/kernel/lcrconn/src/dataset.rs @@ -1,35 +1,51 @@ -use thiserror::Error as TeError; +use crate::common::{ + DeviceValueError, FloatingPointError, sanitize_device_value, sanitize_floating_point, +}; +use ordered_float::OrderedFloat; use std::collections::HashSet; +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Error as IoError, Write}; +use std::num::ParseFloatError; use std::path::Path; +use thiserror::Error as TeError; /// Error occurs when building dataset. #[derive(Debug, TeError)] pub enum DatasetError { - + #[error("invalid device value {0} in dataset item")] + BadDeviceValue(#[from] DeviceValueError), + #[error("unexpected empty string in dataset item")] + BlankDeviceValue, + #[error("bad string form of device value: {0}")] + ParseHumanReadableValue(#[from] ParseHumanReadableValueError), + #[error("duplicate item {0} in standard value list")] + DupDatasetItem(String), + #[error("unexpected empty standard value list")] + EmptyDataset, + #[error("fail to open dataset file: {0}")] + OpenDatasetFile(IoError), + #[error("fail to read dataset file: {0}")] + ReadDatasetFile(IoError), + #[error("fail to write dataset file: {0}")] + WriteDatasetFile(IoError), } /// An item in the dataset. -#[derive(Clone, Debug)] -pub struct DatasetItem { +#[derive(Debug, Clone)] +struct DatasetItem { /// The actual value of this item. - pub value: f64, + value: f64, /// The string form of this value given from original input for re-saving. - pub str_value: String, + str_value: String, } impl DatasetItem { /// Create a new dataset item with validation. - /// - /// # Errors - /// - /// Returns [`DatasetError::InvalidDatasetValue`] if the value is not greater than 0. - /// Returns [`DatasetError::EmptyDatasetItem`] if the string value is empty. - pub fn new(value: f64, str_value: String) -> Result { - if value <= 0.0 { - return Err(DatasetError::InvalidDatasetValue(value)); - } + fn new(value: f64, str_value: String) -> Result { + // Check arguments + let value = sanitize_device_value(value)?; if str_value.is_empty() { - return Err(DatasetError::EmptyDatasetItem); + return Err(DatasetError::BlankDeviceValue); } Ok(Self { value, str_value }) } @@ -47,29 +63,21 @@ pub struct Dataset { } impl Dataset { - /// Create a dataset from an iterable of stringified values. - /// - /// # Errors - /// - /// Returns [`DatasetError::DuplicateDatasetItem`] if duplicate values are found. - /// Returns [`DatasetError::EmptyDataset`] if the iterable produces no items. - /// Returns [`DatasetError::InvalidHumanReadableValue`] if a value cannot be parsed. - pub fn from_iterable(str_values: I) -> Result + /// Internal used generic dataset creation function. + fn new(str_values: I) -> Result where - I: IntoIterator, - S: Into, + I: IntoIterator, { // Check string form value one by one let mut items: Vec = Vec::new(); - let mut seen: HashSet = HashSet::new(); + let mut seen: HashSet> = HashSet::new(); - for str_value_raw in str_values { - let str_value = str_value_raw.into(); + for str_value in str_values { // Try parsing value let value = from_human_readable_value(&str_value)?; // Check and update set - if !seen.insert(value) { - return Err(DatasetError::DuplicateDatasetItem(str_value)); + if !seen.insert(OrderedFloat(value)) { + return Err(DatasetError::DupDatasetItem(str_value.to_string())); } // Add into result items.push(DatasetItem::new(value, str_value)?); @@ -84,97 +92,104 @@ impl Dataset { Ok(Self { items }) } + /// Create a dataset from an iterable of string values. + pub fn from_iterator(str_values: I) -> Result + where + I: IntoIterator, + S: Into, + { + Self::new(str_values.into_iter().map(|i| i.into())) + } + /// 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 + let lines = text .lines() .map(|line| line.trim().to_string()) - .filter(|line| !line.is_empty()) - .collect(); - Self::from_iterable(lines) + .filter(|line| !line.is_empty()); + Self::from_iterator(lines) } /// Load a dataset from a file. /// - /// # Errors - /// - /// Returns [`DatasetError::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) + /// Each non-empty line (after trimming whitespace) is treated as a value. + pub fn from_file

(path: P) -> Result + where + P: AsRef, + { + let file = File::open(path).map_err(|err| DatasetError::OpenDatasetFile(err))?; + let reader = BufReader::new(file); + let lines = reader + .lines() + .map(|line| line.map(|line| line.trim().to_string())) + .filter(|line| !matches!(line, Ok(line) if line.is_empty())) + .collect::, _>>() + .map_err(|err| DatasetError::ReadDatasetFile(err))?; + Self::from_iterator(lines.into_iter()) } /// 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", + Self::from_iterator([ + "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([ + Self::from_iterator([ "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([ + Self::from_iterator([ "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") + fn save(&self) -> impl Iterator { + self.items.iter().map(|i| i.str_value.as_str()) } - /// Save all values to a file. - /// - /// # Errors - /// - /// Returns [`DatasetError::Io`] if the file cannot be written. - pub fn save_file(&self, path: impl AsRef) -> Result<(), DatasetError> { - std::fs::write(path, self.save_text())?; + /// Get the string form of all values one by one for saving + pub fn save_iterator(&self) -> impl Iterator { + self.save() + } + + /// Get the string form of all values joined by newlines for saving./ + pub fn save_text(&self) -> String { + itertools::join(self.save_iterator(), "\n") + } + + /// Save all values joined by newlines to a file. + pub fn save_file

(&self, path: P) -> Result<(), DatasetError> + where + P: AsRef, + { + let file = File::open(path).map_err(|err| DatasetError::OpenDatasetFile(err))?; + let mut writer = BufWriter::new(file); + for line in self.save_iterator() { + writer + .write_all(line.as_bytes()) + .map_err(|err| DatasetError::WriteDatasetFile(err))?; + writer + .write_all("\n".as_bytes()) + .map_err(|err| DatasetError::WriteDatasetFile(err))?; + } Ok(()) } /// Get the available standard values as an iterator of `f64`. - pub fn values(&self) -> impl Iterator + '_ { + 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. @@ -188,6 +203,7 @@ pub struct DatasetCollection { } impl DatasetCollection { + /// Create dataset collection with 3 datasets for resistor, capacitor and inductor respectively. pub fn new(resistor: Dataset, capacitor: Dataset, inductor: Dataset) -> Self { Self { resistor, @@ -198,15 +214,9 @@ impl DatasetCollection { /// 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, @@ -221,24 +231,22 @@ impl DatasetCollection { S3: Into, { Ok(Self { - resistor: Dataset::from_iterable(resistor)?, - capacitor: Dataset::from_iterable(capacitor)?, - inductor: Dataset::from_iterable(inductor)?, + resistor: Dataset::from_iterator(resistor)?, + capacitor: Dataset::from_iterator(capacitor)?, + inductor: Dataset::from_iterator(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 { + pub fn from_text( + resistor: &str, + capacitor: &str, + inductor: &str, + ) -> Result { Ok(Self { resistor: Dataset::from_text(resistor)?, capacitor: Dataset::from_text(capacitor)?, @@ -248,20 +256,19 @@ impl DatasetCollection { /// 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 { + pub fn from_file( + resistor: P1, + capacitor: P2, + inductor: P3, + ) -> Result + where + P1: AsRef, + P2: AsRef, + P3: AsRef, + { Ok(Self { resistor: Dataset::from_file(resistor)?, capacitor: Dataset::from_file(capacitor)?, @@ -270,10 +277,6 @@ impl DatasetCollection { } /// The preset dataset collection for all devices. - /// - /// # Errors - /// - /// See [`Dataset::from_iterable`]. pub fn devices_preset() -> Result { Ok(Self { resistor: Dataset::resistor_preset()?, @@ -282,11 +285,22 @@ impl DatasetCollection { }) } - /// Get the string form of all values. - /// - /// # Returns - /// - /// A tuple of strings for resistor, capacitor and inductor respectively. + /// Get the iterators for saving resistor, capacitor and inductor dataset respectively. + pub fn save_iterator( + &self, + ) -> ( + impl Iterator, + impl Iterator, + impl Iterator, + ) { + ( + self.resistor.save_iterator(), + self.capacitor.save_iterator(), + self.inductor.save_iterator(), + ) + } + + /// Get the string form of all values for saving resistor, capacitor and inductor dataset respectively. pub fn save_text(&self) -> (String, String, String) { ( self.resistor.save_text(), @@ -297,21 +311,20 @@ impl DatasetCollection { /// 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 [`DatasetError::Io`] if any file cannot be written. - pub fn save_file( + pub fn save_file( &self, - resistor: impl AsRef, - capacitor: impl AsRef, - inductor: impl AsRef, - ) -> Result<(), DatasetError> { + resistor: P1, + capacitor: P2, + inductor: P3, + ) -> Result<(), DatasetError> + where + P1: AsRef, + P2: AsRef, + P3: AsRef, + { self.resistor.save_file(resistor)?; self.capacitor.save_file(capacitor)?; self.inductor.save_file(inductor)?; @@ -319,39 +332,34 @@ impl DatasetCollection { } /// Get the dataset for resistor. - pub fn resistor(&self) -> &Dataset { + pub fn resistor_dataset(&self) -> &Dataset { &self.resistor } /// Get the dataset for capacitor. - pub fn capacitor(&self) -> &Dataset { + pub fn capacitor_dataset(&self) -> &Dataset { &self.capacitor } /// Get the dataset for inductor. - pub fn inductor(&self) -> &Dataset { + pub fn inductor_dataset(&self) -> &Dataset { &self.inductor } } #[derive(Debug, TeError)] pub enum ParseHumanReadableValueError { - + #[error("fail to parse floating point part of given human readable value: {0}")] + ParseFloat(#[from] ParseFloatError), + #[error("arithmetic error: {0}")] + BadArithmetic(#[from] FloatingPointError), } /// Convert human readable value to float. /// -/// # Arguments -/// -/// * `strl` - The human readable value. -/// -/// # Returns -/// -/// The parsed float value. -/// -/// # Errors -/// -/// Returns [`DatasetError::InvalidHumanReadableValue`] if the input string is not a valid number. +/// `strl` is the human readable value. +/// The return value is the parsed float value. +/// or error occurs when parsing. pub fn from_human_readable_value(strl: &str) -> Result { let strl = strl.trim(); @@ -373,10 +381,8 @@ pub fn from_human_readable_value(strl: &str) -> Result() - .map(|v| v * multiplier) - .map_err(|_| DatasetError::InvalidHumanReadableValue(strl.to_string())) + let num = num_part.parse::()?; + Ok(sanitize_floating_point(num * multiplier)?) } /// The unit scale for human readable value. @@ -395,13 +401,7 @@ pub enum UnitScale { /// Get the unit scale of human readable value. /// -/// # Arguments -/// -/// * `v` - The value. -/// -/// # Returns -/// -/// The unit scale. +/// `v` is the value for analyzing scale. pub fn get_human_readable_value_scale(v: f64) -> UnitScale { let v = v.abs(); if v < 1e-12 { @@ -427,29 +427,21 @@ pub fn get_human_readable_value_scale(v: f64) -> UnitScale { /// Convert float value to human readable value. /// -/// # Arguments -/// -/// * `v` - The float value. -/// -/// # Returns -/// -/// The human readable value. +/// `v`is the float value for formatting as 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::Nano => format!("{:+.4} p", v / 1e-9), + UnitScale::Micro => format!("{:+.4} u", v / 1e-6), + UnitScale::Milli => format!("{:+.4} m", v / 1e-3), + // YYC MARK: + // The space of this format string is by design + // for keeping the same style with other format strings. + UnitScale::None => format!("{:+.4} ", v), + UnitScale::Kilo => format!("{:+.4} k", v / 1e3), + UnitScale::Mega => format!("{:+.4} M", v / 1e6), + UnitScale::Giga => format!("{:+.4} G", v / 1e9), UnitScale::GigaHigher => format!("{:+.4e} G", v / 1e9), } } diff --git a/kernel/lcrconn/src/resolver/bfs.rs b/kernel/lcrconn/src/resolver/bfs.rs index b9771b5..43fa6df 100644 --- a/kernel/lcrconn/src/resolver/bfs.rs +++ b/kernel/lcrconn/src/resolver/bfs.rs @@ -440,9 +440,9 @@ impl BfsResolver { fn pick_dataset(&self, device_kind: DeviceKind) -> &Dataset { match device_kind { - DeviceKind::Resistor => self.datasets.resistor(), - DeviceKind::Capacitor => self.datasets.capacitor(), - DeviceKind::Inductor => self.datasets.inductor(), + DeviceKind::Resistor => self.datasets.resistor_dataset(), + DeviceKind::Capacitor => self.datasets.capacitor_dataset(), + DeviceKind::Inductor => self.datasets.inductor_dataset(), } } } diff --git a/kernel/lcrconn/src/resolver/lut.rs b/kernel/lcrconn/src/resolver/lut.rs index 39217a8..1b4e53e 100644 --- a/kernel/lcrconn/src/resolver/lut.rs +++ b/kernel/lcrconn/src/resolver/lut.rs @@ -54,9 +54,9 @@ impl LutResolver { /// See [`LutItem::new`]. pub fn new(datasets: &DatasetCollection) -> Result { Ok(Self { - resistor_lut: Self::build_lut(datasets.resistor(), DeviceKind::Resistor)?, - capacitor_lut: Self::build_lut(datasets.capacitor(), DeviceKind::Capacitor)?, - inductor_lut: Self::build_lut(datasets.inductor(), DeviceKind::Inductor)?, + resistor_lut: Self::build_lut(datasets.resistor_dataset(), DeviceKind::Resistor)?, + capacitor_lut: Self::build_lut(datasets.capacitor_dataset(), DeviceKind::Capacitor)?, + inductor_lut: Self::build_lut(datasets.inductor_dataset(), DeviceKind::Inductor)?, }) }