1
0
Files
LCRConnector/kernel/lcrconn/src/resolver/lut.rs

157 lines
5.2 KiB
Rust
Raw Normal View History

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<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 = 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)
}
}