From 782bd8640719f114c923a943f7878cca4954ccae Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 22 Jul 2026 20:27:34 +0800 Subject: [PATCH] refactor: finish refactor of kernel --- kernel/lcrconn-cli/src/app.rs | 12 +- kernel/lcrconn-cli/src/cli.rs | 48 ++--- kernel/lcrconn/src/common.rs | 75 +++++--- kernel/lcrconn/src/query.rs | 10 +- kernel/lcrconn/src/resolver/bfs.rs | 44 +++-- kernel/lcrconn/src/resolver/lut.rs | 272 +++++++++++++++-------------- kernel/lcrconn/tests/spec.rs | 8 +- 7 files changed, 254 insertions(+), 215 deletions(-) diff --git a/kernel/lcrconn-cli/src/app.rs b/kernel/lcrconn-cli/src/app.rs index 4441f2e..2c3f4a8 100644 --- a/kernel/lcrconn-cli/src/app.rs +++ b/kernel/lcrconn-cli/src/app.rs @@ -105,15 +105,15 @@ pub struct App { impl App { /// Create a new app with the given configuration. pub fn new(config: AppConfig) -> Result { - let datasets = SpecCatalog::from_file( - config.get_resistor_dataset(), - config.get_capacitor_dataset(), - config.get_inductor_dataset(), + let sepcs = SpecCatalog::from_file( + config.get_resistor_spec(), + config.get_capacitor_specs(), + config.get_inductor_specs(), )?; let resolver: Box = match config.get_resolver() { - AppResolver::Lut => Box::new(LutResolver::new(&datasets)?), - AppResolver::Bfs => Box::new(BfsResolver::new(datasets)), + AppResolver::Lut => Box::new(LutResolver::new(&sepcs)?), + AppResolver::Bfs => Box::new(BfsResolver::new(sepcs)), }; Ok(Self { resolver }) diff --git a/kernel/lcrconn-cli/src/cli.rs b/kernel/lcrconn-cli/src/cli.rs index d5a2a80..7c31492 100644 --- a/kernel/lcrconn-cli/src/cli.rs +++ b/kernel/lcrconn-cli/src/cli.rs @@ -6,12 +6,12 @@ use clap::{Parser, ValueEnum}; pub struct AppConfig { /// The resolver for the app. resolver: AppResolver, - /// The path to the resistor dataset file. - resistor_dataset: PathBuf, - /// The path to the capacitor dataset file. - capacitor_dataset: PathBuf, - /// The path to the inductor dataset file. - inductor_dataset: PathBuf, + /// The path to the resistor specs file. + resistor_specs: PathBuf, + /// The path to the capacitor specs file. + capacitor_specs: PathBuf, + /// The path to the inductor specs file. + inductor_specs: PathBuf, } impl AppConfig { @@ -19,17 +19,17 @@ impl AppConfig { pub fn get_resolver(&self) -> &AppResolver { &self.resolver } - /// Get the path to the resistor dataset file. - pub fn get_resistor_dataset(&self) -> &Path { - &self.resistor_dataset + /// Get the path to the resistor specs file. + pub fn get_resistor_spec(&self) -> &Path { + &self.resistor_specs } - /// Get the path to the capacitor dataset file. - pub fn get_capacitor_dataset(&self) -> &Path { - &self.capacitor_dataset + /// Get the path to the capacitor specs file. + pub fn get_capacitor_specs(&self) -> &Path { + &self.capacitor_specs } - /// Get the path to the inductor dataset file. - pub fn get_inductor_dataset(&self) -> &Path { - &self.inductor_dataset + /// Get the path to the inductor specs file. + pub fn get_inductor_specs(&self) -> &Path { + &self.inductor_specs } } @@ -57,41 +57,41 @@ struct Cli { #[arg(short = 's', long = "resolver", required = true, value_enum)] resolver: AppResolver, - /// The path to the resistor dataset file. + /// The path to the resistor specs file. #[arg( short = 'r', long = "resistor", required = true, value_name = "RESISTOR.TXT" )] - resistor_dataset: PathBuf, + resistor_specs: PathBuf, - /// The path to the inductor dataset file. + /// The path to the inductor specs file. #[arg( short = 'l', long = "inductor", required = true, value_name = "INDUCTOR.TXT" )] - inductor_dataset: PathBuf, + inductor_specs: PathBuf, - /// The path to the capacitor dataset file. + /// The path to the capacitor specs file. #[arg( short = 'c', long = "capacitor", required = true, value_name = "CAPACITOR.TXT" )] - capacitor_dataset: PathBuf, + capacitor_specs: PathBuf, } impl From for AppConfig { fn from(args: Cli) -> Self { Self { resolver: args.resolver, - resistor_dataset: args.resistor_dataset, - capacitor_dataset: args.capacitor_dataset, - inductor_dataset: args.inductor_dataset, + resistor_specs: args.resistor_specs, + capacitor_specs: args.capacitor_specs, + inductor_specs: args.inductor_specs, } } } diff --git a/kernel/lcrconn/src/common.rs b/kernel/lcrconn/src/common.rs index c3d72bf..bfadc08 100644 --- a/kernel/lcrconn/src/common.rs +++ b/kernel/lcrconn/src/common.rs @@ -116,6 +116,8 @@ pub enum CircuitError { #[error("invalid target value: {0}")] BadTargetValue(DeviceValueError), + #[error("invalid pre-evaluated circuit value: {0}")] + BadCircuitValue(DeviceValueError), #[error("bad previous evaluated joint value: {0}")] BadPreviousValue(DeviceValueError), #[error("floating point is invalid after arithmetic operation: {0}")] @@ -268,34 +270,6 @@ impl Circuit { Ok(value) } - /// Evaluate the circuit value with given target value and device kind - pub fn evaluate_with_target( - &self, - target_value: f64, - device_kind: DeviceKind, - ) -> Result { - let target_value = - validate_device_value(target_value).map_err(|err| CircuitError::BadTargetValue(err))?; - - let value = self.evaluate(device_kind)?; - let difference = validate_floating_point(value - target_value) - .map_err(|err| CircuitError::BadArithmetic(err))?; - let unsigned_difference = validate_floating_point(difference.abs()) - .map_err(|err| CircuitError::BadArithmetic(err))?; - let relative_difference = validate_floating_point(difference / target_value) - .map_err(|err| CircuitError::BadArithmetic(err))?; - let unsigned_relative_difference = validate_floating_point(relative_difference.abs()) - .map_err(|err| CircuitError::BadArithmetic(err))?; - - Ok(CircuitEvaluation { - value, - difference, - unsigned_difference, - relative_difference, - unsigned_relative_difference, - }) - } - /// Get the device scale. /// /// # Returns @@ -370,4 +344,49 @@ pub struct CircuitEvaluation { pub unsigned_relative_difference: f64, } +impl CircuitEvaluation { + /// Internal used constructor. Passed circuit `value` must be checked before calling this. + fn new(value: f64, target_value: f64) -> Result { + // Check target value + let target_value = + validate_device_value(target_value).map_err(|err| CircuitError::BadTargetValue(err))?; + // Start evaluating + let difference = validate_floating_point(value - target_value) + .map_err(|err| CircuitError::BadArithmetic(err))?; + let unsigned_difference = validate_floating_point(difference.abs()) + .map_err(|err| CircuitError::BadArithmetic(err))?; + let relative_difference = validate_floating_point(difference / target_value) + .map_err(|err| CircuitError::BadArithmetic(err))?; + let unsigned_relative_difference = validate_floating_point(relative_difference.abs()) + .map_err(|err| CircuitError::BadArithmetic(err))?; + // Return evaluation result + Ok(CircuitEvaluation { + value, + difference, + unsigned_difference, + relative_difference, + unsigned_relative_difference, + }) + } + + /// Evaluate circuit with device kind and target value. + pub fn from_circuit( + circuit: &Circuit, + device_kind: DeviceKind, + target_value: f64, + ) -> Result { + // Fetch circuit value and evaluate it. + let value = circuit.evaluate(device_kind)?; + Self::new(value, target_value) + } + + /// Evaluate circuit with pre-evaluated circuit value and target value. + pub fn from_circuit_value(value: f64, target_value: f64) -> Result { + // Check user given circuit value and evaluate it. + let value = + validate_device_value(value).map_err(|err| CircuitError::BadCircuitValue(err))?; + Self::new(value, target_value) + } +} + // endregion diff --git a/kernel/lcrconn/src/query.rs b/kernel/lcrconn/src/query.rs index ec5a213..69359c1 100644 --- a/kernel/lcrconn/src/query.rs +++ b/kernel/lcrconn/src/query.rs @@ -124,8 +124,11 @@ impl ResponseItem { // I can use OnceLock to implement the behavior closing to Python cached_property. // But I didn't do that due to the increased size of this struct, and inviable error handling. // So I decide to calculate all values in there. - let circuit_evaluation = - circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?; + let circuit_evaluation = CircuitEvaluation::from_circuit( + &circuit, + request.get_device_kind(), + request.get_target_value(), + )?; // Build self and return Ok(Self { circuit, @@ -217,7 +220,8 @@ impl Response { } ResponsePriority::MoreAccuracy => { items.sort_by(|a, b| { - OrderedFloat(a.unsigned_difference()).cmp(&OrderedFloat(b.unsigned_difference())) + OrderedFloat(a.unsigned_difference()) + .cmp(&OrderedFloat(b.unsigned_difference())) }); } } diff --git a/kernel/lcrconn/src/resolver/bfs.rs b/kernel/lcrconn/src/resolver/bfs.rs index 4671965..93c46f8 100644 --- a/kernel/lcrconn/src/resolver/bfs.rs +++ b/kernel/lcrconn/src/resolver/bfs.rs @@ -1,5 +1,5 @@ use super::{Resolver, ResolverError}; -use crate::common::{Circuit, CircuitError, DeviceKind, JointKind}; +use crate::common::{Circuit, CircuitError, CircuitEvaluation, DeviceKind, JointKind}; use crate::query::{Request, Response, ResponseError}; use crate::spec::{SpecCatalog, SpecGroup}; use itertools::Itertools; @@ -26,7 +26,7 @@ pub enum BfsResolverError { pub struct BfsItem { /// The circuit represented by this item. circuit: Circuit, - /// The computed value of the circuit. + /// The evaluated value of the circuit. value: f64, /// The unsigned difference between the target value and the value of this circuit. unsigned_difference: f64, @@ -37,8 +37,11 @@ impl BfsItem { pub fn new(circuit: Circuit, request: &Request) -> Result { // YYC MARK: // The same reason for replacing cached_property like I done in `ResponseItem`. - let eval = - circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?; + let eval = CircuitEvaluation::from_circuit( + &circuit, + request.get_device_kind(), + request.get_target_value(), + )?; Ok(Self { circuit, @@ -52,7 +55,7 @@ impl BfsItem { &self.circuit } - /// The computed value of the circuit. + /// The evaluated value of the circuit. pub fn value(&self) -> f64 { self.value } @@ -74,8 +77,8 @@ impl BfsItem { /// A resolver that uses breadth first search to find the best matching circuits. pub struct BfsResolver { - /// The datasets for all device kinds. - datasets: SpecCatalog, + /// The specs for all device kinds. + specs: SpecCatalog, } impl BfsResolver { @@ -92,7 +95,7 @@ impl BfsResolver { /// Iterate all possible circuits with one device without repeating equivalent topology. pub fn iter_one_device_circuit(specs: &SpecGroup) -> impl Iterator { // Every single device is unique so we directly output them. - // This feature is insured by dataset itself. + // This feature is insured by spec itself. specs .iter() .map(|v1| Circuit::from_one_device(v1).expect("unexpected failure on building circuit")) @@ -146,16 +149,16 @@ impl BfsResolver { } impl BfsResolver { - /// Create a new BFS resolver with the given datasets. - pub fn new(datasets: SpecCatalog) -> Self { - Self { datasets } + /// Create a new BFS resolver with the given specs. + pub fn new(specs: SpecCatalog) -> Self { + Self { specs } } fn pick_specs(&self, device_kind: DeviceKind) -> &SpecGroup { match device_kind { - DeviceKind::Resistor => self.datasets.resistor_specs(), - DeviceKind::Capacitor => self.datasets.capacitor_specs(), - DeviceKind::Inductor => self.datasets.inductor_specs(), + DeviceKind::Resistor => self.specs.resistor_specs(), + DeviceKind::Capacitor => self.specs.capacitor_specs(), + DeviceKind::Inductor => self.specs.inductor_specs(), } } @@ -172,7 +175,7 @@ impl BfsResolver { } fn intern_resolve(&self, request: &Request) -> Result { - // Pick dataset from collection + // Pick specs group from catalog let specs = self.pick_specs(request.get_device_kind()); // Create the result bucket. @@ -205,7 +208,9 @@ impl Resolver for BfsResolver { } } -// endregion: +// endregion + +// endregion // region: Result Bucket Helper @@ -279,7 +284,7 @@ impl Ord for ResultBucketItem { /// /// When the bucket is full, inserting a new item only succeeds if its score /// is less than the current maximum; the maximum is then evicted. -pub struct ResultBucket { +struct ResultBucket { /// Maximum number of items the bucket can hold. n: usize, /// Max-heap of [`ResultBucketItem`]. @@ -304,13 +309,18 @@ impl ResultBucket { }) } } + + // YYC MARK: + // I want to preserve these 2 functions so I add `allow(dead_code)` to them. /// The number of items currently in the bucket. + #[allow(dead_code)] pub fn len(&self) -> usize { self.heap.len() } /// Whether the bucket is empty. + #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.heap.is_empty() } diff --git a/kernel/lcrconn/src/resolver/lut.rs b/kernel/lcrconn/src/resolver/lut.rs index b683dbd..858bad5 100644 --- a/kernel/lcrconn/src/resolver/lut.rs +++ b/kernel/lcrconn/src/resolver/lut.rs @@ -1,22 +1,24 @@ use super::bfs::BfsResolver; use super::{Resolver, ResolverError}; -use crate::common::{Circuit, CircuitCalculator, CircuitCalculatorError, CircuitError, DeviceKind}; +use crate::common::{Circuit, CircuitError, CircuitEvaluation, DeviceKind}; use crate::spec::{SpecGroup, SpecCatalog}; use crate::query::{Request, Response, ResponseError}; use ordered_float::OrderedFloat; use thiserror::Error as TeError; +// region: LUT Resolver Kernel + /// Errors occurs in LUT resolver. #[derive(Debug, TeError)] pub enum LutResolverError { - #[error("failed to build circuit: {0}")] - Circuit(#[from] CircuitError), - #[error("failed on computing circuit properties: {0}")] - CircuitCalculator(#[from] CircuitCalculatorError), + #[error("failed on evaluating circuit: {0}")] + CircuitCalculator(#[from] CircuitError), #[error("fail to build response: {0}")] Response(#[from] ResponseError), } +// region: LUT Item + /// An item in the lookup table. pub struct LutItem { /// The circuit represented by this item. @@ -46,6 +48,137 @@ impl LutItem { } } +// endregion + +// region: LUT Resolver + +/// A resolver that uses a lookup table to find the best matching circuit. +pub struct LutResolver { + /// The lookup table for resistors. + resistor_lut: Vec, + /// The lookup table for capacitors. + capacitor_lut: Vec, + /// The lookup table for inductors. + inductor_lut: Vec, +} + +impl LutResolver { + /// Create a new LUT resolver by building lookup tables from the given specs. + pub fn new(specs: &SpecCatalog) -> Result { + Ok(Self { + resistor_lut: Self::build_lut(specs.resistor_specs(), DeviceKind::Resistor)?, + capacitor_lut: Self::build_lut(specs.capacitor_specs(), DeviceKind::Capacitor)?, + inductor_lut: Self::build_lut(specs.inductor_specs(), DeviceKind::Inductor)?, + }) + } + + fn build_lut( + specs: &SpecGroup, + device_kind: DeviceKind, + ) -> Result, LutResolverError> { + // Fetch all items + let mut lut = itertools::chain!( + BfsResolver::iter_one_device_circuit(&specs), + BfsResolver::iter_two_devices_circuit(&specs), + BfsResolver::iter_three_devices_circuit(&specs) + ) + .map(|circuit| -> Result { LutItem::new(circuit, device_kind) }) + .collect::, _>>()?; + // Sort them and return + lut.sort_by(|a, b| a.value.cmp(&b.value)); + Ok(lut) + } + + fn pick_lut(&self, device_kind: DeviceKind) -> &[LutItem] { + match device_kind { + DeviceKind::Resistor => &self.resistor_lut, + DeviceKind::Capacitor => &self.capacitor_lut, + DeviceKind::Inductor => &self.inductor_lut, + } + } + + fn intern_resolve(&self, request: &Request) -> Result { + let lut = self.pick_lut(request.get_device_kind()); + let target_value = request.get_target_value(); + let count_limit = request.get_count_limit(); + let mut bucket: Vec = Vec::new(); + + // Locate the insertion point of target in the sorted LUT. + // left/right start at the two nearest neighbours and expand outward. + let lower_bound = 0; + let upper_bound = lut.len() - 1; + let target = OrderedFloat(target_value); + let idx = lut.partition_point(|item| item.value < target); + let mut left = RangedIndex::new(idx, lower_bound, upper_bound); + let mut right = left.clone(); + left.dec(); + + // Expand outward non-symmetrically: at each step compare the two + // candidates on each side and advance the one that is closer to the target. + // This guarantees items are visited in strictly increasing + // difference order, so the first N items within tolerance are exactly + // the N best matches. + loop { + // Check result count + if bucket.len() >= count_limit { + break; + } + + let go_left = if left.in_range() { + if right.in_range() { + let left_item = &lut[left.position()]; + let left_diff = CircuitEvaluation::from_circuit_value(left_item.value(),target_value)?.unsigned_difference; + let right_item = &lut[right.position()]; + let right_diff = CircuitEvaluation::from_circuit_value(right_item.value(), target_value)?.unsigned_difference; + left_diff <= right_diff + } else { + true + } + } else { + if right.in_range() { + false + } else { + break; + } + }; + + let item = if go_left { + let item = &lut[left.position()]; + left.dec(); + item + } else { + let item = &lut[right.position()]; + right.inc(); + item + }; + + let diff = CircuitEvaluation::from_circuit_value(item.value(), target_value)?.unsigned_difference; + // Since the LUT is sorted, values on each side only move further + // from target as we advance. Once one side exceeds tolerance, + // the rest of that side is guaranteed out of range. + if diff > request.get_tolerance() { + break; + } + + bucket.push(item.circuit().clone()); + } + + Ok(Response::new(request, bucket.into_iter())?) + } +} + +impl Resolver for LutResolver { + fn resolve(&self, request: &Request) -> Result { + Ok(self.intern_resolve(request)?) + } +} + +// endregion + +// endregion + +// region: Ranged Index Helper + /// The ranged index for bisect LUT finding in resolver. #[derive(Debug, Clone)] pub struct RangedIndex { @@ -115,131 +248,4 @@ impl RangedIndex { } } -/// A resolver that uses a lookup table to find the best matching circuit. -pub struct LutResolver { - /// The lookup table for resistors. - resistor_lut: Vec, - /// The lookup table for capacitors. - capacitor_lut: Vec, - /// The lookup table for inductors. - inductor_lut: Vec, -} - -impl LutResolver { - /// Create a new LUT resolver by building lookup tables from the given datasets. - pub fn new(datasets: &SpecCatalog) -> Result { - Ok(Self { - resistor_lut: Self::build_lut(datasets.resistor_specs(), DeviceKind::Resistor)?, - capacitor_lut: Self::build_lut(datasets.capacitor_specs(), DeviceKind::Capacitor)?, - inductor_lut: Self::build_lut(datasets.inductor_specs(), DeviceKind::Inductor)?, - }) - } - - fn build_lut( - dataset: &SpecGroup, - device_kind: DeviceKind, - ) -> Result, LutResolverError> { - // Fetch all items - let mut lut = itertools::chain!( - BfsResolver::iter_one_device_circuit(&dataset), - BfsResolver::iter_two_devices_circuit(&dataset), - BfsResolver::iter_three_devices_circuit(&dataset) - ) - .map(|circuit| -> Result { LutItem::new(circuit?, device_kind) }) - .collect::, _>>()?; - // Sort them and return - lut.sort_by(|a, b| a.value.cmp(&b.value)); - Ok(lut) - } - - fn pick_lut(&self, device_kind: DeviceKind) -> &[LutItem] { - match device_kind { - DeviceKind::Resistor => &self.resistor_lut, - DeviceKind::Capacitor => &self.capacitor_lut, - DeviceKind::Inductor => &self.inductor_lut, - } - } - - fn intern_resolve(&self, request: &Request) -> Result { - let lut = self.pick_lut(request.get_device_kind()); - let target = OrderedFloat(request.get_target_value()); - let count_limit = request.get_count_limit(); - let mut bucket: Vec = Vec::new(); - - // Locate the insertion point of target in the sorted LUT. - // left/right start at the two nearest neighbours and expand outward. - let lower_bound = 0; - let upper_bound = lut.len() - 1; - let idx = lut.partition_point(|item| item.value < target); - let mut left = RangedIndex::new(idx, lower_bound, upper_bound); - let mut right = left.clone(); - left.dec(); - - // Expand outward non-symmetrically: at each step compare the two - // candidates on each side and advance the one that is closer to the - // target. This guarantees items are visited in strictly increasing - // difference order, so the first N items within tolerance are exactly - // the N best matches. - let ccalc = CircuitCalculator::new(request.get_device_kind(), target.0)?; - loop { - // Check result count - if bucket.len() >= count_limit { - break; - } - - let go_left = if left.in_range() { - if right.in_range() { - let left_item = &lut[left.position()]; - let left_diff = ccalc.unsigned_difference( - left_item.circuit(), - Some(left_item.value()), - None, - )?; - let right_item = &lut[right.position()]; - let right_diff = ccalc.unsigned_difference( - right_item.circuit(), - Some(right_item.value()), - None, - )?; - left_diff <= right_diff - } else { - true - } - } else { - if right.in_range() { - false - } else { - break; - } - }; - - let item = if go_left { - let item = &lut[left.position()]; - left.dec(); - item - } else { - let item = &lut[right.position()]; - right.inc(); - item - }; - - let diff = ccalc.unsigned_difference(item.circuit(), Some(item.value()), None)?; - // Since the LUT is sorted, values on each side only move further - // from target as we advance. Once one side exceeds tolerance, - // the rest of that side is guaranteed out of range. - if diff > request.get_tolerance() { - break; - } - - bucket.push(item.circuit().clone()); - } - - Ok(Response::new(request, bucket.into_iter())?) - } -} - -impl Resolver for LutResolver { - fn resolve(&self, request: &Request) -> Result { - Ok(self.intern_resolve(request)?) - } -} +// endregion diff --git a/kernel/lcrconn/tests/spec.rs b/kernel/lcrconn/tests/spec.rs index c1518a3..c14370e 100644 --- a/kernel/lcrconn/tests/spec.rs +++ b/kernel/lcrconn/tests/spec.rs @@ -3,9 +3,9 @@ use lcrconn::spec; #[test] fn test_spec_preset() { // All individual preset and catalog preset should nit panic - let specs = spec::SpecGroup::resistor_preset(); - let specs = spec::SpecGroup::capacitor_preset(); - let specs = spec::SpecGroup::inductor_preset(); + let _ = spec::SpecGroup::resistor_preset(); + let _ = spec::SpecGroup::capacitor_preset(); + let _ = spec::SpecGroup::inductor_preset(); - let specs = spec::SpecCatalog::devices_preset(); + let _ = spec::SpecCatalog::devices_preset(); }