2026-06-28 20:47:00 +08:00
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
|
|
|
|
|
use super::bfs::BfsResolver;
|
|
|
|
|
use super::Resolver;
|
2026-06-28 22:30:32 +08:00
|
|
|
use crate::common::{Circuit, CircuitCalculator, DeviceKind, LcrConnError};
|
2026-06-28 20:47:00 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-06-28 22:30:32 +08:00
|
|
|
let cv_trait = CircuitCalculator::new(request.device_kind, target);
|
2026-06-28 20:47:00 +08:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|