1
0
Files
LCRConnector/legacy/src/lcr_connector/resolver/lut.py

126 lines
4.0 KiB
Python

import bisect
from itertools import chain, product
from functools import cached_property
from .common import Resolver
from .bfs import BfsResolver
from ..dataset import DatasetCollection, Dataset
from ..common import Circuit, DeviceKind, JointKind, CircuitValueTrait
from ..query import Request, Response
class LutItem:
"""
An item in the lookup table.
"""
__circuit: Circuit
"""The circuit represented by this item."""
__value: float
"""The value of this circuit."""
def __init__(self, circuit: Circuit, device_kind: DeviceKind):
self.__circuit = circuit
self.__value = self.__circuit.compute(device_kind)
@property
def circuit(self) -> Circuit:
return self.__circuit
@property
def value(self) -> float:
return self.__value
class LutResolver(Resolver):
"""
A resolver that uses a lookup table to find the best matching circuit.
"""
__resistor_lut: list[LutItem]
"""The lookup table for resistors."""
__capacitor_lut: list[LutItem]
"""The lookup table for capacitors."""
__inductor_lut: list[LutItem]
"""The lookup table for inductors."""
def __init__(self, datasets: DatasetCollection):
self.__resistor_lut = self.__build_lut(
datasets.resistor_values, DeviceKind.RESISTOR
)
self.__capacitor_lut = self.__build_lut(
datasets.capacitor_values, DeviceKind.CAPACITOR
)
self.__inductor_lut = self.__build_lut(
datasets.inductor_values, DeviceKind.INDUCTOR
)
def __build_lut(self, dataset: Dataset, device_kind: DeviceKind) -> list[LutItem]:
lut = [
LutItem(circuit, device_kind)
for circuit in chain(
BfsResolver.iter_one_device_circuit(dataset),
BfsResolver.iter_two_devices_circuit(dataset),
BfsResolver.iter_three_devices_circuit(dataset),
)
]
lut.sort(key=lambda item: item.value)
return lut
def resolve(self, request: Request) -> Response:
lut: list[LutItem]
match request.device_kind:
case DeviceKind.RESISTOR:
lut = self.__resistor_lut
case DeviceKind.CAPACITOR:
lut = self.__capacitor_lut
case DeviceKind.INDUCTOR:
lut = self.__inductor_lut
target = request.target_value
count_limit = request.count_limit
bucket: list[Circuit] = []
# Locate the insertion point of target in the sorted LUT.
# left/right start at the two nearest neighbours and expand outward.
idx = bisect.bisect_left(lut, target, key=lambda item: item.value)
left = idx - 1
right = idx
# 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.
while left >= 0 or right < len(lut):
if len(bucket) >= count_limit:
break
if left < 0:
go_left = False
elif right >= len(lut):
go_left = True
else:
go_left = (target - lut[left].value) <= (lut[right].value - target)
if go_left:
item = lut[left]
left -= 1
else:
item = lut[right]
right += 1
diff = abs(target - 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 = len(lut)
continue
bucket.append(item.circuit)
return Response(request, bucket)