use std::cmp::Ordering; use super::bfs::BfsResolver; use super::Resolver; use crate::common::{Circuit, CircuitCalculator, 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 { 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, /// The lookup table for capacitors. capacitor_lut: Vec, /// The lookup table for inductors. inductor_lut: Vec, } 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 { Ok(Self { resistor_lut: Self::build_lut(datasets.resistor_dataset(), DeviceKind::Resistor)?, capacitor_lut: Self::build_lut(datasets.capacitor_dataset(), DeviceKind::Capacitor)?, inductor_lut: Self::build_lut(datasets.inductor_dataset(), DeviceKind::Inductor)?, }) } fn build_lut(dataset: &Dataset, device_kind: DeviceKind) -> Result, LcrConnError> { let mut lut: Vec = 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 { let lut = self.pick_lut(request.device_kind); let target = request.target_value; let count_limit = request.count_limit; let mut bucket: Vec = 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 = CircuitCalculator::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) } }