refactor: finish refactor of kernel
This commit is contained in:
@@ -105,15 +105,15 @@ pub struct App {
|
|||||||
impl App {
|
impl App {
|
||||||
/// Create a new app with the given configuration.
|
/// Create a new app with the given configuration.
|
||||||
pub fn new(config: AppConfig) -> Result<Self> {
|
pub fn new(config: AppConfig) -> Result<Self> {
|
||||||
let datasets = SpecCatalog::from_file(
|
let sepcs = SpecCatalog::from_file(
|
||||||
config.get_resistor_dataset(),
|
config.get_resistor_spec(),
|
||||||
config.get_capacitor_dataset(),
|
config.get_capacitor_specs(),
|
||||||
config.get_inductor_dataset(),
|
config.get_inductor_specs(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let resolver: Box<dyn Resolver> = match config.get_resolver() {
|
let resolver: Box<dyn Resolver> = match config.get_resolver() {
|
||||||
AppResolver::Lut => Box::new(LutResolver::new(&datasets)?),
|
AppResolver::Lut => Box::new(LutResolver::new(&sepcs)?),
|
||||||
AppResolver::Bfs => Box::new(BfsResolver::new(datasets)),
|
AppResolver::Bfs => Box::new(BfsResolver::new(sepcs)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { resolver })
|
Ok(Self { resolver })
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use clap::{Parser, ValueEnum};
|
|||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
/// The resolver for the app.
|
/// The resolver for the app.
|
||||||
resolver: AppResolver,
|
resolver: AppResolver,
|
||||||
/// The path to the resistor dataset file.
|
/// The path to the resistor specs file.
|
||||||
resistor_dataset: PathBuf,
|
resistor_specs: PathBuf,
|
||||||
/// The path to the capacitor dataset file.
|
/// The path to the capacitor specs file.
|
||||||
capacitor_dataset: PathBuf,
|
capacitor_specs: PathBuf,
|
||||||
/// The path to the inductor dataset file.
|
/// The path to the inductor specs file.
|
||||||
inductor_dataset: PathBuf,
|
inductor_specs: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
@@ -19,17 +19,17 @@ impl AppConfig {
|
|||||||
pub fn get_resolver(&self) -> &AppResolver {
|
pub fn get_resolver(&self) -> &AppResolver {
|
||||||
&self.resolver
|
&self.resolver
|
||||||
}
|
}
|
||||||
/// Get the path to the resistor dataset file.
|
/// Get the path to the resistor specs file.
|
||||||
pub fn get_resistor_dataset(&self) -> &Path {
|
pub fn get_resistor_spec(&self) -> &Path {
|
||||||
&self.resistor_dataset
|
&self.resistor_specs
|
||||||
}
|
}
|
||||||
/// Get the path to the capacitor dataset file.
|
/// Get the path to the capacitor specs file.
|
||||||
pub fn get_capacitor_dataset(&self) -> &Path {
|
pub fn get_capacitor_specs(&self) -> &Path {
|
||||||
&self.capacitor_dataset
|
&self.capacitor_specs
|
||||||
}
|
}
|
||||||
/// Get the path to the inductor dataset file.
|
/// Get the path to the inductor specs file.
|
||||||
pub fn get_inductor_dataset(&self) -> &Path {
|
pub fn get_inductor_specs(&self) -> &Path {
|
||||||
&self.inductor_dataset
|
&self.inductor_specs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,41 +57,41 @@ struct Cli {
|
|||||||
#[arg(short = 's', long = "resolver", required = true, value_enum)]
|
#[arg(short = 's', long = "resolver", required = true, value_enum)]
|
||||||
resolver: AppResolver,
|
resolver: AppResolver,
|
||||||
|
|
||||||
/// The path to the resistor dataset file.
|
/// The path to the resistor specs file.
|
||||||
#[arg(
|
#[arg(
|
||||||
short = 'r',
|
short = 'r',
|
||||||
long = "resistor",
|
long = "resistor",
|
||||||
required = true,
|
required = true,
|
||||||
value_name = "RESISTOR.TXT"
|
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(
|
#[arg(
|
||||||
short = 'l',
|
short = 'l',
|
||||||
long = "inductor",
|
long = "inductor",
|
||||||
required = true,
|
required = true,
|
||||||
value_name = "INDUCTOR.TXT"
|
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(
|
#[arg(
|
||||||
short = 'c',
|
short = 'c',
|
||||||
long = "capacitor",
|
long = "capacitor",
|
||||||
required = true,
|
required = true,
|
||||||
value_name = "CAPACITOR.TXT"
|
value_name = "CAPACITOR.TXT"
|
||||||
)]
|
)]
|
||||||
capacitor_dataset: PathBuf,
|
capacitor_specs: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Cli> for AppConfig {
|
impl From<Cli> for AppConfig {
|
||||||
fn from(args: Cli) -> Self {
|
fn from(args: Cli) -> Self {
|
||||||
Self {
|
Self {
|
||||||
resolver: args.resolver,
|
resolver: args.resolver,
|
||||||
resistor_dataset: args.resistor_dataset,
|
resistor_specs: args.resistor_specs,
|
||||||
capacitor_dataset: args.capacitor_dataset,
|
capacitor_specs: args.capacitor_specs,
|
||||||
inductor_dataset: args.inductor_dataset,
|
inductor_specs: args.inductor_specs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ pub enum CircuitError {
|
|||||||
|
|
||||||
#[error("invalid target value: {0}")]
|
#[error("invalid target value: {0}")]
|
||||||
BadTargetValue(DeviceValueError),
|
BadTargetValue(DeviceValueError),
|
||||||
|
#[error("invalid pre-evaluated circuit value: {0}")]
|
||||||
|
BadCircuitValue(DeviceValueError),
|
||||||
#[error("bad previous evaluated joint value: {0}")]
|
#[error("bad previous evaluated joint value: {0}")]
|
||||||
BadPreviousValue(DeviceValueError),
|
BadPreviousValue(DeviceValueError),
|
||||||
#[error("floating point is invalid after arithmetic operation: {0}")]
|
#[error("floating point is invalid after arithmetic operation: {0}")]
|
||||||
@@ -268,34 +270,6 @@ impl Circuit {
|
|||||||
Ok(value)
|
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.
|
/// Get the device scale.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
@@ -370,4 +344,49 @@ pub struct CircuitEvaluation {
|
|||||||
pub unsigned_relative_difference: f64,
|
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
|
// endregion
|
||||||
|
|||||||
@@ -124,8 +124,11 @@ impl ResponseItem {
|
|||||||
// 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 circuit_evaluation =
|
let circuit_evaluation = CircuitEvaluation::from_circuit(
|
||||||
circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?;
|
&circuit,
|
||||||
|
request.get_device_kind(),
|
||||||
|
request.get_target_value(),
|
||||||
|
)?;
|
||||||
// Build self and return
|
// Build self and return
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
circuit,
|
circuit,
|
||||||
@@ -217,7 +220,8 @@ impl Response {
|
|||||||
}
|
}
|
||||||
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()))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{Resolver, ResolverError};
|
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::query::{Request, Response, ResponseError};
|
||||||
use crate::spec::{SpecCatalog, SpecGroup};
|
use crate::spec::{SpecCatalog, SpecGroup};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -26,7 +26,7 @@ pub enum BfsResolverError {
|
|||||||
pub struct BfsItem {
|
pub struct BfsItem {
|
||||||
/// The circuit represented by this item.
|
/// The circuit represented by this item.
|
||||||
circuit: Circuit,
|
circuit: Circuit,
|
||||||
/// The computed value of the circuit.
|
/// The evaluated value of the circuit.
|
||||||
value: f64,
|
value: f64,
|
||||||
/// 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.
|
||||||
unsigned_difference: f64,
|
unsigned_difference: f64,
|
||||||
@@ -37,8 +37,11 @@ impl BfsItem {
|
|||||||
pub fn new(circuit: Circuit, request: &Request) -> 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 eval =
|
let eval = CircuitEvaluation::from_circuit(
|
||||||
circuit.evaluate_with_target(request.get_target_value(), request.get_device_kind())?;
|
&circuit,
|
||||||
|
request.get_device_kind(),
|
||||||
|
request.get_target_value(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
circuit,
|
circuit,
|
||||||
@@ -52,7 +55,7 @@ impl BfsItem {
|
|||||||
&self.circuit
|
&self.circuit
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The computed value of the circuit.
|
/// The evaluated value of the circuit.
|
||||||
pub fn value(&self) -> f64 {
|
pub fn value(&self) -> f64 {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
@@ -74,8 +77,8 @@ impl BfsItem {
|
|||||||
|
|
||||||
/// A resolver that uses breadth first search to find the best matching circuits.
|
/// A resolver that uses breadth first search to find the best matching circuits.
|
||||||
pub struct BfsResolver {
|
pub struct BfsResolver {
|
||||||
/// The datasets for all device kinds.
|
/// The specs for all device kinds.
|
||||||
datasets: SpecCatalog,
|
specs: SpecCatalog,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BfsResolver {
|
impl BfsResolver {
|
||||||
@@ -92,7 +95,7 @@ impl BfsResolver {
|
|||||||
/// Iterate all possible circuits with one device without repeating equivalent topology.
|
/// Iterate all possible circuits with one device without repeating equivalent topology.
|
||||||
pub fn iter_one_device_circuit(specs: &SpecGroup) -> impl Iterator<Item = Circuit> {
|
pub fn iter_one_device_circuit(specs: &SpecGroup) -> impl Iterator<Item = Circuit> {
|
||||||
// Every single device is unique so we directly output them.
|
// 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
|
specs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v1| Circuit::from_one_device(v1).expect("unexpected failure on building circuit"))
|
.map(|v1| Circuit::from_one_device(v1).expect("unexpected failure on building circuit"))
|
||||||
@@ -146,16 +149,16 @@ impl BfsResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BfsResolver {
|
impl BfsResolver {
|
||||||
/// Create a new BFS resolver with the given datasets.
|
/// Create a new BFS resolver with the given specs.
|
||||||
pub fn new(datasets: SpecCatalog) -> Self {
|
pub fn new(specs: SpecCatalog) -> Self {
|
||||||
Self { datasets }
|
Self { specs }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pick_specs(&self, device_kind: DeviceKind) -> &SpecGroup {
|
fn pick_specs(&self, device_kind: DeviceKind) -> &SpecGroup {
|
||||||
match device_kind {
|
match device_kind {
|
||||||
DeviceKind::Resistor => self.datasets.resistor_specs(),
|
DeviceKind::Resistor => self.specs.resistor_specs(),
|
||||||
DeviceKind::Capacitor => self.datasets.capacitor_specs(),
|
DeviceKind::Capacitor => self.specs.capacitor_specs(),
|
||||||
DeviceKind::Inductor => self.datasets.inductor_specs(),
|
DeviceKind::Inductor => self.specs.inductor_specs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +175,7 @@ impl BfsResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn intern_resolve(&self, request: &Request) -> Result<Response, BfsResolverError> {
|
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());
|
let specs = self.pick_specs(request.get_device_kind());
|
||||||
|
|
||||||
// Create the result bucket.
|
// Create the result bucket.
|
||||||
@@ -205,7 +208,9 @@ impl Resolver for BfsResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion:
|
// endregion
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region: Result Bucket Helper
|
// 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
|
/// 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.
|
/// is less than the current maximum; the maximum is then evicted.
|
||||||
pub struct ResultBucket {
|
struct ResultBucket {
|
||||||
/// Maximum number of items the bucket can hold.
|
/// Maximum number of items the bucket can hold.
|
||||||
n: usize,
|
n: usize,
|
||||||
/// Max-heap of [`ResultBucketItem`].
|
/// 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.
|
/// The number of items currently in the bucket.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.heap.len()
|
self.heap.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the bucket is empty.
|
/// Whether the bucket is empty.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.heap.is_empty()
|
self.heap.is_empty()
|
||||||
}
|
}
|
||||||
|
|||||||
+139
-133
@@ -1,22 +1,24 @@
|
|||||||
use super::bfs::BfsResolver;
|
use super::bfs::BfsResolver;
|
||||||
use super::{Resolver, ResolverError};
|
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::spec::{SpecGroup, SpecCatalog};
|
||||||
use crate::query::{Request, Response, ResponseError};
|
use crate::query::{Request, Response, ResponseError};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
// region: LUT Resolver Kernel
|
||||||
|
|
||||||
/// Errors occurs in LUT resolver.
|
/// Errors occurs in LUT resolver.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum LutResolverError {
|
pub enum LutResolverError {
|
||||||
#[error("failed to build circuit: {0}")]
|
#[error("failed on evaluating circuit: {0}")]
|
||||||
Circuit(#[from] CircuitError),
|
CircuitCalculator(#[from] CircuitError),
|
||||||
#[error("failed on computing circuit properties: {0}")]
|
|
||||||
CircuitCalculator(#[from] CircuitCalculatorError),
|
|
||||||
#[error("fail to build response: {0}")]
|
#[error("fail to build response: {0}")]
|
||||||
Response(#[from] ResponseError),
|
Response(#[from] ResponseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region: LUT Item
|
||||||
|
|
||||||
/// An item in the lookup table.
|
/// An item in the lookup table.
|
||||||
pub struct LutItem {
|
pub struct LutItem {
|
||||||
/// The circuit represented by this item.
|
/// 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.
|
/// The ranged index for bisect LUT finding in resolver.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RangedIndex {
|
pub struct RangedIndex {
|
||||||
@@ -115,131 +248,4 @@ impl RangedIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A resolver that uses a lookup table to find the best matching circuit.
|
// endregion
|
||||||
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)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use lcrconn::spec;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_spec_preset() {
|
fn test_spec_preset() {
|
||||||
// All individual preset and catalog preset should nit panic
|
// All individual preset and catalog preset should nit panic
|
||||||
let specs = spec::SpecGroup::resistor_preset();
|
let _ = spec::SpecGroup::resistor_preset();
|
||||||
let specs = spec::SpecGroup::capacitor_preset();
|
let _ = spec::SpecGroup::capacitor_preset();
|
||||||
let specs = spec::SpecGroup::inductor_preset();
|
let _ = spec::SpecGroup::inductor_preset();
|
||||||
|
|
||||||
let specs = spec::SpecCatalog::devices_preset();
|
let _ = spec::SpecCatalog::devices_preset();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user