1
0

feat: still update basic concepts

This commit is contained in:
2026-06-15 13:47:31 +08:00
parent 0812e065b5
commit 7721672b8e
5 changed files with 345 additions and 151 deletions

View File

@@ -1,8 +1,9 @@
import enum import enum
from dataclasses import dataclass
class LcrConnException(Exception): class LcrConnException(Exception):
"""The exception thrown by LCR Connector""" """The exception thrown by LCR Connector"""
pass pass
@@ -25,7 +26,7 @@ class JointKind(enum.IntEnum):
PARALLEL = enum.auto() PARALLEL = enum.auto()
"""Parallel connection""" """Parallel connection"""
def flip(self) -> 'JointKind': def flip(self) -> "JointKind":
""" """
Flip the joint kind Flip the joint kind
@@ -40,16 +41,19 @@ class JointKind(enum.IntEnum):
return JointKind.SERIES return JointKind.SERIES
@dataclass class SubCircuit:
class CircuitJoint:
"""The part of circuit composed of two devices and the joint kind""" """The part of circuit composed of two devices and the joint kind"""
device_value: float __device_value: float
"""The value of the device""" """The value of the device"""
joint_kind: JointKind __joint_kind: JointKind
"""The joint kind between this device and the next device""" """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 Compute the joint value
@@ -57,39 +61,175 @@ class CircuitJoint:
:param device_kind: The kind of the device :param device_kind: The kind of the device
:return: The joint value computed :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") raise LcrConnException("Device value must be greater than 0")
# We perform series connect for: series resistor, series inductor and parallel capacitor. # We perform series connect for: series resistor, series inductor and parallel capacitor.
# We perform parallel connect for: parallel resistor, parallel inductor and series 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: if device_kind == DeviceKind.CAPACITOR:
joint_kind = joint_kind.flip() joint_kind = joint_kind.flip()
match joint_kind: match joint_kind:
case JointKind.SERIES: case JointKind.SERIES:
return self.device_value + value return self.__device_value + value
case JointKind.PARALLEL: 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: class Circuit:
"""The circuit composed of multiple joints""" """The circuit composed of multiple joints"""
device_value: float __first_device_value: float
"""The value of the first device""" """The value of the first device"""
joints: list[CircuitJoint] __second_device_subckt: SubCircuit | None
"""The joints between devices""" """The second device and its joint property"""
__third_device_subckt: SubCircuit | None
"""The third device and its joint property"""
def __init__(self, value: float): def __init__(
self.device_value = value self,
self.joints = [] first_device_value: float,
second_device_subckt: SubCircuit | None,
third_device_subckt: SubCircuit | None,
):
"""
Initialize the circuit
def add_joint(self, joint: CircuitJoint): :param first_device_value: The value of the first device
self.joints.append(joint) :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: self.__first_device_value = first_device_value
return len(self.joints) + 1 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: def compute(self, device_kind: DeviceKind) -> float:
""" """
@@ -98,77 +238,14 @@ class Circuit:
:param device_kind: The kind of the device :param device_kind: The kind of the device
:return: The circuit value :return: The circuit value
""" """
if self.device_value <= 0: if self.__first_device_value <= 0:
raise LcrConnException("Device value must be greater than 0") raise LcrConnException("Device value must be greater than 0")
value = self.device_value value = self.__first_device_value
for joint in self.joints: if self.__second_device_subckt is None:
value = joint.compute(value, device_kind) 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 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)

View File

@@ -1,61 +1,178 @@
from typing import Iterable from typing import Iterable
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from .common import LcrConnException
from .common import LcrConnException, from_human_readable_value
@dataclass(frozen=True) class Dataset:
class DataSubset: """
"""A list holding available device gauge values for resistor, capacitor or inductor""" 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""" """A list of available device gauge values"""
@staticmethod def __init__(self, values: tuple[float, ...]):
def from_iterable(str_gauges: Iterable[str]) -> "DataSubset": # Check redundant parts
# Remove redundant parts valueset = set(values)
gauges_set: set[float] = set() if len(valueset) != len(values):
for str_gauge in str_gauges: raise LcrConnException(f"Duplicate standard value")
numeric_gauge = from_human_readable_value(str_gauge) # Ok, assign it
if numeric_gauge in gauges_set: self.__values = values
raise LcrConnException(f"Duplicate gauge value found: {str_gauge}")
else:
gauges_set.add(numeric_gauge)
# Return value
return DataSubset(tuple(gauges_set))
@staticmethod @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: with open(filename, "r", encoding="utf-8") as f:
legal_lines = filter(lambda line: line != "", (line.strip() for line in 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 DatasetCollection:
class DataSet: """
"""The dataset include 3 lists of available device gauge values for resistor, capacitor and inductor""" 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""" """A list of available device gauge values for resistor"""
capacitor: DataSubset __capacitor: Dataset
"""A list of available device gauge values for capacitor""" """A list of available device gauge values for capacitor"""
inductor: DataSubset __inductor: Dataset
"""A list of available device gauge values for inductor""" """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 @staticmethod
def from_iterable( def from_iterable(
resistor: Iterable[str], capacitor: Iterable[str], inductor: Iterable[str] resistor: Iterable[str], capacitor: Iterable[str], inductor: Iterable[str]
) -> "DataSet": ) -> "DatasetCollection":
return DataSet( return DatasetCollection(
DataSubset.from_iterable(resistor), Dataset.from_iterable(resistor),
DataSubset.from_iterable(capacitor), Dataset.from_iterable(capacitor),
DataSubset.from_iterable(inductor), Dataset.from_iterable(inductor),
) )
@staticmethod @staticmethod
def from_file(resistor: Path, capacitor: Path, inductor: Path) -> "DataSet": def from_text(resistor: str, capacitor: str, inductor: str) -> "DatasetCollection":
return DataSet( return DatasetCollection(
DataSubset.from_file(resistor), Dataset.from_text(resistor),
DataSubset.from_file(capacitor), Dataset.from_text(capacitor),
DataSubset.from_file(inductor), 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)

View File

@@ -12,23 +12,23 @@ def _get_joint_kind_symbol(joint_kind: JointKind) -> str:
def _illustrate_circuit(circuit: Circuit) -> None: def _illustrate_circuit(circuit: Circuit) -> None:
match circuit.len_devices(): match circuit.len_devices():
case 1: case 1:
dev1 = to_human_readable_value(circuit.device_value) dev1 = to_human_readable_value(circuit.__first_device_value)
print(f"{dev1}") print(f"{dev1}")
case 2: case 2:
dev1 = to_human_readable_value(circuit.device_value) dev1 = to_human_readable_value(circuit.__first_device_value)
joint1 = circuit.joints[0] joint1 = circuit.joints[0]
j1 = _get_joint_kind_symbol(joint1.joint_kind) j1 = _get_joint_kind_symbol(joint1.__joint_kind)
dev2 = to_human_readable_value(joint1.device_value) dev2 = to_human_readable_value(joint1.__device_value)
print(f"[{j1}] ┬ {dev1}") print(f"[{j1}] ┬ {dev1}")
print(f"{dev2}") print(f"{dev2}")
case 3: case 3:
dev1 = to_human_readable_value(circuit.device_value) dev1 = to_human_readable_value(circuit.__first_device_value)
joint1 = circuit.joints[0] joint1 = circuit.joints[0]
j1 = _get_joint_kind_symbol(joint1.joint_kind) j1 = _get_joint_kind_symbol(joint1.__joint_kind)
dev2 = to_human_readable_value(joint1.device_value) dev2 = to_human_readable_value(joint1.__device_value)
joint2 = circuit.joints[1] joint2 = circuit.joints[1]
j2 = _get_joint_kind_symbol(joint2.joint_kind) j2 = _get_joint_kind_symbol(joint2.__joint_kind)
dev3 = to_human_readable_value(joint2.device_value) dev3 = to_human_readable_value(joint2.__device_value)
print(f"[{j2}] ┬ [{j1}] ┬ {dev1}") print(f"[{j2}] ┬ [{j1}] ┬ {dev1}")
print(f" │ └ {dev2}") print(f" │ └ {dev2}")
print(f"{dev3}") print(f"{dev3}")

View File

@@ -1,13 +1,13 @@
from typing import Iterator from typing import Iterator
from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority
from ..dataset import DataSet from ..dataset import DatasetCollection
class AStarResolver(Resolver): class AStarResolver(Resolver):
""" """
A resolver that uses A* algorithm to find the best matching circuit. A resolver that uses A* algorithm to find the best matching circuit.
""" """
def __init__(self, dataset: DataSet): def __init__(self, dataset: DatasetCollection):
pass pass

View File

@@ -2,8 +2,8 @@ import struct
from typing import Iterator, BinaryIO from typing import Iterator, BinaryIO
from pathlib import Path from pathlib import Path
from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority from .common import Resolver, ResolverRequest, ResolverResult, ResultPriority
from ..dataset import DataSet from ..dataset import DatasetCollection
from ..common import Circuit, CircuitJoint, JointKind, LcrConnException from ..common import Circuit, SubCircuit, JointKind, LcrConnException
class LutResolver(Resolver): class LutResolver(Resolver):
""" """
@@ -16,7 +16,7 @@ class LutResolver(Resolver):
self.lut = lut self.lut = lut
@staticmethod @staticmethod
def from_dataset(dataset: DataSet) -> 'LutResolver': def from_dataset(dataset: DatasetCollection) -> 'LutResolver':
pass pass
@staticmethod @staticmethod
@@ -61,14 +61,14 @@ class LutItem:
for _ in range(cnt): for _ in range(cnt):
j = JointKind.SERIES if _read_bool(f) else JointKind.PARALLEL j = JointKind.SERIES if _read_bool(f) else JointKind.PARALLEL
dev = _read_double(f) dev = _read_double(f)
joint = CircuitJoint(j, dev) joint = SubCircuit(j, dev)
circuit.add_joint(joint) circuit.add_joint(joint)
return LutItem(circuit) return LutItem(circuit)
def save_as_cache(self, f: BinaryIO) -> None: def save_as_cache(self, f: BinaryIO) -> None:
_write_int(f, self.circuit.len_devices()) _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(): for joint in self.circuit.joints():
_write_bool(f, joint.kind == JointKind.SERIES) _write_bool(f, joint.kind == JointKind.SERIES)
_write_double(f, joint.value) _write_double(f, joint.value)