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)