refactor: finish refactor of kernel

This commit is contained in:
2026-07-22 20:27:34 +08:00
parent 5a41c6266c
commit 782bd86407
7 changed files with 254 additions and 215 deletions
+6 -6
View File
@@ -105,15 +105,15 @@ pub struct App {
impl App {
/// Create a new app with the given configuration.
pub fn new(config: AppConfig) -> Result<Self> {
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<dyn Resolver> = 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 })
+24 -24
View File
@@ -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<Cli> 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,
}
}
}
+47 -28
View File
@@ -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<CircuitEvaluation, CircuitError> {
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<Self, CircuitError> {
// 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<Self, CircuitError> {
// 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<Self, CircuitError> {
// 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
+7 -3
View File
@@ -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()))
});
}
}
+27 -17
View File
@@ -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<Self, BfsResolverError> {
// 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<Item = Circuit> {
// 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<Response, BfsResolverError> {
// 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`].
@@ -305,12 +310,17 @@ 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()
}
+139 -133
View File
@@ -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<LutItem>,
/// The lookup table for capacitors.
capacitor_lut: Vec<LutItem>,
/// The lookup table for inductors.
inductor_lut: Vec<LutItem>,
}
impl LutResolver {
/// Create a new LUT resolver by building lookup tables from the given specs.
pub fn new(specs: &SpecCatalog) -> Result<Self, LutResolverError> {
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<Vec<LutItem>, 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, LutResolverError> { LutItem::new(circuit, device_kind) })
.collect::<Result<Vec<_>, _>>()?;
// 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<Response, LutResolverError> {
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<Circuit> = 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<Response, ResolverError> {
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<LutItem>,
/// The lookup table for capacitors.
capacitor_lut: Vec<LutItem>,
/// The lookup table for inductors.
inductor_lut: Vec<LutItem>,
}
impl LutResolver {
/// Create a new LUT resolver by building lookup tables from the given datasets.
pub fn new(datasets: &SpecCatalog) -> Result<Self, LutResolverError> {
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<Vec<LutItem>, 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, LutResolverError> { LutItem::new(circuit?, device_kind) })
.collect::<Result<Vec<_>, _>>()?;
// 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<Response, LutResolverError> {
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<Circuit> = 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<Response, ResolverError> {
Ok(self.intern_resolve(request)?)
}
}
// endregion
+4 -4
View File
@@ -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();
}