diff --git a/legacy/common.py b/legacy/common.py index c7d6679..14be34b 100644 --- a/legacy/common.py +++ b/legacy/common.py @@ -1,14 +1,15 @@ 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() @@ -19,16 +20,16 @@ class DeviceKind(enum.IntEnum): class JointKind(enum.IntEnum): """The joint type between 2 devices""" - + SERIES = enum.auto() """Series connection""" PARALLEL = enum.auto() """Parallel connection""" - - def flip(self) -> 'JointKind': + + def flip(self) -> "JointKind": """ Flip the joint kind - + Flip the joint kind from series to parallel or vice versa :return: The flipped joint kind @@ -40,16 +41,19 @@ class JointKind(enum.IntEnum): return JointKind.SERIES -@dataclass -class CircuitJoint: +class SubCircuit: """The part of circuit composed of two devices and the joint kind""" - device_value: float + __device_value: float """The value of the device""" - joint_kind: JointKind + __joint_kind: JointKind """The joint kind between this device and the next device""" - - def compute(self, value: float, device_kind: DeviceKind) -> float: + + def __init__(self, device_value: float, joint_kind: JointKind): + self.__device_value = device_value + self.__joint_kind = joint_kind + + def compute(self, value: float, device_kind: DeviceKind) -> float: """ Compute the joint value @@ -57,39 +61,175 @@ class CircuitJoint: :param device_kind: The kind of the device :return: The joint value computed """ - if self.device_value <= 0 or value <= 0: + 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 + 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 + return self.__device_value + value case JointKind.PARALLEL: - return (self.device_value * value) / (self.device_value + value) + return (self.__device_value * value) / (self.__device_value + value) + + def get_device_value(self) -> float: + """ + Get the device value + + :return: The device value + """ + return self.__device_value + + def get_joint_kind(self) -> JointKind: + """ + Get the joint kind + + :return: The joint kind + """ + return self.__joint_kind + + +class CircuitDeviceCount(enum.IntEnum): + """The number of devices in the circuit""" + + ONE = enum.auto() + """One device""" + TWO = enum.auto() + """Two devices""" + THREE = enum.auto() + """Three devices""" + + def to_device_count(self) -> int: + match self: + case CircuitDeviceCount.ONE: + return 1 + case CircuitDeviceCount.TWO: + return 2 + case CircuitDeviceCount.THREE: + return 3 class Circuit: """The circuit composed of multiple joints""" - device_value: float + __first_device_value: float """The value of the first device""" - joints: list[CircuitJoint] - """The joints between devices""" + __second_device_subckt: SubCircuit | None + """The second device and its joint property""" + __third_device_subckt: SubCircuit | None + """The third device and its joint property""" - def __init__(self, value: float): - self.device_value = value - self.joints = [] + def __init__( + self, + first_device_value: float, + second_device_subckt: SubCircuit | None, + third_device_subckt: SubCircuit | None, + ): + """ + Initialize the circuit - def add_joint(self, joint: CircuitJoint): - self.joints.append(joint) + :param first_device_value: The value of the first device + :param second_device_subckt: The second device and its joint property + :param third_device_subckt: The third device and its joint property + """ + if second_device_subckt is None and third_device_subckt is not None: + raise LcrConnException("Third device cannot exist without second device") - def len_devices(self) -> int: - return len(self.joints) + 1 + self.__first_device_value = first_device_value + self.__second_device_subckt = second_device_subckt + self.__third_device_subckt = third_device_subckt + + @staticmethod + def from_one_device(device1_value: float) -> "Circuit": + return Circuit(device1_value, None, None) + + @staticmethod + def from_two_devices( + device1_value: float, device2_value: float, device2_joint: JointKind + ) -> "Circuit": + return Circuit(device1_value, SubCircuit(device2_value, device2_joint), None) + + @staticmethod + def from_three_devices( + device1_value: float, + device2_value: float, + device2_joint: JointKind, + device3_value: float, + device3_joint: JointKind, + ) -> "Circuit": + return Circuit( + device1_value, + SubCircuit(device2_value, device2_joint), + SubCircuit(device3_value, device3_joint), + ) + + def get_device_count(self) -> CircuitDeviceCount: + if self.__third_device_subckt is not None: + return CircuitDeviceCount.THREE + elif self.__second_device_subckt is not None: + return CircuitDeviceCount.TWO + else: + return CircuitDeviceCount.ONE + + def get_first_device_value(self) -> float: + """ + Get the value of the first device + + :return: The value of the first device + """ + return self.__first_device_value + + def get_second_device_joint(self) -> JointKind: + """ + Get the joint kind of the second device + + :return: The joint kind of the second device + :raises LcrConnException: If there is no second device + """ + if self.__second_device_subckt is not None: + return self.__second_device_subckt.get_joint_kind() + else: + raise LcrConnException("No second device") + + def get_second_device_value(self) -> float: + """ + Get the value of the second device + + :return: The value of the second device + :raises LcrConnException: If there is no second device + """ + if self.__second_device_subckt is not None: + return self.__second_device_subckt.get_device_value() + else: + raise LcrConnException("No second device") + + def get_third_device_joint(self) -> JointKind: + """ + Get the joint kind of the third device + + :return: The joint kind of the third device + :raises LcrConnException: If there is no third device + """ + if self.__third_device_subckt is not None: + return self.__third_device_subckt.get_joint_kind() + else: + raise LcrConnException("No third device") + + def get_third_device_value(self) -> float: + """ + Get the value of the third device + + :return: The value of the third device + :raises LcrConnException: If there is no third device + """ + if self.__third_device_subckt is not None: + return self.__third_device_subckt.get_device_value() + else: + raise LcrConnException("No third device") def compute(self, device_kind: DeviceKind) -> float: """ @@ -98,77 +238,14 @@ class Circuit: :param device_kind: The kind of the device :return: The circuit value """ - if self.device_value <= 0: + if self.__first_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) + + value = self.__first_device_value + if self.__second_device_subckt is None: + return value + value = self.__second_device_subckt.compute(value, device_kind) + if self.__third_device_subckt is None: + return value + value = self.__third_device_subckt.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) - - - - - - - - - - - - diff --git a/legacy/dataset.py b/legacy/dataset.py index d71c6bc..c8042f9 100644 --- a/legacy/dataset.py +++ b/legacy/dataset.py @@ -1,61 +1,178 @@ from typing import Iterable from pathlib import Path -from dataclasses import dataclass -from .common import LcrConnException, from_human_readable_value +from .common import LcrConnException -@dataclass(frozen=True) -class DataSubset: - """A list holding available device gauge values for resistor, capacitor or inductor""" +class Dataset: + """ + A list holding available standard values for resistor, capacitor or inductor. - gauges: tuple[float, ...] + Standard values is a collection of all possible values of specific device manufactured by electronic factory. + In reality, it also can be replaced by all possible values of specific device provided by your laboratory. + For example, your laboratory only provide resistor with 100 Ohm and 4.7k Ohm. + This list will only contain 100 and 4.7k. + """ + + __values: 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)) + def __init__(self, values: tuple[float, ...]): + # Check redundant parts + valueset = set(values) + if len(valueset) != len(values): + raise LcrConnException(f"Duplicate standard value") + # Ok, assign it + self.__values = values @staticmethod - def from_file(filename: Path) -> "DataSubset": + def from_iterable(stringfied_values: Iterable[str]) -> "Dataset": + return Dataset( + tuple( + from_human_readable_value(stringfied_value) + for stringfied_value in stringfied_values + ) + ) + + @staticmethod + def from_text(text: str) -> "Dataset": + lines = text.split("\n") + legal_lines = filter(lambda line: line != "", (line.strip() for line in lines)) + return Dataset.from_iterable(legal_lines) + + @staticmethod + def from_file(filename: Path) -> "Dataset": 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) + return Dataset.from_iterable(legal_lines) + + def get_values(self) -> tuple[float, ...]: + """ + Get the available standard values + + :return: A tuple of available standard values + """ + return self.__values -@dataclass(frozen=True) -class DataSet: - """The dataset include 3 lists of available device gauge values for resistor, capacitor and inductor""" +class DatasetCollection: + """ + The collection holding all standard values for resistor, capacitor and inductor respectively. + """ - resistor: DataSubset + __resistor: Dataset """A list of available device gauge values for resistor""" - capacitor: DataSubset + __capacitor: Dataset """A list of available device gauge values for capacitor""" - inductor: DataSubset + __inductor: Dataset """A list of available device gauge values for inductor""" + def __init__(self, resistor: Dataset, capacitor: Dataset, inductor: Dataset): + self.__resistor = resistor + self.__capacitor = capacitor + self.__inductor = 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), + ) -> "DatasetCollection": + return DatasetCollection( + Dataset.from_iterable(resistor), + Dataset.from_iterable(capacitor), + Dataset.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), + def from_text(resistor: str, capacitor: str, inductor: str) -> "DatasetCollection": + return DatasetCollection( + Dataset.from_text(resistor), + Dataset.from_text(capacitor), + Dataset.from_text(inductor), ) + + @staticmethod + def from_file( + resistor: Path, capacitor: Path, inductor: Path + ) -> "DatasetCollection": + return DatasetCollection( + Dataset.from_file(resistor), + Dataset.from_file(capacitor), + Dataset.from_file(inductor), + ) + + def get_resistor_values(self) -> tuple[float, ...]: + """ + Get the available standard values for resistor + + :return: A tuple of available standard values for resistor + """ + return self.__resistor.get_values() + + def get_capacitor_values(self) -> tuple[float, ...]: + """ + Get the available standard values for capacitor + + :return: A tuple of available standard values for capacitor + """ + return self.__capacitor.get_values() + + def get_inductor_values(self) -> tuple[float, ...]: + """ + Get the available standard values for inductor + + :return: A tuple of available standard values for inductor + """ + return self.__inductor.get_values() + + +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) diff --git a/legacy/lcr_connector.py b/legacy/lcr_connector.py index fc651e5..53dfabb 100644 --- a/legacy/lcr_connector.py +++ b/legacy/lcr_connector.py @@ -12,23 +12,23 @@ def _get_joint_kind_symbol(joint_kind: JointKind) -> str: def _illustrate_circuit(circuit: Circuit) -> None: match circuit.len_devices(): case 1: - dev1 = to_human_readable_value(circuit.device_value) + dev1 = to_human_readable_value(circuit.__first_device_value) print(f"{dev1}") case 2: - dev1 = to_human_readable_value(circuit.device_value) + dev1 = to_human_readable_value(circuit.__first_device_value) joint1 = circuit.joints[0] - j1 = _get_joint_kind_symbol(joint1.joint_kind) - dev2 = to_human_readable_value(joint1.device_value) + 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) + dev1 = to_human_readable_value(circuit.__first_device_value) joint1 = circuit.joints[0] - j1 = _get_joint_kind_symbol(joint1.joint_kind) - dev2 = to_human_readable_value(joint1.device_value) + 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) + 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}") diff --git a/legacy/resolver/astar.py b/legacy/resolver/astar.py index d801962..51a9dd8 100644 --- a/legacy/resolver/astar.py +++ b/legacy/resolver/astar.py @@ -1,13 +1,13 @@ from typing import Iterator from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority -from ..dataset import DataSet +from ..dataset import DatasetCollection class AStarResolver(Resolver): """ A resolver that uses A* algorithm to find the best matching circuit. """ - def __init__(self, dataset: DataSet): + def __init__(self, dataset: DatasetCollection): pass diff --git a/legacy/resolver/lut.py b/legacy/resolver/lut.py index 010e5d9..40f05c6 100644 --- a/legacy/resolver/lut.py +++ b/legacy/resolver/lut.py @@ -2,8 +2,8 @@ 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 +from ..dataset import DatasetCollection +from ..common import Circuit, SubCircuit, JointKind, LcrConnException class LutResolver(Resolver): """ @@ -16,7 +16,7 @@ class LutResolver(Resolver): self.lut = lut @staticmethod - def from_dataset(dataset: DataSet) -> 'LutResolver': + def from_dataset(dataset: DatasetCollection) -> 'LutResolver': pass @staticmethod @@ -61,14 +61,14 @@ class LutItem: for _ in range(cnt): j = JointKind.SERIES if _read_bool(f) else JointKind.PARALLEL dev = _read_double(f) - joint = CircuitJoint(j, dev) + joint = SubCircuit(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) + _write_double(f, self.circuit.__first_device_value) for joint in self.circuit.joints(): _write_bool(f, joint.kind == JointKind.SERIES) _write_double(f, joint.value)