2026-06-17 19:49:54 +08:00
|
|
|
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-25 12:31:09 +08:00
|
|
|
from .bfs import BfsResolver
|
2026-06-15 15:58:04 +08:00
|
|
|
from ..dataset import DatasetCollection, Dataset
|
2026-06-25 12:31:09 +08:00
|
|
|
from ..common import Circuit, DeviceKind, JointKind, CircuitValueTrait
|
|
|
|
|
from ..query import Request, Response
|
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-25 12:31:09 +08:00
|
|
|
__value: float
|
|
|
|
|
"""The value of 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
|
2026-06-25 12:31:09 +08:00
|
|
|
self.__value = self.__circuit.compute(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-25 12:31:09 +08:00
|
|
|
@property
|
2026-06-15 15:58:04 +08:00
|
|
|
def value(self) -> float:
|
2026-06-25 12:31:09 +08:00
|
|
|
return self.__value
|
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):
|
2026-06-17 19:49:54 +08:00
|
|
|
self.__resistor_lut = self.__build_lut(
|
2026-06-15 15:58:04 +08:00
|
|
|
datasets.resistor_values, DeviceKind.RESISTOR
|
|
|
|
|
)
|
2026-06-17 19:49:54 +08:00
|
|
|
self.__capacitor_lut = self.__build_lut(
|
2026-06-15 15:58:04 +08:00
|
|
|
datasets.capacitor_values, DeviceKind.CAPACITOR
|
|
|
|
|
)
|
2026-06-17 19:49:54 +08:00
|
|
|
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
|
|
|
|
2026-06-17 19:49:54 +08:00
|
|
|
def __build_lut(self, dataset: Dataset, device_kind: DeviceKind) -> list[LutItem]:
|
|
|
|
|
lut = [
|
2026-06-15 15:58:04 +08:00
|
|
|
LutItem(circuit, device_kind)
|
|
|
|
|
for circuit in chain(
|
2026-06-25 12:31:09 +08:00
|
|
|
BfsResolver.iter_one_device_circuit(dataset),
|
|
|
|
|
BfsResolver.iter_two_devices_circuit(dataset),
|
|
|
|
|
BfsResolver.iter_three_devices_circuit(dataset),
|
2026-06-15 15:58:04 +08:00
|
|
|
)
|
|
|
|
|
]
|
2026-06-17 19:49:54 +08:00
|
|
|
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
|
|
|
|
|
|
2026-06-17 19:49:54 +08:00
|
|
|
target = request.target_value
|
2026-06-25 12:31:09 +08:00
|
|
|
count_limit = request.count_limit
|
|
|
|
|
bucket: list[Circuit] = []
|
2026-06-17 19:49:54 +08:00
|
|
|
|
|
|
|
|
# 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):
|
2026-06-25 12:31:09 +08:00
|
|
|
if len(bucket) >= count_limit:
|
2026-06-17 19:49:54 +08:00
|
|
|
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
|
|
|
|
|
|
2026-06-25 12:31:09 +08:00
|
|
|
bucket.append(item.circuit)
|
2026-06-17 19:49:54 +08:00
|
|
|
|
2026-06-25 12:31:09 +08:00
|
|
|
return Response(request, bucket)
|