refactor: refactoring old code
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
## ======== Personal ========
|
||||
.vscode/
|
||||
@@ -1,3 +1,3 @@
|
||||
# LCR Connector
|
||||
|
||||
在3个元器件内,使用给定元器件数值列表快速找到目标数值元器件的最好拼接方式,支持电阻,电容,电感
|
||||
TODO
|
||||
|
||||
15
legacy/.gitignore
vendored
Normal file
15
legacy/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
## ======== Personal ========
|
||||
|
||||
|
||||
## ======== Python ========
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
1
legacy/.python-version
Normal file
1
legacy/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.11
|
||||
3
legacy/README.md
Normal file
3
legacy/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# LCR Connector (Legacy)
|
||||
|
||||
在3个元器件内,使用给定元器件数值列表快速找到目标数值元器件的最好拼接方式,支持电阻,电容,电感
|
||||
174
legacy/common.py
Normal file
174
legacy/common.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
|
||||
class LcrConnException(Exception):
|
||||
"""The exception thrown by LCR Connector"""
|
||||
pass
|
||||
|
||||
|
||||
class DeviceKind(enum.IntEnum):
|
||||
"""The kind of device"""
|
||||
|
||||
RESISTOR = enum.auto()
|
||||
"""Resistor device"""
|
||||
CAPACITOR = enum.auto()
|
||||
"""Capacitor device"""
|
||||
INDUCTOR = enum.auto()
|
||||
"""Inductor device"""
|
||||
|
||||
|
||||
class JointKind(enum.IntEnum):
|
||||
"""The joint type between 2 devices"""
|
||||
|
||||
SERIES = enum.auto()
|
||||
"""Series connection"""
|
||||
PARALLEL = enum.auto()
|
||||
"""Parallel connection"""
|
||||
|
||||
def flip(self) -> 'JointKind':
|
||||
"""
|
||||
Flip the joint kind
|
||||
|
||||
Flip the joint kind from series to parallel or vice versa
|
||||
|
||||
:return: The flipped joint kind
|
||||
"""
|
||||
match self:
|
||||
case JointKind.SERIES:
|
||||
return JointKind.PARALLEL
|
||||
case JointKind.PARALLEL:
|
||||
return JointKind.SERIES
|
||||
|
||||
|
||||
@dataclass
|
||||
class CircuitJoint:
|
||||
"""The part of circuit composed of two devices and the joint kind"""
|
||||
|
||||
device_value: float
|
||||
"""The value of the device"""
|
||||
joint_kind: JointKind
|
||||
"""The joint kind between this device and the next device"""
|
||||
|
||||
def compute(self, value: float, device_kind: DeviceKind) -> float:
|
||||
"""
|
||||
Compute the joint value
|
||||
|
||||
:param value: The value computed from previous devices
|
||||
:param device_kind: The kind of the device
|
||||
:return: The joint value computed
|
||||
"""
|
||||
if self.device_value <= 0 or value <= 0:
|
||||
raise LcrConnException("Device value must be greater than 0")
|
||||
|
||||
# We perform series connect for: series resistor, series inductor and parallel capacitor.
|
||||
# We perform parallel connect for: parallel resistor, parallel inductor and series capacitor.
|
||||
joint_kind = self.joint_kind
|
||||
if device_kind == DeviceKind.CAPACITOR:
|
||||
joint_kind = joint_kind.flip()
|
||||
|
||||
match joint_kind:
|
||||
case JointKind.SERIES:
|
||||
return self.device_value + value
|
||||
case JointKind.PARALLEL:
|
||||
return (self.device_value * value) / (self.device_value + value)
|
||||
|
||||
|
||||
class Circuit:
|
||||
"""The circuit composed of multiple joints"""
|
||||
|
||||
device_value: float
|
||||
"""The value of the first device"""
|
||||
joints: list[CircuitJoint]
|
||||
"""The joints between devices"""
|
||||
|
||||
def __init__(self, value: float):
|
||||
self.device_value = value
|
||||
self.joints = []
|
||||
|
||||
def add_joint(self, joint: CircuitJoint):
|
||||
self.joints.append(joint)
|
||||
|
||||
def len_devices(self) -> int:
|
||||
return len(self.joints) + 1
|
||||
|
||||
def compute(self, device_kind: DeviceKind) -> float:
|
||||
"""
|
||||
Compute the circuit value
|
||||
|
||||
:param device_kind: The kind of the device
|
||||
:return: The circuit value
|
||||
"""
|
||||
if self.device_value <= 0:
|
||||
raise LcrConnException("Device value must be greater than 0")
|
||||
|
||||
value = self.device_value
|
||||
for joint in self.joints:
|
||||
value = joint.compute(value, device_kind)
|
||||
return value
|
||||
|
||||
|
||||
def from_human_readable_value(strl: str) -> float:
|
||||
"""
|
||||
Convert human readable value to float
|
||||
|
||||
:param strl: The human readable value
|
||||
:return: The parsed float value
|
||||
:raises ValueError: If the input string is not a valid number
|
||||
"""
|
||||
strl = strl.strip()
|
||||
|
||||
if strl.endswith('n'):
|
||||
return float(strl[0:-1]) * 1e-12
|
||||
if strl.endswith('p'):
|
||||
return float(strl[0:-1]) * 1e-9
|
||||
if strl.endswith('u'):
|
||||
return float(strl[0:-1]) * 1e-6
|
||||
if strl.endswith('m'):
|
||||
return float(strl[0:-1]) * 1e-3
|
||||
if strl.endswith('k'):
|
||||
return float(strl[0:-1]) * 1e3
|
||||
if strl.endswith('M'):
|
||||
return float(strl[0:-1]) * 1e6
|
||||
if strl.endswith('G'):
|
||||
return float(strl[0:-1]) * 1e9
|
||||
return float(strl)
|
||||
|
||||
|
||||
|
||||
def to_human_readable_value(v: float) -> str:
|
||||
"""
|
||||
Convert float value to human readable value
|
||||
|
||||
:param value: The float value
|
||||
:return: The human readable value
|
||||
"""
|
||||
if v / 1e-12 < 1e3:
|
||||
return "{:e} n".format(v / 1e-12)
|
||||
if v / 1e-9 < 1e3:
|
||||
return "{:.4f} p".format(v / 1e-9)
|
||||
if v / 1e-6 < 1e3:
|
||||
return "{:.4f} u".format(v / 1e-6)
|
||||
if v / 1e-3 < 1e3:
|
||||
return "{:.4f} m".format(v / 1e-3)
|
||||
if v < 1e3:
|
||||
return "{:.4f}".format(v)
|
||||
if v / 1e3 < 1e3:
|
||||
return "{:.4f} k".format(v / 1e3)
|
||||
if v / 1e6 < 1e3:
|
||||
return "{:.4f} M".format(v / 1e6)
|
||||
if v / 1e9 < 1e3:
|
||||
return "{:.4f} G".format(v / 1e9)
|
||||
|
||||
return "{:e}".format(v)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
61
legacy/dataset.py
Normal file
61
legacy/dataset.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from typing import Iterable
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from .common import LcrConnException, from_human_readable_value
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DataSubset:
|
||||
"""A list holding available device gauge values for resistor, capacitor or inductor"""
|
||||
|
||||
gauges: tuple[float, ...]
|
||||
"""A list of available device gauge values"""
|
||||
|
||||
@staticmethod
|
||||
def from_iterable(str_gauges: Iterable[str]) -> "DataSubset":
|
||||
# Remove redundant parts
|
||||
gauges_set: set[float] = set()
|
||||
for str_gauge in str_gauges:
|
||||
numeric_gauge = from_human_readable_value(str_gauge)
|
||||
if numeric_gauge in gauges_set:
|
||||
raise LcrConnException(f"Duplicate gauge value found: {str_gauge}")
|
||||
else:
|
||||
gauges_set.add(numeric_gauge)
|
||||
# Return value
|
||||
return DataSubset(tuple(gauges_set))
|
||||
|
||||
@staticmethod
|
||||
def from_file(filename: Path) -> "DataSubset":
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
legal_lines = filter(lambda line: line != "", (line.strip() for line in f))
|
||||
return DataSubset.from_iterable(legal_lines)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DataSet:
|
||||
"""The dataset include 3 lists of available device gauge values for resistor, capacitor and inductor"""
|
||||
|
||||
resistor: DataSubset
|
||||
"""A list of available device gauge values for resistor"""
|
||||
capacitor: DataSubset
|
||||
"""A list of available device gauge values for capacitor"""
|
||||
inductor: DataSubset
|
||||
"""A list of available device gauge values for inductor"""
|
||||
|
||||
@staticmethod
|
||||
def from_iterable(
|
||||
resistor: Iterable[str], capacitor: Iterable[str], inductor: Iterable[str]
|
||||
) -> "DataSet":
|
||||
return DataSet(
|
||||
DataSubset.from_iterable(resistor),
|
||||
DataSubset.from_iterable(capacitor),
|
||||
DataSubset.from_iterable(inductor),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_file(resistor: Path, capacitor: Path, inductor: Path) -> "DataSet":
|
||||
return DataSet(
|
||||
DataSubset.from_file(resistor),
|
||||
DataSubset.from_file(capacitor),
|
||||
DataSubset.from_file(inductor),
|
||||
)
|
||||
36
legacy/lcr_connector.py
Normal file
36
legacy/lcr_connector.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from .common import LcrConnException, Circuit, JointKind, to_human_readable_value
|
||||
|
||||
|
||||
def _get_joint_kind_symbol(joint_kind: JointKind) -> str:
|
||||
match joint_kind:
|
||||
case JointKind.SERIES:
|
||||
return "S"
|
||||
case JointKind.PARALLEL:
|
||||
return "P"
|
||||
|
||||
|
||||
def _illustrate_circuit(circuit: Circuit) -> None:
|
||||
match circuit.len_devices():
|
||||
case 1:
|
||||
dev1 = to_human_readable_value(circuit.device_value)
|
||||
print(f"{dev1}")
|
||||
case 2:
|
||||
dev1 = to_human_readable_value(circuit.device_value)
|
||||
joint1 = circuit.joints[0]
|
||||
j1 = _get_joint_kind_symbol(joint1.joint_kind)
|
||||
dev2 = to_human_readable_value(joint1.device_value)
|
||||
print(f"[{j1}] ┬ {dev1}")
|
||||
print(f" └ {dev2}")
|
||||
case 3:
|
||||
dev1 = to_human_readable_value(circuit.device_value)
|
||||
joint1 = circuit.joints[0]
|
||||
j1 = _get_joint_kind_symbol(joint1.joint_kind)
|
||||
dev2 = to_human_readable_value(joint1.device_value)
|
||||
joint2 = circuit.joints[1]
|
||||
j2 = _get_joint_kind_symbol(joint2.joint_kind)
|
||||
dev3 = to_human_readable_value(joint2.device_value)
|
||||
print(f"[{j2}] ┬ [{j1}] ┬ {dev1}")
|
||||
print(f" │ └ {dev2}")
|
||||
print(f" └ {dev3}")
|
||||
case _:
|
||||
raise LcrConnException("Circuit too complex to illustrate")
|
||||
7
legacy/pyproject.toml
Normal file
7
legacy/pyproject.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "lcr-connector"
|
||||
version = "0.1.0"
|
||||
description = "Use as much 3 devices to reach target value for resistor, capacitor and inductor."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
0
legacy/resolver/__init__.py
Normal file
0
legacy/resolver/__init__.py
Normal file
15
legacy/resolver/astar.py
Normal file
15
legacy/resolver/astar.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import Iterator
|
||||
from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority
|
||||
from ..dataset import DataSet
|
||||
|
||||
class AStarResolver(Resolver):
|
||||
"""
|
||||
A resolver that uses A* algorithm to find the best matching circuit.
|
||||
"""
|
||||
|
||||
def __init__(self, dataset: DataSet):
|
||||
pass
|
||||
|
||||
|
||||
def resolve(self, request: ResolverRequest) -> Iterator[ResolverResult]:
|
||||
pass
|
||||
102
legacy/resolver/common.py
Normal file
102
legacy/resolver/common.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import enum
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
from ..common import DeviceKind, Circuit
|
||||
|
||||
|
||||
class ResultPriority(enum.Enum):
|
||||
"""
|
||||
The priority of the result.
|
||||
"""
|
||||
|
||||
LESS_DEVICES = enum.auto()
|
||||
"""Less devices is the first priority."""
|
||||
MORE_ACCURACY = enum.auto()
|
||||
"""More accuracy is the first priority."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResolverRequest:
|
||||
"""
|
||||
The request object for the resolver.
|
||||
"""
|
||||
|
||||
device_kind: DeviceKind
|
||||
"""The kind of device to resolve."""
|
||||
target_value: float
|
||||
"""The target value of the device."""
|
||||
tolerance: float
|
||||
"""The tolerance of the device."""
|
||||
result_priority: ResultPriority
|
||||
"""The priority of the result."""
|
||||
count_limit: int
|
||||
"""The limit of the count of results."""
|
||||
|
||||
|
||||
class ResolverResult:
|
||||
"""
|
||||
The result of the resolver.
|
||||
"""
|
||||
|
||||
circuit: Circuit
|
||||
"""The circuit of the result."""
|
||||
|
||||
__value_cache: float | None
|
||||
"""The cache of the circuit value."""
|
||||
__difference_cache: float | None
|
||||
"""The cache of the difference between the target value and the circuit value."""
|
||||
__relative_difference_cache: float | None
|
||||
"""The cache of the relative difference between the target value and the circuit value."""
|
||||
|
||||
def __init__(self, circuit: Circuit):
|
||||
self.circuit = circuit
|
||||
self.__value_cache = None
|
||||
self.__difference_cache = None
|
||||
self.__relative_difference_cache = None
|
||||
|
||||
def compute(self, device_kind: DeviceKind) -> float:
|
||||
"""
|
||||
Compute the circuit value.
|
||||
"""
|
||||
if self.__value_cache is None:
|
||||
self.__value_cache = self.circuit.compute(device_kind)
|
||||
return self.__value_cache
|
||||
|
||||
def difference(self, target_value: float, device_kind: DeviceKind) -> float:
|
||||
"""
|
||||
Get the difference between the target value and the circuit value.
|
||||
"""
|
||||
if self.__difference_cache is None:
|
||||
self.__difference_cache = abs(
|
||||
target_value - self.circuit.compute(device_kind)
|
||||
)
|
||||
return self.__difference_cache
|
||||
|
||||
def relative_difference(
|
||||
self, target_value: float, device_kind: DeviceKind
|
||||
) -> float:
|
||||
"""
|
||||
Get the relative difference between the target value and the circuit value.
|
||||
"""
|
||||
if self.__relative_difference_cache is None:
|
||||
self.__relative_difference_cache = (
|
||||
abs(target_value - self.circuit.compute(device_kind)) / target_value
|
||||
)
|
||||
return self.__relative_difference_cache
|
||||
|
||||
def len_devices(self) -> int:
|
||||
"""
|
||||
Get the number of devices in the circuit.
|
||||
"""
|
||||
return self.circuit.len_devices()
|
||||
|
||||
|
||||
class Resolver(ABC):
|
||||
"""
|
||||
Abstract base class for all resolvers.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def resolve(self, request: ResolverRequest) -> Iterator[ResolverResult]:
|
||||
pass
|
||||
109
legacy/resolver/lut.py
Normal file
109
legacy/resolver/lut.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import struct
|
||||
from typing import Iterator, BinaryIO
|
||||
from pathlib import Path
|
||||
from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority
|
||||
from ..dataset import DataSet
|
||||
from ..common import Circuit, CircuitJoint, JointKind, LcrConnException
|
||||
|
||||
class LutResolver(Resolver):
|
||||
"""
|
||||
A resolver that uses a lookup table to find the best matching circuit.
|
||||
"""
|
||||
|
||||
lut: tuple[Circuit]
|
||||
|
||||
def __init__(self, lut: tuple[Circuit]):
|
||||
self.lut = lut
|
||||
|
||||
@staticmethod
|
||||
def from_dataset(dataset: DataSet) -> 'LutResolver':
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def from_cache(filename: Path) -> 'LutResolver':
|
||||
with open(filename, "rb") as f:
|
||||
cnt = _read_int(f)
|
||||
return LutResolver(tuple(LutItem.from_cache(f) for _ in range(cnt)))
|
||||
|
||||
def save_as_cache(self, filename: Path) -> None:
|
||||
with open(filename, "wb") as f:
|
||||
_write_int(f, len(self.lut))
|
||||
for item in self.lut:
|
||||
item.save_as_cache(f)
|
||||
|
||||
def resolve(self, request: ResolverRequest) -> Iterator[ResolverResult]:
|
||||
pass
|
||||
|
||||
|
||||
class LutItem:
|
||||
"""
|
||||
An item in the lookup table.
|
||||
"""
|
||||
|
||||
circuit: Circuit
|
||||
"""The circuit represented by this item."""
|
||||
__value_cache: float | None
|
||||
"""The cached computed value of the circuit, or None if it has not been cached yet."""
|
||||
|
||||
def __init__(self, circuit: Circuit):
|
||||
self.circuit = circuit
|
||||
|
||||
@staticmethod
|
||||
def from_cache(f: BinaryIO) -> 'LutItem':
|
||||
cnt = _read_int(f)
|
||||
|
||||
if cnt < 1:
|
||||
raise LcrConnException("Invalid circuit count in LUT item")
|
||||
device_value = _read_double(f)
|
||||
circuit = Circuit(device_value)
|
||||
cnt -= 1
|
||||
|
||||
for _ in range(cnt):
|
||||
j = JointKind.SERIES if _read_bool(f) else JointKind.PARALLEL
|
||||
dev = _read_double(f)
|
||||
joint = CircuitJoint(j, dev)
|
||||
circuit.add_joint(joint)
|
||||
|
||||
return LutItem(circuit)
|
||||
|
||||
def save_as_cache(self, f: BinaryIO) -> None:
|
||||
_write_int(f, self.circuit.len_devices())
|
||||
_write_double(f, self.circuit.device_value)
|
||||
for joint in self.circuit.joints():
|
||||
_write_bool(f, joint.kind == JointKind.SERIES)
|
||||
_write_double(f, joint.value)
|
||||
|
||||
def compute(self) -> float:
|
||||
"""The computed value of the circuit."""
|
||||
if self.__value_cache is None:
|
||||
self.__value_cache = self.circuit.value()
|
||||
return self.__value_cache
|
||||
|
||||
|
||||
|
||||
DOUBLE_PACKER = struct.Struct("d")
|
||||
INT_PACKER = struct.Struct("I")
|
||||
BOOL_PACKER = struct.Struct("?")
|
||||
|
||||
def _read_double(fs) -> float:
|
||||
return DOUBLE_PACKER.unpack(fs.read(DOUBLE_PACKER.size))[0]
|
||||
|
||||
def _read_int(fs) -> int:
|
||||
return INT_PACKER.unpack(fs.read(INT_PACKER.size))[0]
|
||||
|
||||
def _read_bool(fs) -> bool:
|
||||
return BOOL_PACKER.unpack(fs.read(BOOL_PACKER.size))[0]
|
||||
|
||||
def _write_double(fs, num: float):
|
||||
fs.write(DOUBLE_PACKER.pack(num))
|
||||
|
||||
def _write_int(fs, num: int):
|
||||
fs.write(INT_PACKER.pack(num))
|
||||
|
||||
def _write_bool(fs, num: bool):
|
||||
fs.write(BOOL_PACKER.pack(num))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
8
legacy/uv.lock
generated
Normal file
8
legacy/uv.lock
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "lcr-connector"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
Reference in New Issue
Block a user