refactor: finish refactor of kernel
This commit is contained in:
@@ -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 })
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user