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

140 lines
4.5 KiB
Python
Raw Normal View History

import bisect
2026-06-15 15:58:04 +08:00
from itertools import chain, product
from functools import cached_property
2026-06-15 17:13:15 +08:00
from .common import Resolver
2026-06-15 15:58:04 +08:00
from ..dataset import DatasetCollection, Dataset
2026-06-15 17:13:15 +08:00
from ..common import Circuit, DeviceKind, JointKind
from ..query import Request, Response, ResponseDeduper
2026-06-02 21:08:48 +08:00
class LutItem:
"""
An item in the lookup table.
"""
2026-06-15 15:58:04 +08:00
__circuit: Circuit
2026-06-02 21:08:48 +08:00
"""The circuit represented by this item."""
2026-06-15 15:58:04 +08:00
__device_kind: DeviceKind
"""The device kind applied for this circuit."""
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
def __init__(self, circuit: Circuit, device_kind: DeviceKind):
self.__circuit = circuit
self.__device_kind = device_kind
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
@property
def circuit(self) -> Circuit:
return self.__circuit
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
@cached_property
def value(self) -> float:
"""
The computed value of the circuit.
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
:return: The computed value.
"""
return self.__circuit.compute(self.__device_kind)
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
class LutResolver(Resolver):
"""
A resolver that uses a lookup table to find the best matching circuit.
"""
2026-06-02 21:08:48 +08:00
2026-06-15 15:58:04 +08:00
__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(
2026-06-15 15:58:04 +08:00
datasets.resistor_values, DeviceKind.RESISTOR
)
self.__capacitor_lut = self.__build_lut(
2026-06-15 15:58:04 +08:00
datasets.capacitor_values, DeviceKind.CAPACITOR
)
self.__inductor_lut = self.__build_lut(
2026-06-15 15:58:04 +08:00
datasets.inductor_values, DeviceKind.INDUCTOR
)
2026-06-02 21:08:48 +08:00
def __build_lut(self, dataset: Dataset, device_kind: DeviceKind) -> list[LutItem]:
2026-06-15 15:58:04 +08:00
values = dataset.values
joints = tuple(JointKind)
lut = [
2026-06-15 15:58:04 +08:00
LutItem(circuit, device_kind)
for circuit in chain(
(Circuit.from_one_device(v1) for v1 in values),
(
Circuit.from_two_devices(v1, v2, j2)
for v1, v2, j2 in product(values, values, joints)
),
(
Circuit.from_three_devices(v1, v2, j2, v3, j3)
for v1, v2, j2, v3, j3 in product(
values, values, joints, values, joints
)
),
)
]
lut.sort(key=lambda item: item.value)
return lut
2026-06-15 15:58:04 +08:00
2026-06-15 17:13:15 +08:00
def resolve(self, request: Request) -> Response:
2026-06-15 15:58:04 +08:00
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 = min(request.count_limit, 100)
deduper = ResponseDeduper()
# 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(deduper) >= 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)
2026-06-15 15:58:04 +08:00
continue
deduper.add(item.circuit)
return Response(request, deduper)