175 lines
4.5 KiB
Python
175 lines
4.5 KiB
Python
|
|
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)
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|