2026-06-02 21:08:48 +08:00
|
|
|
import enum
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
|
|
|
|
|
class LcrConnException(Exception):
|
|
|
|
|
"""The exception thrown by LCR Connector"""
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DeviceKind(enum.IntEnum):
|
|
|
|
|
"""The kind of device"""
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
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"""
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
SERIES = enum.auto()
|
|
|
|
|
"""Series connection"""
|
|
|
|
|
PARALLEL = enum.auto()
|
|
|
|
|
"""Parallel connection"""
|
2026-06-15 13:47:31 +08:00
|
|
|
|
|
|
|
|
def flip(self) -> "JointKind":
|
2026-06-02 21:08:48 +08:00
|
|
|
"""
|
|
|
|
|
Flip the joint kind
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
class SubCircuit:
|
2026-06-02 21:08:48 +08:00
|
|
|
"""The part of circuit composed of two devices and the joint kind"""
|
|
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
__device_value: float
|
2026-06-02 21:08:48 +08:00
|
|
|
"""The value of the device"""
|
2026-06-15 13:47:31 +08:00
|
|
|
__joint_kind: JointKind
|
2026-06-02 21:08:48 +08:00
|
|
|
"""The joint kind between this device and the next device"""
|
2026-06-15 13:47:31 +08:00
|
|
|
|
|
|
|
|
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:
|
2026-06-02 21:08:48 +08:00
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
"""
|
2026-06-15 13:47:31 +08:00
|
|
|
if self.__device_value <= 0 or value <= 0:
|
2026-06-02 21:08:48 +08:00
|
|
|
raise LcrConnException("Device value must be greater than 0")
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-02 21:08:48 +08:00
|
|
|
# We perform series connect for: series resistor, series inductor and parallel capacitor.
|
|
|
|
|
# We perform parallel connect for: parallel resistor, parallel inductor and series capacitor.
|
2026-06-15 13:47:31 +08:00
|
|
|
joint_kind = self.__joint_kind
|
2026-06-02 21:08:48 +08:00
|
|
|
if device_kind == DeviceKind.CAPACITOR:
|
|
|
|
|
joint_kind = joint_kind.flip()
|
|
|
|
|
|
|
|
|
|
match joint_kind:
|
|
|
|
|
case JointKind.SERIES:
|
2026-06-15 13:47:31 +08:00
|
|
|
return self.__device_value + value
|
2026-06-02 21:08:48 +08:00
|
|
|
case JointKind.PARALLEL:
|
2026-06-15 13:47:31 +08:00
|
|
|
return (self.__device_value * value) / (self.__device_value + value)
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def device_value(self) -> float:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the device value
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The device value
|
|
|
|
|
"""
|
|
|
|
|
return self.__device_value
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def joint_kind(self) -> JointKind:
|
2026-06-02 21:08:48 +08:00
|
|
|
"""
|
2026-06-15 13:47:31 +08:00
|
|
|
Get the joint kind
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The joint kind
|
2026-06-02 21:08:48 +08:00
|
|
|
"""
|
2026-06-15 13:47:31 +08:00
|
|
|
return self.__joint_kind
|
2026-06-02 21:08:48 +08:00
|
|
|
|
|
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
class CircuitDeviceScale(enum.IntEnum):
|
|
|
|
|
"""The scale of devices in the circuit"""
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
ONE = enum.auto()
|
|
|
|
|
"""One device"""
|
|
|
|
|
TWO = enum.auto()
|
|
|
|
|
"""Two devices"""
|
|
|
|
|
THREE = enum.auto()
|
|
|
|
|
"""Three devices"""
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
def to_device_count(self) -> int:
|
2026-06-15 15:58:04 +08:00
|
|
|
"""
|
|
|
|
|
Convert circuit device scale to device count
|
|
|
|
|
|
|
|
|
|
:return: The device count
|
|
|
|
|
"""
|
2026-06-15 13:47:31 +08:00
|
|
|
match self:
|
2026-06-15 15:58:04 +08:00
|
|
|
case CircuitDeviceScale.ONE:
|
2026-06-15 13:47:31 +08:00
|
|
|
return 1
|
2026-06-15 15:58:04 +08:00
|
|
|
case CircuitDeviceScale.TWO:
|
2026-06-15 13:47:31 +08:00
|
|
|
return 2
|
2026-06-15 15:58:04 +08:00
|
|
|
case CircuitDeviceScale.THREE:
|
2026-06-15 13:47:31 +08:00
|
|
|
return 3
|
2026-06-02 21:08:48 +08:00
|
|
|
|
|
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
class Circuit:
|
|
|
|
|
"""The circuit composed of multiple joints"""
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
__first_device_value: float
|
|
|
|
|
"""The value of the first device"""
|
|
|
|
|
__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,
|
|
|
|
|
first_device_value: float,
|
|
|
|
|
second_device_subckt: SubCircuit | None,
|
|
|
|
|
third_device_subckt: SubCircuit | None,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Initialize the circuit
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
: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")
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
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.__first_device_value <= 0:
|
|
|
|
|
raise LcrConnException("Device value must be greater than 0")
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def device_scale(self) -> CircuitDeviceScale:
|
|
|
|
|
"""
|
|
|
|
|
Get the device scale
|
|
|
|
|
|
|
|
|
|
:return: The device scale
|
|
|
|
|
"""
|
2026-06-15 13:47:31 +08:00
|
|
|
if self.__third_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return CircuitDeviceScale.THREE
|
2026-06-15 13:47:31 +08:00
|
|
|
elif self.__second_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return CircuitDeviceScale.TWO
|
2026-06-15 13:47:31 +08:00
|
|
|
else:
|
2026-06-15 15:58:04 +08:00
|
|
|
return CircuitDeviceScale.ONE
|
2026-06-15 13:47:31 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def first_device_value(self) -> float:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the value of the first device
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The value of the first device
|
|
|
|
|
"""
|
|
|
|
|
return self.__first_device_value
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def second_device_joint(self) -> JointKind:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the joint kind of the second device
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The joint kind of the second device
|
|
|
|
|
:raises LcrConnException: If there is no second device
|
|
|
|
|
"""
|
|
|
|
|
if self.__second_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return self.__second_device_subckt.joint_kind
|
2026-06-15 13:47:31 +08:00
|
|
|
else:
|
|
|
|
|
raise LcrConnException("No second device")
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def second_device_value(self) -> float:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the value of the second device
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The value of the second device
|
|
|
|
|
:raises LcrConnException: If there is no second device
|
|
|
|
|
"""
|
|
|
|
|
if self.__second_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return self.__second_device_subckt.device_value
|
2026-06-15 13:47:31 +08:00
|
|
|
else:
|
|
|
|
|
raise LcrConnException("No second device")
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def third_device_joint(self) -> JointKind:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the joint kind of the third device
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The joint kind of the third device
|
|
|
|
|
:raises LcrConnException: If there is no third device
|
|
|
|
|
"""
|
|
|
|
|
if self.__third_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return self.__third_device_subckt.joint_kind
|
2026-06-15 13:47:31 +08:00
|
|
|
else:
|
|
|
|
|
raise LcrConnException("No third device")
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 15:58:04 +08:00
|
|
|
@property
|
|
|
|
|
def third_device_value(self) -> float:
|
2026-06-15 13:47:31 +08:00
|
|
|
"""
|
|
|
|
|
Get the value of the third device
|
2026-06-02 21:08:48 +08:00
|
|
|
|
2026-06-15 13:47:31 +08:00
|
|
|
:return: The value of the third device
|
|
|
|
|
:raises LcrConnException: If there is no third device
|
|
|
|
|
"""
|
|
|
|
|
if self.__third_device_subckt is not None:
|
2026-06-15 15:58:04 +08:00
|
|
|
return self.__third_device_subckt.device_value
|
2026-06-15 13:47:31 +08:00
|
|
|
else:
|
|
|
|
|
raise LcrConnException("No third device")
|