refactor: continue refactoring kernel

This commit is contained in:
2026-07-22 16:59:55 +08:00
parent 1514d25da1
commit 5a41c6266c
4 changed files with 212 additions and 186 deletions
+26 -38
View File
@@ -1,12 +1,11 @@
use crate::common::{ use crate::common::{
Circuit, CircuitCalculator, CircuitCalculatorError, DeviceKind, DeviceValueError, Circuit, CircuitError, CircuitEvaluation, DeviceKind, DeviceValueError, validate_device_value,
validate_device_value,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use thiserror::Error as TeError; use thiserror::Error as TeError;
/// The priority of the result. /// The priority of the result.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy)]
pub enum ResponsePriority { pub enum ResponsePriority {
/// Less devices is the first priority. /// Less devices is the first priority.
LessDevices, LessDevices,
@@ -76,11 +75,15 @@ impl Request {
} }
/// Get the target value of this request. /// Get the target value of this request.
///
/// The return value was ensured that it must be valid device value.
pub fn get_target_value(&self) -> f64 { pub fn get_target_value(&self) -> f64 {
self.target_value self.target_value
} }
/// Get the tolerance of this request. /// Get the tolerance of this request.
///
/// The return value was ensured that it must be unsigned non-relative valid device value.
pub fn get_tolerance(&self) -> f64 { pub fn get_tolerance(&self) -> f64 {
self.tolerance self.tolerance
} }
@@ -91,6 +94,8 @@ impl Request {
} }
/// Get the limited count of results. /// Get the limited count of results.
///
/// The return value was ensured that it must >= 0 and < [`MAX_RESPONSE_CNT`].
pub fn get_count_limit(&self) -> usize { pub fn get_count_limit(&self) -> usize {
self.count_limit self.count_limit
} }
@@ -99,8 +104,8 @@ impl Request {
/// Error occurs when building [Response] and [ResponseItem]. /// Error occurs when building [Response] and [ResponseItem].
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
pub enum ResponseError { pub enum ResponseError {
#[error("failed on computing circuit properties: {0}")] #[error("failed on evaluating circuit: {0}")]
CircuitCalculator(#[from] CircuitCalculatorError), EvaluateCircuit(#[from] CircuitError),
} }
/// The possible solution given by the resolver. /// The possible solution given by the resolver.
@@ -108,39 +113,23 @@ pub enum ResponseError {
pub struct ResponseItem { pub struct ResponseItem {
/// The circuit of this response item. /// The circuit of this response item.
circuit: Circuit, circuit: Circuit,
/// The value of this circuit. /// The evaluation result of this circuit.
value: f64, circuit_evaluation: CircuitEvaluation,
/// The signed difference.
difference: f64,
/// The unsigned difference.
unsigned_difference: f64,
/// The signed relative difference.
relative_difference: f64,
/// The unsigned relative difference.
unsigned_relative_difference: f64,
} }
impl ResponseItem { impl ResponseItem {
/// Create a new response item by computing all values eagerly. /// Create a new response item by computing all values eagerly.
pub fn new(circuit: Circuit, ccalc: &CircuitCalculator) -> Result<Self, ResponseError> { fn new(circuit: Circuit, request: &Request) -> Result<Self, ResponseError> {
// YYC MARK: // YYC MARK:
// I can use OnceLock to implement the behavior closing to Python cached_property. // 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. // 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. // So I decide to calculate all values in there.
let value = ccalc.value(&circuit)?; let circuit_evaluation =
let difference = ccalc.difference(&circuit, Some(value))?; circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?;
let unsigned_difference = ccalc.unsigned_difference(&circuit, None, Some(difference))?; // Build self and return
let relative_difference = ccalc.relative_difference(&circuit, None, Some(difference))?;
let unsigned_relative_difference =
ccalc.unsigned_relative_difference(&circuit, None, None, Some(relative_difference))?;
Ok(Self { Ok(Self {
circuit, circuit,
value, circuit_evaluation,
difference,
unsigned_difference,
relative_difference,
unsigned_relative_difference,
}) })
} }
@@ -156,7 +145,7 @@ impl ResponseItem {
/// The value of this circuit. /// The value of this circuit.
pub fn value(&self) -> f64 { pub fn value(&self) -> f64 {
self.value self.circuit_evaluation.value
} }
/// The signed difference between the target value and the value of this circuit. /// The signed difference between the target value and the value of this circuit.
@@ -164,12 +153,12 @@ impl ResponseItem {
/// Positive value indicates that the value of this circuit is greater than the target value. /// Positive value indicates that the value of this circuit is greater than the target value.
/// Negative value indicates that the value of this circuit is less than the target value. /// Negative value indicates that the value of this circuit is less than the target value.
pub fn difference(&self) -> f64 { pub fn difference(&self) -> f64 {
self.difference self.circuit_evaluation.difference
} }
/// The unsigned difference between the target value and the value of this circuit. /// The unsigned difference between the target value and the value of this circuit.
pub fn unsigned_difference(&self) -> f64 { pub fn unsigned_difference(&self) -> f64 {
self.unsigned_difference self.circuit_evaluation.unsigned_difference
} }
/// The signed relative difference between the target value and the value of this circuit. /// The signed relative difference between the target value and the value of this circuit.
@@ -177,12 +166,12 @@ impl ResponseItem {
/// Positive value indicates that the value of this circuit is greater than the target value. /// Positive value indicates that the value of this circuit is greater than the target value.
/// Negative value indicates that the value of this circuit is less than the target value. /// Negative value indicates that the value of this circuit is less than the target value.
pub fn relative_difference(&self) -> f64 { pub fn relative_difference(&self) -> f64 {
self.relative_difference self.circuit_evaluation.relative_difference
} }
/// The unsigned relative difference between the target value and the value of this circuit. /// The unsigned relative difference between the target value and the value of this circuit.
pub fn unsigned_relative_difference(&self) -> f64 { pub fn unsigned_relative_difference(&self) -> f64 {
self.unsigned_relative_difference self.circuit_evaluation.unsigned_relative_difference
} }
} }
@@ -211,10 +200,9 @@ impl Response {
where where
I: Iterator<Item = Circuit>, I: Iterator<Item = Circuit>,
{ {
let ccalc = CircuitCalculator::new(request.device_kind, request.target_value)?;
let mut items: Vec<ResponseItem> = candidates let mut items: Vec<ResponseItem> = candidates
.into_iter() .into_iter()
.map(|c| ResponseItem::new(c, &ccalc)) .map(|c| ResponseItem::new(c, request))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Sort by different strategy // Sort by different strategy
@@ -222,14 +210,14 @@ impl Response {
ResponsePriority::LessDevices => { ResponsePriority::LessDevices => {
items.sort_by(|a, b| { items.sort_by(|a, b| {
a.device_count().cmp(&b.device_count()).then_with(|| { a.device_count().cmp(&b.device_count()).then_with(|| {
OrderedFloat(a.unsigned_difference) OrderedFloat(a.unsigned_difference())
.cmp(&OrderedFloat(b.unsigned_difference)) .cmp(&OrderedFloat(b.unsigned_difference()))
}) })
}); });
} }
ResponsePriority::MoreAccuracy => { ResponsePriority::MoreAccuracy => {
items.sort_by(|a, b| { items.sort_by(|a, b| {
OrderedFloat(a.unsigned_difference).cmp(&OrderedFloat(b.unsigned_difference)) OrderedFloat(a.unsigned_difference()).cmp(&OrderedFloat(b.unsigned_difference()))
}); });
} }
} }
+171 -144
View File
@@ -1,9 +1,7 @@
use super::{Resolver, ResolverError}; use super::{Resolver, ResolverError};
use crate::common::{ use crate::common::{Circuit, CircuitError, DeviceKind, JointKind};
Circuit, CircuitCalculator, CircuitCalculatorError, CircuitError, DeviceKind, JointKind,
};
use crate::spec::{SpecGroup, SpecCatalog};
use crate::query::{Request, Response, ResponseError}; use crate::query::{Request, Response, ResponseError};
use crate::spec::{SpecCatalog, SpecGroup};
use itertools::Itertools; use itertools::Itertools;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use std::cmp::Ordering; use std::cmp::Ordering;
@@ -11,15 +9,13 @@ use std::collections::BinaryHeap;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use thiserror::Error as TeError; use thiserror::Error as TeError;
// region: BFS Resolver Kernel
/// Error occurs BFS resolver. /// Error occurs BFS resolver.
#[derive(Debug, TeError)] #[derive(Debug, TeError)]
pub enum BfsResolverError { pub enum BfsResolverError {
#[error("failed to build circuit: {0}")] #[error("failed on evaluating circuit: {0}")]
Circuit(#[from] CircuitError), EvaluateCircuit(#[from] CircuitError),
#[error("failed on computing circuit properties: {0}")]
CircuitCalculator(#[from] CircuitCalculatorError),
#[error("the size of binary heap {0} is invalid")]
BadBinHeapSize(usize),
#[error("fail to build response: {0}")] #[error("fail to build response: {0}")]
Response(#[from] ResponseError), Response(#[from] ResponseError),
} }
@@ -38,15 +34,16 @@ pub struct BfsItem {
impl BfsItem { impl BfsItem {
/// Create a new BFS item by computing values eagerly. /// Create a new BFS item by computing values eagerly.
pub fn new(circuit: Circuit, ccalc: &CircuitCalculator) -> Result<Self, BfsResolverError> { pub fn new(circuit: Circuit, request: &Request) -> Result<Self, BfsResolverError> {
// YYC MARK: // YYC MARK:
// The same reason for replacing cached_property like I done in `ResponseItem`. // The same reason for replacing cached_property like I done in `ResponseItem`.
let value = ccalc.value(&circuit)?; let eval =
let unsigned_difference = ccalc.unsigned_difference(&circuit, Some(value), None)?; circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?;
Ok(Self { Ok(Self {
circuit, circuit,
value, value: eval.value,
unsigned_difference, unsigned_difference: eval.unsigned_difference,
}) })
} }
@@ -73,7 +70,153 @@ impl BfsItem {
// endregion // endregion
// region Result Bucket // region: BFS Resolver
/// A resolver that uses breadth first search to find the best matching circuits.
pub struct BfsResolver {
/// The datasets for all device kinds.
datasets: SpecCatalog,
}
impl BfsResolver {
// YYC MARK:
// Some circuit are equivalent in topology.
// If we deduplicate these equaivalent circuit in building result, there are too complex works.
// So we should deduplicated these equivalent circuit at the beginning, i.e. when generating them.
// So following iterator functions are taking this job.
//
// Additionally, these device values are coming from `spec`.
// All values are verified so the building step must success.
// So we can safely unwrap them.
/// 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.
specs
.iter()
.map(|v1| Circuit::from_one_device(v1).expect("unexpected failure on building circuit"))
}
/// Iterate all possible circuits with two devices without repeating equivalent topology.
pub fn iter_two_devices_circuit(specs: &SpecGroup) -> impl Iterator<Item = Circuit> {
// The two devices in this circuit is always swapable,
// so we iterate them without repeating.
itertools::iproduct!(
specs.iter().array_combinations_with_replacement::<2>(),
JointKind::iter()
)
.map(|([v1, v2], j2)| {
Circuit::from_two_devices(v1, v2, j2).expect("unexpected failure on building circuit")
})
}
/// Iterate all possible circuits with three devices without repeating equivalent topology.
pub fn iter_three_devices_circuit(specs: &SpecGroup) -> impl Iterator<Item = Circuit> {
// For generating three devices circuit,
// it should be consisted by 2 parts.
itertools::chain!(
// First, the whole circuit has only one joint type.
// In this case, 3 devices are swapable and we should iterate them without repeating
itertools::iproduct!(
specs.iter().array_combinations_with_replacement::<3>(),
JointKind::iter()
)
.map(
|([v1, v2, v3], j)| Circuit::from_three_devices(v1, v2, j, v3, j)
.expect("unexpected failure on building circuit")
),
// Second, if the joint type is different, then the first 2 devices are swapable.
// So we need iterate them without repeating.
itertools::iproduct!(
specs.iter().array_combinations_with_replacement::<2>(),
specs.iter(),
JointKind::iter()
)
.map(|([v1, v2], v3, j)| Circuit::from_three_devices(
v1,
v2,
j,
v3,
j.flip()
)
.expect("unexpected failure on building circuit")),
)
}
}
impl BfsResolver {
/// Create a new BFS resolver with the given datasets.
pub fn new(datasets: SpecCatalog) -> Self {
Self { datasets }
}
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(),
}
}
fn bfs_iteration(
specs: &SpecGroup,
request: &Request,
) -> impl Iterator<Item = Result<BfsItem, BfsResolverError>> {
itertools::chain!(
BfsResolver::iter_one_device_circuit(&specs),
BfsResolver::iter_two_devices_circuit(&specs),
BfsResolver::iter_three_devices_circuit(&specs)
)
.map(|circuit| BfsItem::new(circuit, request))
}
fn intern_resolve(&self, request: &Request) -> Result<Response, BfsResolverError> {
// Pick dataset from collection
let specs = self.pick_specs(request.get_device_kind());
// Create the result bucket.
// The count limit held by request is must be greater than zero, so we can simply unwrap it.
let mut bucket =
ResultBucket::new(request.get_count_limit()).expect("unexpected blank result bucket");
// Iterate circuit item one by one
for item in BfsResolver::bfs_iteration(specs, request) {
let item = item?;
// If circuit absolute difference is out of tolerance, skip it directly.
if item.unsigned_difference() <= request.get_tolerance() {
// Put it into bucket
let score = item.unsigned_difference();
bucket.insert(item, score);
} else {
continue;
}
}
// Return result
let circuits = bucket.into_iter().map(|i| i.into_circuit());
Ok(Response::new(request, circuits)?)
}
}
impl Resolver for BfsResolver {
fn resolve(&self, request: &Request) -> Result<Response, ResolverError> {
Ok(self.intern_resolve(request)?)
}
}
// endregion:
// region: Result Bucket Helper
/// The error occurs in [`ResultBucket`] and [`ResultBucketItem`].
#[derive(Debug, TeError)]
enum ResultBucketError {
#[error("the size of binary heap {0} is invalid")]
BadBinHeapSize(usize),
}
// region: Result Bucket Item
/// An item stored in a [`ResultBucket`]. /// An item stored in a [`ResultBucket`].
struct ResultBucketItem { struct ResultBucketItem {
@@ -128,6 +271,10 @@ impl Ord for ResultBucketItem {
} }
} }
// endregion
// region: Result Bucket
/// A bounded bucket that keeps up to N entries with the smallest scores. /// A bounded bucket that keeps up to N entries with the smallest scores.
/// ///
/// When the bucket is full, inserting a new item only succeeds if its score /// When the bucket is full, inserting a new item only succeeds if its score
@@ -145,10 +292,10 @@ pub struct ResultBucket {
impl ResultBucket { impl ResultBucket {
/// Create a new bucket that holds at most `n` items. /// Create a new bucket that holds at most `n` items.
pub fn new(n: usize) -> Result<Self, BfsResolverError> { pub fn new(n: usize) -> Result<Self, ResultBucketError> {
// Check heap size // Check heap size
if n == 0 { if n == 0 {
Err(BfsResolverError::BadBinHeapSize(n)) Err(ResultBucketError::BadBinHeapSize(n))
} else { } else {
Ok(Self { Ok(Self {
n, n,
@@ -182,6 +329,11 @@ impl ResultBucket {
/// ///
/// Returns `true` if the item was inserted, `false` otherwise. /// Returns `true` if the item was inserted, `false` otherwise.
pub fn insert(&mut self, item: BfsItem, score: f64) -> bool { pub fn insert(&mut self, item: BfsItem, score: f64) -> bool {
// YYC MARK:
// Because this struct stored `n` is must greater than zero,
// so after the first `if` branch, the length of this binary heap must be greater than zero.
// So there must be at least one item in binary heap.
// and we can safely use `expect()` to peek from binary heap.
let entry = ResultBucketItem::new(score, item, self.counter); let entry = ResultBucketItem::new(score, item, self.counter);
if self.heap.len() < self.n { if self.heap.len() < self.n {
self.heap.push(entry); self.heap.push(entry);
@@ -205,129 +357,4 @@ impl ResultBucket {
// endregion // endregion
/// A resolver that uses brute-force search to find the best matching circuits. // endregion
pub struct BfsResolver {
/// The datasets for all device kinds.
datasets: SpecCatalog,
}
impl BfsResolver {
// YYC MARK:
// Some circuit are equivalent in topology.
// If we deduplicate these equaivalent circuit in building result,
// there are too complex works.
// So we should deduplicated these equivalent circuit at the beginning,
// i.e. when generating them.
// So following iterator structs are taking this job.
/// Iterate all possible circuits with one device without repeating equivalent topology.
pub fn iter_one_device_circuit(
dataset: &SpecGroup,
) -> impl Iterator<Item = Result<Circuit, CircuitError>> {
// Every single device is unique so we directly output them.
// This feature is insured by dataset itself.
dataset.specs().map(|v1| Circuit::from_one_device(v1))
}
/// Iterate all possible circuits with two devices without repeating equivalent topology.
pub fn iter_two_devices_circuit(
dataset: &SpecGroup,
) -> impl Iterator<Item = Result<Circuit, CircuitError>> {
// The two devices in this circuit is always swapable,
// so we iterate them without repeating.
itertools::iproduct!(
dataset.specs().array_combinations_with_replacement::<2>(),
JointKind::iter()
)
.map(|([v1, v2], j2)| Circuit::from_two_devices(v1, v2, j2))
}
/// Iterate all possible circuits with three devices without repeating equivalent topology.
pub fn iter_three_devices_circuit(
dataset: &SpecGroup,
) -> impl Iterator<Item = Result<Circuit, CircuitError>> {
// For generating three devices circuit,
// it should be consisted by 2 parts.
itertools::chain!(
// First, the whole circuit has only one joint type.
// In this case, 3 devices are swapable and we should iterate them without repeating
itertools::iproduct!(
dataset.specs().array_combinations_with_replacement::<3>(),
JointKind::iter()
)
.map(|([v1, v2, v3], j)| Circuit::from_three_devices(v1, v2, j, v3, j)),
// Second, if the joint type is different, then the first 2 devices are swapable.
// So we need iterate them without repeating.
itertools::iproduct!(
dataset.specs().array_combinations_with_replacement::<2>(),
dataset.specs(),
JointKind::iter()
)
.map(|([v1, v2], v3, j)| Circuit::from_three_devices(
v1,
v2,
j,
v3,
j.flip()
)),
)
}
}
impl BfsResolver {
/// Create a new BFS resolver with the given datasets.
pub fn new(datasets: SpecCatalog) -> Self {
Self { datasets }
}
fn pick_dataset(&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(),
}
}
fn bfs_iteration(
dataset: &SpecGroup,
ccalc: &CircuitCalculator,
) -> impl Iterator<Item = Result<BfsItem, BfsResolverError>> {
itertools::chain!(
BfsResolver::iter_one_device_circuit(&dataset),
BfsResolver::iter_two_devices_circuit(&dataset),
BfsResolver::iter_three_devices_circuit(&dataset)
)
.map(|circuit| -> Result<BfsItem, BfsResolverError> { BfsItem::new(circuit?, ccalc) })
}
fn intern_resolve(&self, request: &Request) -> Result<Response, BfsResolverError> {
// Pick dataset from collection
let dataset = self.pick_dataset(request.get_device_kind());
// Iterate circuit item one by one
let mut bucket = ResultBucket::new(request.get_count_limit())?;
let ccalc = CircuitCalculator::new(request.get_device_kind(), request.get_target_value())?;
for item in BfsResolver::bfs_iteration(dataset, &ccalc) {
let item = item?;
// If circuit absolute difference is out of tolerance, skip it directly.
if item.unsigned_difference() <= request.get_tolerance() {
// Put it into bucket
let score = item.unsigned_difference();
bucket.insert(item, score);
} else {
continue;
}
}
// Return result
let circuits = bucket.into_iter().map(|i| i.into_circuit());
Ok(Response::new(request, circuits)?)
}
}
impl Resolver for BfsResolver {
fn resolve(&self, request: &Request) -> Result<Response, ResolverError> {
Ok(self.intern_resolve(request)?)
}
}
+4 -4
View File
@@ -156,7 +156,7 @@ impl SpecGroup {
Self::from_iterator([ Self::from_iterator([
"100", "220", "270", "390", "470", "680", "1k", "1.2k", "1.5k", "2.2k", "3.3k", "4.7k", "100", "220", "270", "390", "470", "680", "1k", "1.2k", "1.5k", "2.2k", "3.3k", "4.7k",
"6.8k", "10k", "47k", "100k", "1M", "6.8k", "10k", "47k", "100k", "1M",
]).expect("unexpected bad rated values set") ]).expect("unexpected bad rated values preset")
} }
/// A commonly used set of capacitor rated values. /// A commonly used set of capacitor rated values.
@@ -164,7 +164,7 @@ impl SpecGroup {
Self::from_iterator([ Self::from_iterator([
"10p", "22p", "33p", "47p", "68p", "100p", "150p", "220p", "330p", "470p", "560p", "10p", "22p", "33p", "47p", "68p", "100p", "150p", "220p", "330p", "470p", "560p",
"1u", "2.2u", "3.3u", "4.7u", "10u", "22u", "47u", "100u", "220u", "470u", "1u", "2.2u", "3.3u", "4.7u", "10u", "22u", "47u", "100u", "220u", "470u",
]).expect("unexpected bad rated values set") ]).expect("unexpected bad rated values preset")
} }
/// A commonly used set of inductor rated values. /// A commonly used set of inductor rated values.
@@ -172,7 +172,7 @@ impl SpecGroup {
Self::from_iterator([ Self::from_iterator([
"0.1u", "0.15u", "0.47u", "0.68u", "1u", "1.5u", "2.2u", "3.3u", "4.7u", "6.8u", "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", "8.2u", "10u", "15u", "22u", "33u", "47u", "68u", "100u",
]).expect("unexpected bad rated values set") ]).expect("unexpected bad rated values preset")
} }
fn save(&self) -> impl Iterator<Item = &str> { fn save(&self) -> impl Iterator<Item = &str> {
@@ -218,7 +218,7 @@ impl SpecGroup {
} }
/// Iterate over all numeric rated values in this group. /// Iterate over all numeric rated values in this group.
pub fn specs(&self) -> impl Iterator<Item = f64> + Clone { pub fn iter(&self) -> impl Iterator<Item = f64> + Clone {
self.specs.iter().map(|i| i.value) self.specs.iter().map(|i| i.value)
} }
} }
+11
View File
@@ -0,0 +1,11 @@
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 specs = spec::SpecCatalog::devices_preset();
}