1
0

feat: use AI to migrate project (no fix now)

This commit is contained in:
2026-06-28 20:47:00 +08:00
parent 7665de0889
commit aa6c4f72bd
10 changed files with 2551 additions and 15 deletions

View File

@@ -0,0 +1,512 @@
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use thiserror::Error as TeError;
// /// The error thrown by LCR Connector.
// #[derive(Debug, TeError)]
// pub enum LcrConnError {
// #[error("Device value must be greater than 0")]
// InvalidDeviceValue,
// #[error("Third device cannot exist without second device")]
// ThirdDeviceWithoutSecond,
// #[error("No second device")]
// NoSecondDevice,
// #[error("No third device")]
// NoThirdDevice,
// #[error("Invalid value {0} in dataset")]
// InvalidDatasetValue(f64),
// #[error("Unexpected empty string in dataset item")]
// EmptyDatasetItem,
// #[error("Duplicate item {0} in standard value list")]
// DuplicateDatasetItem(String),
// #[error("Empty standard value list is not allowed")]
// EmptyDataset,
// #[error("Invalid value {0} for target value in request")]
// InvalidTargetValue(f64),
// #[error("Invalid value {0} for tolerance in request")]
// InvalidTolerance(f64),
// #[error("Too large or too less value {0} for response count limit in request")]
// InvalidCountLimit(usize),
// #[error("Invalid human readable value: {0}")]
// InvalidHumanReadableValue(String),
// #[error(transparent)]
// Io(#[from] std::io::Error),
// }
/// The kind of device.
#[derive(Debug, Clone, Copy)]
pub enum DeviceKind {
/// Resistor device.
Resistor,
/// Capacitor device.
Capacitor,
/// Inductor device.
Inductor,
}
/// The joint type between 2 devices.
#[derive(Debug, Clone, Copy, EnumIter)]
pub enum JointKind {
/// Series connection.
Series,
/// Parallel connection.
Parallel,
}
impl JointKind {
/// Flip the joint kind from series to parallel or vice versa.
///
/// # Returns
///
/// The flipped joint kind.
pub fn flip(self) -> Self {
match self {
JointKind::Series => JointKind::Parallel,
JointKind::Parallel => JointKind::Series,
}
}
}
/// The part of circuit composed of two devices and the joint kind.
#[derive(Debug, Clone)]
pub struct SubCircuit {
/// The value of the device.
device_value: f64,
/// The joint kind between this device and the next device.
joint_kind: JointKind,
}
impl SubCircuit {
/// Initialize subcircuit with given device value and joint kind.
///
/// # Panics
///
/// This function will panic if given device value is equal or lower than zero.
pub fn new(device_value: f64, joint_kind: JointKind) -> Self {
// Make sure value is greater than zero.
assert!(
device_value > 0f64,
"given device value {} should greater than zero",
device_value
);
// Okey, build and return self
Self {
device_value,
joint_kind,
}
}
/// Compute the joint value.
///
/// # Arguments
///
/// * `value` - The value computed from previous devices.
/// * `device_kind` - The kind of the device.
///
/// # Returns
///
/// The joint value computed.
///
/// # Errors
///
/// Returns [`LcrConnError::InvalidDeviceValue`] if any device value is not greater than 0.
pub fn compute(&self, value: f64, device_kind: DeviceKind) -> Result<f64, LcrConnError> {
if self.device_value <= 0.0 || value <= 0.0 {
return Err(LcrConnError::InvalidDeviceValue);
}
// We perform series connect for: series resistor, series inductor and parallel capacitor.
// We perform parallel connect for: parallel resistor, parallel inductor and series capacitor.
let joint_kind = if device_kind == DeviceKind::Capacitor {
self.joint_kind.flip()
} else {
self.joint_kind
};
Ok(match joint_kind {
JointKind::Series => self.device_value + value,
JointKind::Parallel => (self.device_value * value) / (self.device_value + value),
})
}
/// Get the device value.
pub fn device_value(&self) -> f64 {
self.device_value
}
/// Get the joint kind.
pub fn joint_kind(&self) -> JointKind {
self.joint_kind
}
}
/// The scale of devices in the circuit.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CircuitDeviceScale {
/// One device.
One,
/// Two devices.
Two,
/// Three devices.
Three,
}
impl CircuitDeviceScale {
/// Convert circuit device scale to device count.
///
/// # Returns
///
/// The device count.
pub fn to_device_count(self) -> usize {
match self {
CircuitDeviceScale::One => 1,
CircuitDeviceScale::Two => 2,
CircuitDeviceScale::Three => 3,
}
}
}
/// The circuit composed of multiple joints.
#[derive(Clone, Debug)]
pub struct Circuit {
/// The value of the first device.
first_device_value: f64,
/// The second device and its joint property.
second_device_subckt: Option<SubCircuit>,
/// The third device and its joint property.
third_device_subckt: Option<SubCircuit>,
}
impl Circuit {
/// Initialize the circuit.
///
/// # Arguments
///
/// * `first_device_value` - The value of the first device.
/// * `second_device_subckt` - The second device and its joint property.
/// * `third_device_subckt` - The third device and its joint property.
///
/// # Errors
///
/// Returns [`LcrConnError::ThirdDeviceWithoutSecond`] if a third device is provided
/// without a second device.
pub fn new(
first_device_value: f64,
second_device_subckt: Option<SubCircuit>,
third_device_subckt: Option<SubCircuit>,
) -> Result<Self, LcrConnError> {
if second_device_subckt.is_none() && third_device_subckt.is_some() {
return Err(LcrConnError::ThirdDeviceWithoutSecond);
}
Ok(Self {
first_device_value,
second_device_subckt,
third_device_subckt,
})
}
/// Create a circuit from a single device.
pub fn from_one_device(device1_value: f64) -> Self {
Self {
first_device_value: device1_value,
second_device_subckt: None,
third_device_subckt: None,
}
}
/// Create a circuit from two devices.
pub fn from_two_devices(
device1_value: f64,
device2_value: f64,
device2_joint: JointKind,
) -> Self {
Self {
first_device_value: device1_value,
second_device_subckt: Some(SubCircuit::new(device2_value, device2_joint)),
third_device_subckt: None,
}
}
/// Create a circuit from three devices.
pub fn from_three_devices(
device1_value: f64,
device2_value: f64,
device2_joint: JointKind,
device3_value: f64,
device3_joint: JointKind,
) -> Self {
Self {
first_device_value: device1_value,
second_device_subckt: Some(SubCircuit::new(device2_value, device2_joint)),
third_device_subckt: Some(SubCircuit::new(device3_value, device3_joint)),
}
}
/// Compute the circuit value.
///
/// # Arguments
///
/// * `device_kind` - The kind of the device.
///
/// # Returns
///
/// The circuit value.
///
/// # Errors
///
/// Returns [`LcrConnError::InvalidDeviceValue`] if any device value is not greater than 0.
pub fn compute(&self, device_kind: DeviceKind) -> Result<f64, LcrConnError> {
if self.first_device_value <= 0.0 {
return Err(LcrConnError::InvalidDeviceValue);
}
let mut value = self.first_device_value;
if let Some(subckt) = &self.second_device_subckt {
value = subckt.compute(value, device_kind)?;
} else {
return Ok(value);
}
if let Some(subckt) = &self.third_device_subckt {
value = subckt.compute(value, device_kind)?;
}
Ok(value)
}
/// Get the device scale.
///
/// # Returns
///
/// The device scale.
pub fn device_scale(&self) -> CircuitDeviceScale {
if self.third_device_subckt.is_some() {
CircuitDeviceScale::Three
} else if self.second_device_subckt.is_some() {
CircuitDeviceScale::Two
} else {
CircuitDeviceScale::One
}
}
/// Get the value of the first device.
pub fn first_device_value(&self) -> f64 {
self.first_device_value
}
/// Get the joint kind of the second device.
///
/// # Errors
///
/// Returns [`LcrConnError::NoSecondDevice`] if there is no second device.
pub fn second_device_joint(&self) -> Result<JointKind, LcrConnError> {
self.second_device_subckt
.map(|s| s.joint_kind())
.ok_or(LcrConnError::NoSecondDevice)
}
/// Get the value of the second device.
///
/// # Errors
///
/// Returns [`LcrConnError::NoSecondDevice`] if there is no second device.
pub fn second_device_value(&self) -> Result<f64, LcrConnError> {
self.second_device_subckt
.map(|s| s.device_value())
.ok_or(LcrConnError::NoSecondDevice)
}
/// Get the joint kind of the third device.
///
/// # Errors
///
/// Returns [`LcrConnError::NoThirdDevice`] if there is no third device.
pub fn third_device_joint(&self) -> Result<JointKind, LcrConnError> {
self.third_device_subckt
.map(|s| s.joint_kind())
.ok_or(LcrConnError::NoThirdDevice)
}
/// Get the value of the third device.
///
/// # Errors
///
/// Returns [`LcrConnError::NoThirdDevice`] if there is no third device.
pub fn third_device_value(&self) -> Result<f64, LcrConnError> {
self.third_device_subckt
.map(|s| s.device_value())
.ok_or(LcrConnError::NoThirdDevice)
}
}
/// The helper for circuit value computation.
#[derive(Clone, Debug)]
pub struct CircuitValueTrait {
/// The kind of the device.
device_kind: DeviceKind,
/// The target value.
target_value: f64,
}
impl CircuitValueTrait {
pub fn new(device_kind: DeviceKind, target_value: f64) -> Self {
Self {
device_kind,
target_value,
}
}
/// The value of this circuit.
///
/// # Arguments
///
/// * `circuit` - The circuit for computation.
///
/// # Returns
///
/// The value.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn value(&self, circuit: &Circuit) -> Result<f64, LcrConnError> {
circuit.compute(self.device_kind)
}
/// The signed difference between the target value and the value of this circuit.
///
/// 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.
///
/// # Arguments
///
/// * `circuit` - The circuit for computation.
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
/// for reducing computation steps, or `None` if you request this method to compute the value.
///
/// # Returns
///
/// The signed difference.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn difference(&self, circuit: &Circuit, value: Option<f64>) -> Result<f64, LcrConnError> {
let value = match value {
Some(v) => v,
None => self.value(circuit)?,
};
Ok(value - self.target_value)
}
/// The unsigned difference between the target value and the value of this circuit.
///
/// # Arguments
///
/// * `circuit` - The circuit for computation.
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
/// for reducing computation steps, or `None` if you request this method to compute the value.
/// * `difference` - The difference of the circuit computed by the
/// [`difference`](Self::difference) method for reducing computation steps,
/// or `None` if you request this method to compute the difference.
///
/// # Returns
///
/// The unsigned difference.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn unsigned_difference(
&self,
circuit: &Circuit,
value: Option<f64>,
difference: Option<f64>,
) -> Result<f64, LcrConnError> {
let diff = match difference {
Some(d) => d,
None => self.difference(circuit, value)?,
};
Ok(diff.abs())
}
/// The signed relative difference between the target value and the value of this circuit.
///
/// 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.
///
/// # Arguments
///
/// * `circuit` - The circuit for computation.
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
/// for reducing computation steps, or `None` if you request this method to compute the value.
/// * `difference` - The difference of the circuit computed by the
/// [`difference`](Self::difference) method for reducing computation steps,
/// or `None` if you request this method to compute the difference.
///
/// # Returns
///
/// The signed relative difference.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn relative_difference(
&self,
circuit: &Circuit,
value: Option<f64>,
difference: Option<f64>,
) -> Result<f64, LcrConnError> {
let diff = match difference {
Some(d) => d,
None => self.difference(circuit, value)?,
};
Ok(diff / self.target_value)
}
/// The unsigned relative difference between the target value and the value of this circuit.
///
/// # Arguments
///
/// * `circuit` - The circuit for computation.
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
/// for reducing computation steps, or `None` if you request this method to compute the value.
/// * `difference` - The difference of the circuit computed by the
/// [`difference`](Self::difference) method for reducing computation steps,
/// or `None` if you request this method to compute the difference.
/// * `relative_difference` - The relative difference of the circuit computed by the
/// [`relative_difference`](Self::relative_difference) method for reducing computation steps,
/// or `None` if you request this method to compute the relative difference.
///
/// # Returns
///
/// The unsigned relative difference.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn unsigned_relative_difference(
&self,
circuit: &Circuit,
value: Option<f64>,
difference: Option<f64>,
relative_difference: Option<f64>,
) -> Result<f64, LcrConnError> {
let rel_diff = match relative_difference {
Some(rd) => rd,
None => self.relative_difference(circuit, value, difference)?,
};
Ok(rel_diff.abs())
}
}

View File

@@ -0,0 +1,445 @@
use std::collections::HashSet;
use std::path::Path;
use crate::common::LcrConnError;
/// An item in the dataset.
#[derive(Clone, Debug)]
pub struct DatasetItem {
/// The actual value of this item.
pub value: f64,
/// The string form of this value given from original input for re-saving.
pub str_value: String,
}
impl DatasetItem {
/// Create a new dataset item with validation.
///
/// # Errors
///
/// Returns [`LcrConnError::InvalidDatasetValue`] if the value is not greater than 0.
/// Returns [`LcrConnError::EmptyDatasetItem`] if the string value is empty.
pub fn new(value: f64, str_value: String) -> Result<Self, LcrConnError> {
if value <= 0.0 {
return Err(LcrConnError::InvalidDatasetValue(value));
}
if str_value.is_empty() {
return Err(LcrConnError::EmptyDatasetItem);
}
Ok(Self { value, str_value })
}
}
/// A list holding available standard values for resistor, capacitor or inductor.
///
/// Standard values is a collection of all possible values of specific device manufactured
/// by electronic factory. In reality, it also can be replaced by all possible values of
/// specific device provided by your laboratory. For example, your laboratory only provide
/// resistor with 100 Ohm and 4.7k Ohm. This list will only contain 100 and 4.7k.
pub struct Dataset {
/// A list of available device gauge values.
items: Vec<DatasetItem>,
}
impl Dataset {
/// Create a dataset from an iterable of stringified values.
///
/// # Errors
///
/// Returns [`LcrConnError::DuplicateDatasetItem`] if duplicate values are found.
/// Returns [`LcrConnError::EmptyDataset`] if the iterable produces no items.
/// Returns [`LcrConnError::InvalidHumanReadableValue`] if a value cannot be parsed.
pub fn from_iterable<I, S>(str_values: I) -> Result<Self, LcrConnError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
// Check string form value one by one
let mut items: Vec<DatasetItem> = Vec::new();
let mut seen: HashSet<f64> = HashSet::new();
for str_value_raw in str_values {
let str_value = str_value_raw.into();
// Try parsing value
let value = from_human_readable_value(&str_value)?;
// Check and update set
if !seen.insert(value) {
return Err(LcrConnError::DuplicateDatasetItem(str_value));
}
// Add into result
items.push(DatasetItem::new(value, str_value)?);
}
// Check empty case
if items.is_empty() {
return Err(LcrConnError::EmptyDataset);
}
// Ok, assign it
Ok(Self { items })
}
/// Load a dataset from a block of text.
///
/// Each non-empty line (after trimming whitespace) is treated as a value.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn from_text(text: &str) -> Result<Self, LcrConnError> {
let lines: Vec<String> = text
.lines()
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.collect();
Self::from_iterable(lines)
}
/// Load a dataset from a file.
///
/// # Errors
///
/// Returns [`LcrConnError::Io`] if the file cannot be read.
/// See [`Dataset::from_iterable`] for other errors.
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, LcrConnError> {
let text = std::fs::read_to_string(path)?;
Self::from_text(&text)
}
/// The preset dataset for resistors.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn resistor_preset() -> Result<Self, LcrConnError> {
Self::from_iterable([
"100", "220", "270", "390", "470", "680", "1k", "1.2k", "1.5k", "2.2k", "3.3k",
"4.7k", "6.8k", "10k", "47k", "100k", "1M",
])
}
/// The preset dataset for capacitors.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn capacitor_preset() -> Result<Self, LcrConnError> {
Self::from_iterable([
"10p", "22p", "33p", "47p", "68p", "100p", "150p", "220p", "330p", "470p", "560p",
"1u", "2.2u", "3.3u", "4.7u", "10u", "22u", "47u", "100u", "220u", "470u",
])
}
/// The preset dataset for inductors.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn inductor_preset() -> Result<Self, LcrConnError> {
Self::from_iterable([
"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",
])
}
/// Get the string form of all values joined by newlines.
pub fn save_text(&self) -> String {
self.items
.iter()
.map(|i| i.str_value.as_str())
.collect::<Vec<_>>()
.join("\n")
}
/// Save all values to a file.
///
/// # Errors
///
/// Returns [`LcrConnError::Io`] if the file cannot be written.
pub fn save_file(&self, path: impl AsRef<Path>) -> Result<(), LcrConnError> {
std::fs::write(path, self.save_text())?;
Ok(())
}
/// Get the available standard values as an iterator of `f64`.
pub fn values(&self) -> impl Iterator<Item = f64> + '_ {
self.items.iter().map(|i| i.value)
}
/// Get the underlying dataset items as a slice.
pub fn items(&self) -> &[DatasetItem] {
&self.items
}
}
/// The collection holding all standard values for resistor, capacitor and inductor respectively.
pub struct DatasetCollection {
/// A list of available device gauge values for resistor.
resistor: Dataset,
/// A list of available device gauge values for capacitor.
capacitor: Dataset,
/// A list of available device gauge values for inductor.
inductor: Dataset,
}
impl DatasetCollection {
pub fn new(resistor: Dataset, capacitor: Dataset, inductor: Dataset) -> Self {
Self {
resistor,
capacitor,
inductor,
}
}
/// Load the standard values for resistor, capacitor and inductor respectively from iterables.
///
/// # Arguments
///
/// * `resistor` - The iterable to load available standard values for resistor.
/// * `capacitor` - The iterable to load available standard values for capacitor.
/// * `inductor` - The iterable to load available standard values for inductor.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn from_iterable<I1, S1, I2, S2, I3, S3>(
resistor: I1,
capacitor: I2,
inductor: I3,
) -> Result<Self, LcrConnError>
where
I1: IntoIterator<Item = S1>,
S1: Into<String>,
I2: IntoIterator<Item = S2>,
S2: Into<String>,
I3: IntoIterator<Item = S3>,
S3: Into<String>,
{
Ok(Self {
resistor: Dataset::from_iterable(resistor)?,
capacitor: Dataset::from_iterable(capacitor)?,
inductor: Dataset::from_iterable(inductor)?,
})
}
/// Load the standard values from strings.
///
/// # Arguments
///
/// * `resistor` - The string to load available standard values for resistor.
/// * `capacitor` - The string to load available standard values for capacitor.
/// * `inductor` - The string to load available standard values for inductor.
///
/// # Errors
///
/// See [`Dataset::from_text`].
pub fn from_text(resistor: &str, capacitor: &str, inductor: &str) -> Result<Self, LcrConnError> {
Ok(Self {
resistor: Dataset::from_text(resistor)?,
capacitor: Dataset::from_text(capacitor)?,
inductor: Dataset::from_text(inductor)?,
})
}
/// Load the standard values from files.
///
/// # Arguments
///
/// * `resistor` - The file to load available standard values for resistor.
/// * `capacitor` - The file to load available standard values for capacitor.
/// * `inductor` - The file to load available standard values for inductor.
///
/// # Errors
///
/// See [`Dataset::from_file`].
pub fn from_file(
resistor: impl AsRef<Path>,
capacitor: impl AsRef<Path>,
inductor: impl AsRef<Path>,
) -> Result<Self, LcrConnError> {
Ok(Self {
resistor: Dataset::from_file(resistor)?,
capacitor: Dataset::from_file(capacitor)?,
inductor: Dataset::from_file(inductor)?,
})
}
/// The preset dataset collection for all devices.
///
/// # Errors
///
/// See [`Dataset::from_iterable`].
pub fn devices_preset() -> Result<Self, LcrConnError> {
Ok(Self {
resistor: Dataset::resistor_preset()?,
capacitor: Dataset::capacitor_preset()?,
inductor: Dataset::inductor_preset()?,
})
}
/// Get the string form of all values.
///
/// # Returns
///
/// A tuple of strings for resistor, capacitor and inductor respectively.
pub fn save_text(&self) -> (String, String, String) {
(
self.resistor.save_text(),
self.capacitor.save_text(),
self.inductor.save_text(),
)
}
/// Save all values to files.
///
/// # Arguments
///
/// * `resistor` - The file to save available standard values for resistor.
/// * `capacitor` - The file to save available standard values for capacitor.
/// * `inductor` - The file to save available standard values for inductor.
///
/// # Errors
///
/// Returns [`LcrConnError::Io`] if any file cannot be written.
pub fn save_file(
&self,
resistor: impl AsRef<Path>,
capacitor: impl AsRef<Path>,
inductor: impl AsRef<Path>,
) -> Result<(), LcrConnError> {
self.resistor.save_file(resistor)?;
self.capacitor.save_file(capacitor)?;
self.inductor.save_file(inductor)?;
Ok(())
}
/// Get the dataset for resistor.
pub fn resistor(&self) -> &Dataset {
&self.resistor
}
/// Get the dataset for capacitor.
pub fn capacitor(&self) -> &Dataset {
&self.capacitor
}
/// Get the dataset for inductor.
pub fn inductor(&self) -> &Dataset {
&self.inductor
}
}
/// Convert human readable value to float.
///
/// # Arguments
///
/// * `strl` - The human readable value.
///
/// # Returns
///
/// The parsed float value.
///
/// # Errors
///
/// Returns [`LcrConnError::InvalidHumanReadableValue`] if the input string is not a valid number.
pub fn from_human_readable_value(strl: &str) -> Result<f64, LcrConnError> {
let strl = strl.trim();
let (num_part, multiplier) = if let Some(stripped) = strl.strip_suffix('n') {
(stripped, 1e-12)
} else if let Some(stripped) = strl.strip_suffix('p') {
(stripped, 1e-9)
} else if let Some(stripped) = strl.strip_suffix('u') {
(stripped, 1e-6)
} else if let Some(stripped) = strl.strip_suffix('m') {
(stripped, 1e-3)
} else if let Some(stripped) = strl.strip_suffix('k') {
(stripped, 1e3)
} else if let Some(stripped) = strl.strip_suffix('M') {
(stripped, 1e6)
} else if let Some(stripped) = strl.strip_suffix('G') {
(stripped, 1e9)
} else {
(strl, 1.0)
};
num_part
.parse::<f64>()
.map(|v| v * multiplier)
.map_err(|_| LcrConnError::InvalidHumanReadableValue(strl.to_string()))
}
/// The unit scale for human readable value.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnitScale {
NanoLower,
Nano,
Micro,
Milli,
None,
Kilo,
Mega,
Giga,
GigaHigher,
}
/// Get the unit scale of human readable value.
///
/// # Arguments
///
/// * `v` - The value.
///
/// # Returns
///
/// The unit scale.
pub fn get_human_readable_value_scale(v: f64) -> UnitScale {
let v = v.abs();
if v < 1e-12 {
UnitScale::NanoLower
} else if v < 1e-9 {
UnitScale::Nano
} else if v < 1e-6 {
UnitScale::Micro
} else if v < 1e-3 {
UnitScale::Milli
} else if v < 1e3 {
UnitScale::None
} else if v < 1e6 {
UnitScale::Kilo
} else if v < 1e9 {
UnitScale::Mega
} else if v < 1e12 {
UnitScale::Giga
} else {
UnitScale::GigaHigher
}
}
/// Convert float value to human readable value.
///
/// # Arguments
///
/// * `v` - The float value.
///
/// # Returns
///
/// The human readable value.
pub fn to_human_readable_value(v: f64) -> String {
let scale = get_human_readable_value_scale(v);
match scale {
UnitScale::NanoLower => format!("{:+.4e} n", v / 1e-12),
UnitScale::Nano => format!("{:+.4f} p", v / 1e-9),
UnitScale::Micro => format!("{:+.4f} u", v / 1e-6),
UnitScale::Milli => format!("{:+.4f} m", v / 1e-3),
UnitScale::None => {
// YYC MARK:
// The space of this format string is by design
// for keeping the same style with other format strings.
format!("{:+.4} ", v)
}
UnitScale::Kilo => format!("{:+.4f} k", v / 1e3),
UnitScale::Mega => format!("{:+.4f} M", v / 1e6),
UnitScale::Giga => format!("{:+.4f} G", v / 1e9),
UnitScale::GigaHigher => format!("{:+.4e} G", v / 1e9),
}
}

View File

@@ -1,14 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
pub mod common;
pub mod dataset;
pub mod query;
pub mod resolver;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub use common::{
Circuit, CircuitDeviceScale, CircuitValueTrait, DeviceKind, JointKind, LcrConnError, SubCircuit,
};
pub use dataset::{
from_human_readable_value, get_human_readable_value_scale, to_human_readable_value, Dataset,
DatasetCollection, DatasetItem, UnitScale,
};
pub use query::{Request, Response, ResponseItem, ResponsePriority, MAX_RESPONSE_CNT};
pub use resolver::{BfsResolver, LutResolver, Resolver};

256
kernel/lcrconn/src/query.rs Normal file
View File

@@ -0,0 +1,256 @@
use std::cmp::Ordering;
use std::ops::Index;
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, LcrConnError};
/// The priority of the result.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResponsePriority {
/// Less devices is the first priority.
LessDevices,
/// More accuracy is the first priority.
MoreAccuracy,
}
/// The maximum count for the response item count passed in request.
pub const MAX_RESPONSE_CNT: usize = 50;
/// All request information for the resolver.
#[derive(Clone, Debug)]
pub struct Request {
/// The kind of device to resolve.
pub device_kind: DeviceKind,
/// The target value of the device.
pub target_value: f64,
/// The tolerance of the device in absolute value.
pub tolerance: f64,
/// The priority principle when sorting response items.
pub response_priority: ResponsePriority,
/// The limited count of results.
pub count_limit: usize,
}
impl Request {
/// Create a new request with validation.
///
/// # Errors
///
/// Returns [`LcrConnError::InvalidTargetValue`] if the target value is not greater than 0.
/// Returns [`LcrConnError::InvalidTolerance`] if the tolerance is negative.
/// Returns [`LcrConnError::InvalidCountLimit`] if the count limit is 0 or exceeds
/// [`MAX_RESPONSE_CNT`].
pub fn new(
device_kind: DeviceKind,
target_value: f64,
tolerance: f64,
response_priority: ResponsePriority,
count_limit: usize,
) -> Result<Self, LcrConnError> {
if target_value <= 0.0 {
return Err(LcrConnError::InvalidTargetValue(target_value));
}
if tolerance < 0.0 {
return Err(LcrConnError::InvalidTolerance(tolerance));
}
if count_limit == 0 || count_limit > MAX_RESPONSE_CNT {
return Err(LcrConnError::InvalidCountLimit(count_limit));
}
Ok(Self {
device_kind,
target_value,
tolerance,
response_priority,
count_limit,
})
}
}
/// The possible solution given by the resolver.
#[derive(Clone, Debug)]
pub struct ResponseItem {
/// The circuit of this response item.
circuit: Circuit,
/// The device count of this circuit.
device_count: usize,
/// The value of this circuit.
value: f64,
/// The signed difference between the target value and the value of this circuit.
///
/// 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.
difference: f64,
/// The unsigned difference between the target value and the value of this circuit.
unsigned_difference: f64,
/// The signed relative difference between the target value and the value of this circuit.
///
/// 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.
relative_difference: f64,
/// The unsigned relative difference between the target value and the value of this circuit.
unsigned_relative_difference: f64,
}
impl ResponseItem {
/// Create a new response item by computing all values eagerly.
///
/// # Errors
///
/// See [`CircuitValueTrait::value`].
pub fn new(circuit: Circuit, cv_trait: &CircuitValueTrait) -> Result<Self, LcrConnError> {
let value = cv_trait.value(&circuit)?;
let difference = cv_trait.difference(&circuit, Some(value))?;
let unsigned_difference = cv_trait.unsigned_difference(&circuit, None, Some(difference))?;
let relative_difference = cv_trait.relative_difference(&circuit, None, Some(difference))?;
let unsigned_relative_difference =
cv_trait.unsigned_relative_difference(&circuit, None, None, Some(relative_difference))?;
let device_count = circuit.device_scale().to_device_count();
Ok(Self {
circuit,
device_count,
value,
difference,
unsigned_difference,
relative_difference,
unsigned_relative_difference,
})
}
/// The circuit of this response item.
pub fn circuit(&self) -> &Circuit {
&self.circuit
}
/// The device count of this circuit.
pub fn device_count(&self) -> usize {
self.device_count
}
/// The value of this circuit.
pub fn value(&self) -> f64 {
self.value
}
/// The signed difference between the target value and the value of this circuit.
///
/// 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.
pub fn difference(&self) -> f64 {
self.difference
}
/// The unsigned difference between the target value and the value of this circuit.
pub fn unsigned_difference(&self) -> f64 {
self.unsigned_difference
}
/// The signed relative difference between the target value and the value of this circuit.
///
/// 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.
pub fn relative_difference(&self) -> f64 {
self.relative_difference
}
/// The unsigned relative difference between the target value and the value of this circuit.
pub fn unsigned_relative_difference(&self) -> f64 {
self.unsigned_relative_difference
}
}
/// The collection of possible solutions given by the resolver.
///
/// For getting the response items, please use `response[index]` or `response.get(index)`.
/// For iterating the response items, please use the `into_iter()` method.
/// For getting the count of response items, please use the `len()` method.
pub struct Response {
/// The kind of device of this response.
device_kind: DeviceKind,
/// The sorted items by priority and difference.
sorted_items: Vec<ResponseItem>,
}
impl Response {
/// Create a new response from request and candidate circuits.
///
/// The candidates are sorted by the priority specified in the request and then truncated
/// to the count limit.
///
/// # Errors
///
/// See [`ResponseItem::new`].
pub fn new(
request: &Request,
candidates: impl IntoIterator<Item = Circuit>,
) -> Result<Self, LcrConnError> {
let cv_trait = CircuitValueTrait::new(request.device_kind, request.target_value);
let mut items: Vec<ResponseItem> = candidates
.into_iter()
.map(|c| ResponseItem::new(c, &cv_trait))
.collect::<Result<_, _>>()?;
// Sort by different strategy
match request.response_priority {
ResponsePriority::LessDevices => {
items.sort_by(|a, b| {
a.device_count
.cmp(&b.device_count)
.then_with(|| {
a.unsigned_difference
.partial_cmp(&b.unsigned_difference)
.unwrap_or(Ordering::Equal)
})
});
}
ResponsePriority::MoreAccuracy => {
items.sort_by(|a, b| {
a.unsigned_difference
.partial_cmp(&b.unsigned_difference)
.unwrap_or(Ordering::Equal)
});
}
}
// Cut item by limit
items.truncate(request.count_limit);
Ok(Self {
device_kind: request.device_kind,
sorted_items: items,
})
}
/// The kind of device of this response.
pub fn device_kind(&self) -> DeviceKind {
self.device_kind
}
/// The number of response items.
pub fn len(&self) -> usize {
self.sorted_items.len()
}
/// Whether the response is empty.
pub fn is_empty(&self) -> bool {
self.sorted_items.is_empty()
}
/// Get a response item by index.
pub fn get(&self, index: usize) -> Option<&ResponseItem> {
self.sorted_items.get(index)
}
/// Iterate over response items by reference.
pub fn iter(&self) -> impl Iterator<Item = &ResponseItem> {
self.sorted_items.iter()
}
}
impl Index<usize> for Response {
type Output = ResponseItem;
fn index(&self, index: usize) -> &Self::Output {
&self.sorted_items[index]
}
}

View File

@@ -0,0 +1,481 @@
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::iter::FusedIterator;
use super::Resolver;
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, JointKind, LcrConnError};
use crate::dataset::{Dataset, DatasetCollection, DatasetItem};
use crate::query::{Request, Response};
// ============================================================================
// Lazy iterator structs for circuit generation
// ============================================================================
// 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.
/// Iterator over all possible one-device circuits without repeating equivalent topology.
pub struct OneDeviceCircuitIter<'a> {
items: &'a [DatasetItem],
pos: usize,
}
impl<'a> OneDeviceCircuitIter<'a> {
pub fn new(items: &'a [DatasetItem]) -> Self {
Self { items, pos: 0 }
}
}
impl Iterator for OneDeviceCircuitIter<'_> {
type Item = Circuit;
fn next(&mut self) -> Option<Self::Item> {
if self.pos < self.items.len() {
// Every single device is unique so we directly output them.
// This feature is insured by dataset itself.
let circuit = Circuit::from_one_device(self.items[self.pos].value);
self.pos += 1;
Some(circuit)
} else {
None
}
}
}
impl FusedIterator for OneDeviceCircuitIter<'_> {}
/// Iterator over all possible two-device circuits without repeating equivalent topology.
pub struct TwoDeviceCircuitIter<'a> {
items: &'a [DatasetItem],
i: usize,
j: usize,
joint_idx: usize,
}
impl<'a> TwoDeviceCircuitIter<'a> {
pub fn new(items: &'a [DatasetItem]) -> Self {
Self {
items,
i: 0,
j: 0,
joint_idx: 0,
}
}
}
impl Iterator for TwoDeviceCircuitIter<'_> {
type Item = Circuit;
fn next(&mut self) -> Option<Self::Item> {
let n = self.items.len();
if n == 0 {
return None;
}
loop {
if self.joint_idx < JointKind::ALL.len() {
let jk = JointKind::ALL[self.joint_idx];
self.joint_idx += 1;
// The two devices in this circuit is always swapable,
// so we iterate them without repeating.
return Some(Circuit::from_two_devices(
self.items[self.i].value,
self.items[self.j].value,
jk,
));
}
// Advance to next combination
self.joint_idx = 0;
self.j += 1;
if self.j >= n {
self.i += 1;
self.j = self.i;
if self.i >= n {
return None;
}
}
}
}
}
impl FusedIterator for TwoDeviceCircuitIter<'_> {}
/// Iterator over three-device circuits where both joints share the same type.
///
/// In this case, all 3 devices are swapable and are iterated without repeating.
pub struct ThreeDeviceSameJointIter<'a> {
items: &'a [DatasetItem],
i: usize,
j: usize,
k: usize,
joint_idx: usize,
}
impl<'a> ThreeDeviceSameJointIter<'a> {
pub fn new(items: &'a [DatasetItem]) -> Self {
Self {
items,
i: 0,
j: 0,
k: 0,
joint_idx: 0,
}
}
}
impl Iterator for ThreeDeviceSameJointIter<'_> {
type Item = Circuit;
fn next(&mut self) -> Option<Self::Item> {
let n = self.items.len();
if n == 0 {
return None;
}
loop {
if self.joint_idx < JointKind::ALL.len() {
let jk = JointKind::ALL[self.joint_idx];
self.joint_idx += 1;
return Some(Circuit::from_three_devices(
self.items[self.i].value,
self.items[self.j].value,
jk,
self.items[self.k].value,
jk,
));
}
self.joint_idx = 0;
self.k += 1;
if self.k >= n {
self.j += 1;
self.k = self.j;
if self.j >= n {
self.i += 1;
self.j = self.i;
self.k = self.i;
if self.i >= n {
return None;
}
}
}
}
}
}
impl FusedIterator for ThreeDeviceSameJointIter<'_> {}
/// Iterator over three-device circuits where the two joint types differ.
///
/// In this case, the first 2 devices are swapable and are iterated without repeating,
/// while the third device iterates over all values independently.
pub struct ThreeDeviceDiffJointIter<'a> {
items: &'a [DatasetItem],
i: usize,
j: usize,
k: usize,
joint_idx: usize,
}
impl<'a> ThreeDeviceDiffJointIter<'a> {
pub fn new(items: &'a [DatasetItem]) -> Self {
Self {
items,
i: 0,
j: 0,
k: 0,
joint_idx: 0,
}
}
}
impl Iterator for ThreeDeviceDiffJointIter<'_> {
type Item = Circuit;
fn next(&mut self) -> Option<Self::Item> {
let n = self.items.len();
if n == 0 {
return None;
}
loop {
if self.joint_idx < JointKind::ALL.len() {
let j = JointKind::ALL[self.joint_idx];
self.joint_idx += 1;
return Some(Circuit::from_three_devices(
self.items[self.i].value,
self.items[self.j].value,
j,
self.items[self.k].value,
j.flip(),
));
}
self.joint_idx = 0;
self.k += 1;
if self.k >= n {
self.j += 1;
self.k = 0;
if self.j >= n {
self.i += 1;
self.j = self.i;
self.k = 0;
if self.i >= n {
return None;
}
}
}
}
}
}
impl FusedIterator for ThreeDeviceDiffJointIter<'_> {}
/// Type alias for the chained three-device circuit iterator.
pub type ThreeDeviceCircuitIter<'a> = std::iter::Chain<
ThreeDeviceSameJointIter<'a>,
ThreeDeviceDiffJointIter<'a>,
>;
// ============================================================================
// BfsItem
// ============================================================================
/// The entry used in BFS iteration storing circuit and value.
pub struct BfsItem {
/// The circuit represented by this item.
circuit: Circuit,
/// The computed value of the circuit.
value: f64,
/// The unsigned difference between the target value and the value of this circuit.
unsigned_difference: f64,
}
impl BfsItem {
/// Create a new BFS item by computing values eagerly.
///
/// # Errors
///
/// See [`CircuitValueTrait::value`].
pub fn new(circuit: Circuit, cv_trait: &CircuitValueTrait) -> Result<Self, LcrConnError> {
let value = cv_trait.value(&circuit)?;
let unsigned_difference = cv_trait.unsigned_difference(&circuit, Some(value))?;
Ok(Self {
circuit,
value,
unsigned_difference,
})
}
/// The circuit represented by this item.
pub fn circuit(&self) -> &Circuit {
&self.circuit
}
/// The computed value of the circuit.
pub fn value(&self) -> f64 {
self.value
}
/// The unsigned difference between the target value and the value of this circuit.
pub fn unsigned_difference(&self) -> f64 {
self.unsigned_difference
}
/// Consume this item and return the inner circuit.
pub fn into_circuit(self) -> Circuit {
self.circuit
}
}
// ============================================================================
// ResultBucket
// ============================================================================
/// An item stored in a [`ResultBucket`].
struct ResultBucketItem {
/// The score associated with this item.
score: f64,
/// The underlying BfsItem.
item: BfsItem,
/// Monotonic counter used as a tiebreaker when scores are equal,
/// ensuring that BinaryHeap never compares BfsItem directly.
seq: usize,
}
impl ResultBucketItem {
fn new(score: f64, item: BfsItem, seq: usize) -> Self {
Self { score, item, seq }
}
}
impl PartialEq for ResultBucketItem {
fn eq(&self, other: &Self) -> bool {
self.score == other.score && self.seq == other.seq
}
}
impl Eq for ResultBucketItem {}
impl PartialOrd for ResultBucketItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ResultBucketItem {
fn cmp(&self, other: &Self) -> Ordering {
// BinaryHeap is a max-heap: the greatest element is at the top.
// We want the entry with the largest score at the top.
match self.score.partial_cmp(&other.score) {
Some(Ordering::Equal) | None => self.seq.cmp(&other.seq),
Some(ord) => ord,
}
}
}
/// 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
/// is less than the current maximum; the maximum is then evicted.
pub struct ResultBucket {
/// Maximum number of items the bucket can hold.
n: usize,
/// Max-heap of [`ResultBucketItem`].
/// The entry with the largest score sits at index 0.
heap: BinaryHeap<ResultBucketItem>,
/// Monotonic counter fed to each [`ResultBucketItem`] as a tiebreaker,
/// preventing BinaryHeap from comparing BfsItem on score collisions.
counter: usize,
}
impl ResultBucket {
/// Create a new bucket that holds at most `n` items.
pub fn new(n: usize) -> Self {
Self {
n,
heap: BinaryHeap::new(),
counter: 0,
}
}
/// The number of items currently in the bucket.
pub fn len(&self) -> usize {
self.heap.len()
}
/// Whether the bucket is empty.
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
/// Insert a [`BfsItem`] with the given score.
///
/// If the bucket is not yet full the item is always inserted.
/// Otherwise the item is only inserted when `score` is smaller
/// than the largest score currently in the bucket; the entry
/// with the largest score is then evicted.
///
/// # Returns
///
/// `true` if the item was inserted, `false` otherwise.
pub fn insert(&mut self, item: BfsItem, score: f64) -> bool {
let entry = ResultBucketItem::new(score, item, self.counter);
if self.heap.len() < self.n {
self.heap.push(entry);
self.counter += 1;
true
} else if score >= self.heap.peek().unwrap().score {
false
} else {
*self.heap.peek_mut().unwrap() = entry;
self.counter += 1;
true
}
}
/// Consume the bucket and return all stored items.
pub fn into_items(self) -> Vec<BfsItem> {
self.heap.into_iter().map(|entry| entry.item).collect()
}
}
// ============================================================================
// BfsResolver
// ============================================================================
/// A resolver that uses brute-force search to find the best matching circuits.
pub struct BfsResolver {
/// The datasets for all device kinds.
datasets: DatasetCollection,
}
impl BfsResolver {
/// Create a new BFS resolver with the given datasets.
pub fn new(datasets: DatasetCollection) -> Self {
Self { datasets }
}
/// Iterate all possible circuits with one device without repeating equivalent topology.
pub fn iter_one_device_circuit(dataset: &Dataset) -> OneDeviceCircuitIter<'_> {
OneDeviceCircuitIter::new(dataset.items())
}
/// Iterate all possible circuits with two devices without repeating equivalent topology.
pub fn iter_two_devices_circuit(dataset: &Dataset) -> TwoDeviceCircuitIter<'_> {
TwoDeviceCircuitIter::new(dataset.items())
}
/// Iterate all possible circuits with three devices without repeating equivalent topology.
pub fn iter_three_devices_circuit(dataset: &Dataset) -> ThreeDeviceCircuitIter<'_> {
ThreeDeviceSameJointIter::new(dataset.items())
.chain(ThreeDeviceDiffJointIter::new(dataset.items()))
}
fn pick_dataset(&self, device_kind: DeviceKind) -> &Dataset {
match device_kind {
DeviceKind::Resistor => self.datasets.resistor(),
DeviceKind::Capacitor => self.datasets.capacitor(),
DeviceKind::Inductor => self.datasets.inductor(),
}
}
}
impl Resolver for BfsResolver {
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError> {
// Pick dataset from collection
let dataset = self.pick_dataset(request.device_kind);
// Iterate circuit item one by one
let mut bucket = ResultBucket::new(request.count_limit);
let cv_trait = CircuitValueTrait::new(request.device_kind, request.target_value);
let circuits = Self::iter_one_device_circuit(dataset)
.chain(Self::iter_two_devices_circuit(dataset))
.chain(Self::iter_three_devices_circuit(dataset));
for circuit in circuits {
let item = BfsItem::new(circuit, &cv_trait)?;
// If circuit absolute difference is out of tolerance, skip it directly.
if item.unsigned_difference() > request.tolerance {
continue;
}
// Put it into bucket
bucket.insert(item, item.unsigned_difference());
}
// Return result
let circuits: Vec<Circuit> = bucket
.into_items()
.into_iter()
.map(BfsItem::into_circuit)
.collect();
Response::new(request, circuits)
}
}

View File

@@ -0,0 +1,156 @@
use std::cmp::Ordering;
use super::bfs::BfsResolver;
use super::Resolver;
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, LcrConnError};
use crate::dataset::{Dataset, DatasetCollection};
use crate::query::{Request, Response};
/// An item in the lookup table.
pub struct LutItem {
/// The circuit represented by this item.
circuit: Circuit,
/// The value of this circuit.
value: f64,
}
impl LutItem {
/// Create a new LUT item by computing the circuit value.
///
/// # Errors
///
/// See [`Circuit::compute`].
pub fn new(circuit: Circuit, device_kind: DeviceKind) -> Result<Self, LcrConnError> {
let value = circuit.compute(device_kind)?;
Ok(Self { circuit, value })
}
/// The circuit represented by this item.
pub fn circuit(&self) -> &Circuit {
&self.circuit
}
/// The value of this circuit.
pub fn value(&self) -> f64 {
self.value
}
}
/// 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.
///
/// # Errors
///
/// See [`LutItem::new`].
pub fn new(datasets: &DatasetCollection) -> Result<Self, LcrConnError> {
Ok(Self {
resistor_lut: Self::build_lut(datasets.resistor(), DeviceKind::Resistor)?,
capacitor_lut: Self::build_lut(datasets.capacitor(), DeviceKind::Capacitor)?,
inductor_lut: Self::build_lut(datasets.inductor(), DeviceKind::Inductor)?,
})
}
fn build_lut(dataset: &Dataset, device_kind: DeviceKind) -> Result<Vec<LutItem>, LcrConnError> {
let mut lut: Vec<LutItem> = Vec::new();
let circuits = BfsResolver::iter_one_device_circuit(dataset)
.chain(BfsResolver::iter_two_devices_circuit(dataset))
.chain(BfsResolver::iter_three_devices_circuit(dataset));
for circuit in circuits {
lut.push(LutItem::new(circuit, device_kind)?);
}
lut.sort_by(|a, b| a.value.partial_cmp(&b.value).unwrap_or(Ordering::Equal));
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,
}
}
}
impl Resolver for LutResolver {
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError> {
let lut = self.pick_lut(request.device_kind);
let target = request.target_value;
let count_limit = request.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 idx = lut.partition_point(|item| item.value < target);
// 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 mut left = idx as isize - 1;
let mut right = idx as isize;
let lut_len = lut.len() as isize;
let cv_trait = CircuitValueTrait::new(request.device_kind, target);
while left >= 0 || right < lut_len {
if bucket.len() >= count_limit {
break;
}
let go_left = if left < 0 {
false
} else if right >= lut_len {
true
} else {
let left_item = &lut[left as usize];
let left_diff =
cv_trait.unsigned_difference(left_item.circuit(), Some(left_item.value()))?;
let right_item = &lut[right as usize];
let right_diff = cv_trait
.unsigned_difference(right_item.circuit(), Some(right_item.value()))?;
left_diff <= right_diff
};
let item = if go_left {
let item = &lut[left as usize];
left -= 1;
item
} else {
let item = &lut[right as usize];
right += 1;
item
};
let diff = cv_trait.unsigned_difference(item.circuit(), Some(item.value()))?;
// 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 — disable it.
if diff > request.tolerance {
if go_left {
left = -1;
} else {
right = lut_len;
}
continue;
}
bucket.push(item.circuit().clone());
}
Response::new(request, bucket)
}
}

View File

@@ -0,0 +1,26 @@
pub mod bfs;
pub mod lut;
use crate::common::LcrConnError;
use crate::query::{Request, Response};
/// Abstract base trait for all resolvers.
pub trait Resolver {
/// Resolve the request and return the response.
///
/// # Arguments
///
/// * `request` - The request to resolve.
///
/// # Returns
///
/// The response containing the best matching circuits.
///
/// # Errors
///
/// See [`Circuit::compute`](crate::common::Circuit::compute).
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError>;
}
pub use bfs::BfsResolver;
pub use lut::LutResolver;