################################################################################"""Visualizations Specifically for Electrical Engineering.Filled with plotting functions and visualization tools for electrical engineers,this module is designed to assist engineers visualize their designs."""################################################################################importcmathas_cimportnumpyas_npimportmatplotlibas_matplotlibimportmatplotlib.pyplotas_pltfromelectricpyimportpowerset,geometryfromelectricpy.geometryimportPointfromelectricpy.geometry.circleimportCircle# Define Power Triangle Function
[docs]defpowertriangle(P=None,Q=None,S=None,PF=None,color="red",text="Power Triangle",printval=False):""" Power Triangle Plotting Function. This function is designed to draw a power triangle given values for the complex power system. .. image:: /static/PowerTriangle.png Parameters ---------- P: float Real Power, unitless, default=None Q: float Reactive Power, unitless, default=None S: float Apparent Power, unitless, default=None PF: float Power Factor, unitless, provided as a decimal value, lagging is positive, leading is negative; default=None color: string, optional The color of the power triangle lines, default="red" text: string, optional The title of the power triangle plot, default="Power Triangle" printval: bool, optional Control argument to allow the numeric values to be printed on the plot, default="False" Returns ------- matplotlib.pyplot: Plotting object to be used for additional configuration or plotting. """# Calculate all values if not all are providedifPisNoneorQisNoneorSisNoneorPFisNone:P,Q,S,PF=powerset(P,Q,S,PF)# Generate Linesp_line_x=[0,P]p_line_y=[0,0]q_line_x=[P,P]q_line_y=[0,Q]s_line_x=[0,P]s_line_y=[0,Q]# Plot Power Triangle_plt.figure(1)_plt.title(text)_plt.plot(p_line_x,p_line_y,color=color)_plt.plot(q_line_x,q_line_y,color=color)_plt.plot(s_line_x,s_line_y,color=color)_plt.xlabel("Real Power (W)")_plt.ylabel("Reactive Power (VAR)")maximum=max(abs(P),abs(Q))ifP>0:_plt.xlim(0,maximum*1.1)x=maximumelse:_plt.xlim(-maximum*1.1,0)x=-maximumifQ>0:_plt.ylim(0,maximum*1.1)y=maximumelse:_plt.ylim(-maximum*1.1,0)y=-maximumifPF>0:power_factor_text="Lagging"else:power_factor_text="Leading"# Print all values if asked toifprintval:_plt.text(x/20,y*4/5,(f"P: {P} W\n"f"Q: {Q} VAR\n"f"S: {S} VA\n"f"PF: {abs(PF)}{power_factor_text}\n"f"ΘPF: {_np.degrees(_np.arccos(PF))}° {power_factor_text}"),color=color,)return_plt
# Define Convolution Bar-Graph Function:
[docs]defconvbar(h,x,outline=True):""" Convolution Bar-Graph Plotter Function. Generates plots of each of two input arrays as bar-graphs, then generates a convolved bar-graph of the two inputs to demonstrate and illustrate convolution, typically for an educational purpose. Examples -------- >>> import numpy as np >>> import electricpy.visu as visu >>> h = np.array([0, 1, 1, 1, 0]) >>> x = np.array([0, 1, 1, 1, 0]) >>> visu.convbar(h, x) .. image:: /static/convbar-example.png Parameters ---------- h: numpy.ndarray Impulse Response - Given as Array (Prefferably Numpy Array) x: numpy.ndarray Input Function - Given as Array (Prefferably Numpy Array) """# The impulse responseM=len(h)t=_np.arange(M)# Plot_plt.subplot(121)ifoutline:_plt.plot(t,h,color="red")_plt.bar(t,h,color="black")_plt.xticks([0,5,9])_plt.ylabel("h")_plt.title("Impulse Response")_plt.grid()# The input functionN=len(x)s=_np.arange(N)# Plot_plt.subplot(122)ifoutline:_plt.plot(s,x,color="red")_plt.bar(s,x,color="black")_plt.xticks([0,10,19])_plt.title("Input Function")_plt.grid()_plt.ylabel("x")# The outputL=M+N-1w=_np.arange(L)_plt.figure(3)y=_np.convolve(h,x)ifoutline:_plt.plot(w,y,color="red")_plt.bar(w,y,color="black")_plt.ylabel("y")_plt.grid()_plt.title("Convolved Output")return_plt
# Define Phasor Plot Generator
[docs]defphasorplot(phasors,title="Phasor Diagram",legend=False,bg=None,colors=None,radius=None,linewidth=None,size=None,label=False,labels=False,tolerance=None,):""" Phasor Plotting Function. This function is designed to plot a phasor-diagram with angles in degrees for up to 12 phasor sets (more may be used if additional colors are set). Phasors must be passed as a complex number set, (e.g. [ m+ja, m+ja, m+ja, ... , m+ja ] ). Examples -------- >>> import numpy as np >>> from electricpy import phasors >>> from electricpy import visu >>> voltages = np.array([ ... [67,0], ... [45,-120], ... [52,120] ... ]) >>> phasors = phasors.phasorlist(voltages) >>> plt = visu.phasorplot(phasors, colors=["red", "green", "blue"]) >>> plt.show() .. image:: /static/PhasorPlot.png Parameters ---------- phasors: list of complex The set of phasors to be plotted. title: string, optional The Plot Title, default="Phasor Diagram" legend: bool, optional Control argument to enable displaying the legend, must be passed as an array or list of strings. `label` and `labels` are mimic- arguments and will perform similar operation, default=False bg: string, optional Background-Color control, default="#d5de9c" radius: float, optional The diagram radius, unless specified, automatically scales colors: list of str, optional List of hexidecimal color strings denoting the line colors to use. size: float, optional Control argument for figure size. default=None linewidth: float, optional Control argument to declare the line thickness. default=None tolerance: float, optional Minimum magnitude to plot, anything less than tolerance will be plotted as a single point at the origin, by default, the tolerance is scaled to be 1/25-th the maximum radius. To disable the tolerance, simply provide either False or -1. Returns ------- matplotlib.pyplot: Plotting object to be used for additional configuration or plotting. """# Load Complex Values if Necessarytry:len(phasors)exceptTypeError:phasors=[phasors]# Manage ColorsifcolorsisNone:colors=["#FF0000","#800000","#FFFF00","#808000","#00ff00","#008000","#00ffff","#008080","#0000ff","#000080","#ff00ff","#800080",]# Scale RadiusifradiusisNone:radius=_np.abs(phasors).max()# Set ToleranceiftoleranceisNone:tolerance=radius/25eliftoleranceisFalse:tolerance=-1# Set Background ColorifbgisNone:bg="#FFFFFF"# Load labels if handled in other argumentiflabel:legend=labeliflabels:legend=labels# Check for more phasors than colorsiflen(phasors)>len(colors):raiseValueError("ERROR: Too many phasors provided. Specify more line colors.")ifsizeisNone:# Force square figure and square axeswidth,height=_matplotlib.rcParams["figure.figsize"]size=min(width,height)# Make a square figurefig=_plt.figure(figsize=(size,size))ax=fig.add_axes([0.1,0.1,0.8,0.8],polar=True,facecolor=bg)_plt.grid(True)# Plot the diagram_plt.title(title+"\n")arrows=[]fori,phasorinenumerate(phasors):mag,ang_r=_c.polar(phasor)# Plot with labelsiflegend:ifmag>tolerance:arrows.append(_plt.arrow(0,0,ang_r,mag,color=colors[i],label=legend[i],linewidth=linewidth,))else:arrows.append(_plt.plot(0,0,"o",markersize=linewidth*3,label=legend[i],color=colors[i],))# Plot without labelselse:_plt.arrow(0,0,ang_r,mag,color=colors[i],linewidth=linewidth)iflegend:_plt.legend(arrows,legend)# Set Minimum and Maximum Radius Termsax.set_rmax(radius)ax.set_rmin(0)return_plt
[docs]classInductionMotorCircle:""" Plot Induction Motor Circle Diagram. This class is designed to plot induction motor circle diagram and plot circle diagram to obtain various parameters of induction motor. Examples -------- >>> from electricpy.visu import InductionMotorCircle >>> open_circuit_test_data = {'V0': 400, 'I0': 9, 'W0': 1310} >>> blocked_rotor_test_data = {'Vsc': 200, 'Isc': 50, 'Wsc': 7100} >>> ratio = 1 # stator copper loss/ rotor copper loss >>> output_power = 15000 >>> InductionMotorCircle( ... no_load_data=open_circuit_test_data, ... blocked_rotor_data=blocked_rotor_test_data, ... output_power=output_power, ... torque_ration=ratio, ... frequency=50, ... poles=4 ... ) .. image:: /static/InductionMotorCircleExample.png Parameters ---------- no_load_data: dict {'V0', 'I0', 'W0'} V0: no load test voltage I0: no load current in rotor W0: No load power(in Watts) blocked_rotor_data: dict {'Vsc','Isc','Wsc'} Vsc: blocked rotor terminal voltage Isc: blocked rotor current in rotor Wsc: Power consumed in blocked rotor test output_power: int Desired power output from the induction motor torque_ration: float Ration between rotor resistance to stator resistance (i.e., R2/R1) frequency: int AC supply frequency poles: int Pole count of induction Motor """
[docs]def__init__(self,no_load_data,blocked_rotor_data,output_power,torque_ration=1,frequency=50,poles=4,):"""Primary Entrypoint."""self.no_load_data=no_load_dataself.blocked_rotor_data=blocked_rotor_dataself.frequency=frequencyself.operating_power=output_powerself.torque_ratio=torque_rationself.poles=polesself.sync_speed=120*frequency/poles# rpmv0=no_load_data["V0"]i0=no_load_data["I0"]w0=no_load_data["W0"]self.no_load_pf=w0/(_np.sqrt(3)*v0*i0)theta0=_np.arccos(self.no_load_pf)# get short circuit power factor and Current at slip=1vsc=blocked_rotor_data["Vsc"]isc=blocked_rotor_data["Isc"]wsc=blocked_rotor_data["Wsc"]self.blocked_rotor_pf=wsc/(_np.sqrt(3)*vsc*isc)theta_sc=_np.arccos(self.blocked_rotor_pf)# because V is on Y axistheta0=_np.pi/2-theta0theta_sc=_np.pi/2-theta_sc# isc is the current at reduced voltage# calculate current at rated voltageisc=v0*isc/vscself.no_load_line=[[0,i0*_np.cos(theta0)],[0,i0*_np.sin(theta0)]]self.full_load_line=[[0,isc*_np.cos(theta_sc)],[0,isc*_np.sin(theta_sc)],]# secondary current lineself.secondary_current_line=[[i0*_np.cos(theta0),isc*_np.cos(theta_sc)],[i0*_np.sin(theta0),isc*_np.sin(theta_sc)],][[x1,x2],[y1,y2]]=self.secondary_current_lineself.theta=_np.arctan((y2-y1)/(x2-x1))# get the induction motor circleself.power_scale=w0/(i0*_np.sin(theta0))self.center,self.radius=self.compute_circle_params()[self.center_x,self.center_y]=self.centerself.p_max=self.radius*_np.cos(self.theta)-(self.radius-self.radius*_np.sin(self.theta))*_np.tan(self.theta)self.torque_line,self.torque_point=self.get_torque_line()self.torque_max,self.torque_max_x,self.torque_max_y=self.get_torque_max()# Take low slip point_,[self.power_x,self.power_y]=self.get_output_power()self.data=self.compute_efficiency()
def__call__(self):# noqa: D102__doc__=self.__doc__returnself.datadefplot(self):"""Plot the Induction Motor Circle Diagram."""[circle_x,circle_y]=InductionMotorCircle.__get_circle(self.center,self.radius,semi=True)_plt.plot(circle_x,circle_y)InductionMotorCircle.__plot_line(self.no_load_line)InductionMotorCircle.__plot_line(self.secondary_current_line)InductionMotorCircle.__plot_line(self.full_load_line,ls="-.")InductionMotorCircle.__plot_line(self.torque_line,ls="-.")# Full load output_plt.plot([self.secondary_current_line[0][1],self.secondary_current_line[0][1]],[self.secondary_current_line[1][1],self.center_y],)# Diameter of the circle_plt.plot([self.center_x-self.radius,self.center_x+self.radius],[self.center_y,self.center_y],ls="-.",)# Max torque line_plt.plot([self.center_x,self.torque_max_x],[self.center_y,self.torque_max_y],ls="-.",)# Max Output Power line_plt.plot([self.center_x,self.center_x-self.radius*_np.sin(self.theta)],[self.center_y,self.center_y+self.radius*_np.cos(self.theta)],ls="-.",)# Operating Point_plt.plot([0,self.power_x],[0,self.power_y],c="black")_plt.scatter(self.power_x,self.power_y,marker="X",c="red",label="_nolegend_")# mark the center of the circle_plt.scatter(self.center_x,self.center_y,marker="*",c="blue",label="_nolegend_")_plt.scatter(self.center_x-self.radius*_np.sin(self.theta),self.center_y+self.radius*_np.cos(self.theta),linewidths=3,c="black",marker="*",label="_nolegend_",)_plt.scatter(self.torque_max_x,self.torque_max_y,linewidths=3,c="black",marker="*",label="_nolegend_",)_plt.title("Induction Motor Circle Diagram")_plt.grid()_plt.legend(["I2 locus","No Load Current","Output Line","Blocked Rotor Current","Torque line","Full Load Losses","Diameter","Maximum Torque","Maximum Output Power",f"Operating Power {self.operating_power}",])return_pltdefcompute_efficiency(self):"""Compute the output efficiency of induction motor."""[[_,no_load_x],[_,no_load_y]]=self.no_load_lineno_load_losses=no_load_y*self.power_scalecompute_slope=InductionMotorCircle.compute_slopetorque_slope=compute_slope(self.torque_line)stator_cu_loss=(self.power_x-no_load_x)*torque_slope*self.power_scalerotor_current_slope=compute_slope(self.secondary_current_line)total_cu_loss=((self.power_x-no_load_x)*rotor_current_slope*self.power_scale)rotor_cu_loss=total_cu_loss-stator_cu_lossrotor_output=self.power_y*self.power_scale-(rotor_cu_loss+stator_cu_loss+no_load_losses)slip=rotor_cu_loss/rotor_outputself.rotor_speed=self.sync_speed*(1-slip)data={"no_load_loss":no_load_losses,"rotor_copper_loss":rotor_cu_loss,"stator_copper_loss":stator_cu_loss,"rotor_output":rotor_output,"slip":slip,"stator_rmf_speed (RPM)":self.sync_speed,"rotor_speed (RMP)":self.rotor_speed,"power_factor":(self.power_y/_np.sqrt(self.power_x**2+self.power_y**2)),"efficiency":f"{rotor_output*100/(self.power_y*self.power_scale)} %",}returndata@staticmethoddef__get_circle(center,radius,semi=False):""" Determine parametric equation of circle. Parameters ---------- center: list[float, float] [x0, y0] radius: float Returns ------- (x, y): tuple parametric equation of circle (x = x0 + r*cos(theta) ; y = y0 + r*sin(theta)) """[x0,y0]=centerifsemi:theta=_np.arange(0,_np.pi,1e-4)else:theta=_np.arange(0,_np.pi*2,1e-4)x=x0+radius*_np.cos(theta)y=y0+radius*_np.sin(theta)returnx,y@staticmethoddef__plot_line(line,mark_start=True,mark_end=True,ls="-",marker=None):"""Supporting function to plot a line."""[x,y]=line[x1,x2]=x[y1,y2]=y_plt.plot(x,y,ls=ls)ifmark_start:_plt.scatter(x1,y1,marker=marker,label="_nolegend_")ifmark_end:_plt.scatter(x2,y2,marker=marker,label="_nolegend_")defcompute_circle_params(self):"""Compute the parameters of induction motor circle."""[[x1,x2],[y1,y2]]=self.secondary_current_linetheta=_np.arctan((y2-y1)/(x2-x1))length=_np.sqrt((x2-x1)**2+(y2-y1)**2)radius=length/(2*_np.cos(theta))center=[radius+x1,y1]returncenter,radiusdefget_torque_line(self):"""Obtain the torque line of the induction motor."""[[x1,x2],[y1,y2]]=self.secondary_current_liney=(self.torque_ratio*y2+y1)/(self.torque_ratio+1)torque_point=[x2,y]torque_line=[[x1,x2],[y1,y]]returntorque_line,torque_pointdefget_torque_max(self):"""Compute max torque for given Induction Motor parameters."""[x,y]=self.torque_line[x1,x2]=x[y1,y2]=yalpha=_np.arctan((y2-y1)/(x2-x1))torque_max=self.radius*_np.cos(alpha)-(self.radius-self.radius*_np.sin(alpha))*_np.tan(alpha)torque_max_x=self.center_x-self.radius*_np.sin(alpha)torque_max_y=self.center_y+self.radius*_np.cos(alpha)returntorque_max,torque_max_x,torque_max_y@staticmethoddefcompute_slope(line):""" Compute slope of the line. Parameters ---------- line: list[float, float] Returns ------- slope: float """[[x1,x2],[y1,y2]]=linereturn(y2-y1)/(x2-x1)defget_output_power(self):""" Determine induction motor circle desired output power point. Obtain the point on the induction motor circle diagram which corresponds to the desired output power """[[x1,x2],[y1,y2]]=self.secondary_current_linealpha=_np.arctan((y2-y1)/(x2-x1))[center_x,center_y]=self.center[[_,no_load_x],[_,_]]=self.no_load_linebeta=_np.arcsin((self.operating_power/self.power_scale+(center_x-no_load_x)*_np.tan(alpha))*_np.cos(alpha)/self.radius)beta_0=alpha+betabeta_1=-alpha+beta# high slipp_x_1=center_x+self.radius*_np.cos(beta_0)p_y_1=center_y+self.radius*_np.sin(beta_0)# low slipp_x_2=center_x-self.radius*_np.cos(beta_1)p_y_2=center_y+self.radius*_np.sin(beta_1)return[p_x_1,p_y_1],[p_x_2,p_y_2]
[docs]classPowerCircle:r""" Plot Power Circle Diagram of Transmission System. This class is designed to plot the power circle diagram of a transmission system both sending and receiving ends. Examples -------- >>> import math, cmath >>> from electricpy import visu >>> visu.PowerCircle( ... power_circle_type="receiving", ... A=cmath.rect(0.895, math.radians(1.4)), ... B=cmath.rect(182.5, math.radians(78.6)), ... Vr=cmath.rect(215, 0), ... Pr=50, ... power_factor=-0.9 ... ) .. image:: /static/ReceivingPowerCircleExample.png Parameters ---------- power_circle_type: ["sending", "receiving"] Type of power circle diagram to plot. Vr: complex Transmission Line Receiving End Voltage (phasor complex value) Vs: complex Transmission Line Sending End Voltage (phasor complex value) power_factor: float Power Factor of the transmission system, default = None Pr: float Receiving End Real Power, default = None Qr: float Receiving End Reactive Power, default = None Sr: complex Receiving End Total Complex Power, default = None Ps: float Sending End Real Power, default = None Qs: float Sending End Reactive Power, default = None Ss: complex Sending End Total Complex Power, default = None A: float Transmission System ABCD Parameters, A, default = None B: float Transmission System ABCD Parameters, B, default = None C: float Transmission System ABCD Parameters, C, default = None D: float Transmission System ABCD Parameters, D, default = None """
[docs]def__init__(self,power_circle_type:str,power_factor:float=None,Vr:complex=None,Vs:complex=None,Pr:float=None,Qr:float=None,Sr:complex=None,Ps:float=None,Qs:float=None,Ss:complex=None,A:complex=None,B:complex=None,C:complex=None,D:complex=None,)->None:r"""Initialize the class."""ifCisnotNone:assert(abs(A*D-B*C-1)<1e-6),"ABCD Matrix is not a valid ABCD Matrix"ifpower_circle_type.lower()=="receiving":ifAisnotNoneandBisnotNoneandVrisnotNone:(self.radius,self.center,self.operating_point,)=PowerCircle._build_circle(A,B,"receiving_end",Vr,Pr,Qr,Sr,power_factor,Vs)else:raiseValueError("Not enough attributes to build circle")elifpower_circle_type.lower()=="sending":ifBisnotNoneandDisnotNoneandVsisnotNone:(self.radius,self.center,self.operating_point,)=PowerCircle._build_circle(D,B,"sending_end",Vs,Ps,Qs,Ss,power_factor,Vr)else:raiseValueError("Not enough attributes to build power circle")else:raiseValueError("Invalid power circle type")self.circle=Circle(self.center,self.radius)self.parameters=locals()
@staticmethoddef_build_circle(a1,a2,circle_type,V,P=None,Q=None,S=None,power_factor=None,V_ref=None):k=(abs(V)**2)*abs(a1)/abs(a2)alpha=_c.phase(a1)beta=_c.phase(a2)ifcircle_type=="receiving_end":center=Point(-k*_c.cos(alpha-beta),-k*_c.sin(alpha-beta))elifcircle_type=="sending_end":center=Point(k*_c.cos(alpha-beta),-k*_c.sin(alpha-beta))ifV_refisnotNoneandPisnotNoneandQisnotNone:radius=abs(V)*abs(V_ref)/(abs(a2))operation_point=Point(P,Q)elifV_refisnotNoneandSisnotNone:radius=abs(V)*abs(V_ref)/(abs(a2))operation_point=Point(S.real,S.imag)elifPisnotNoneandQisnotNone:radius=geometry.distance(center,Point(P,Q))operation_point=Point(P,Q)elifSisnotNone:radius=geometry.distance(center,Point(S.real,S.imag))operation_point=Point(S.real,S.imag)elifPisnotNoneandpower_factorisnotNone:Q=P*_c.sqrt(1/power_factor**2-1).realifpower_factor<0:Q=-1*Qradius=geometry.distance(center,Point(P,Q))operation_point=Point(P,Q)elifQisnotNoneandpower_factorisnotNone:P=Q/_c.sqrt(1/power_factor**2-1).realradius=geometry.distance(center,Point(P,Q))operation_point=Point(P,Q)else:raiseAttributeError("Not enough attributes found to perform calculation")returnradius,center,operation_pointdef_cal_parameters(self,type1,type2):ifself.parameters["V"+type2]isNone:self.parameters["V"+type2]=(abs(self.parameters["B"])*self.radius/self.parameters["V"+type1])ifself.parameters["P"+type1]isNone:self.parameters["P"+type1]=self.operating_point.xifself.parameters["Q"+type1]isNone:self.parameters["Q"+type1]=self.operating_point.yifself.parameters["S"+type1]==None:self.parameters["S"+type1]=(self.operating_point.x+1j*self.operating_point.y)ifself.parameters["power_factor"]isNone:self.parameters["power_factor"]=(self.operating_point.y/self.operating_point.x)iftype1=="r"andtype2=="s":self.parameters["Vs"]=(self.parameters["B"]*self.parameters["Sr"]+self.parameters["A"]*abs(self.parameters["Vr"])**2)self.parameters["Vs"]=(self.parameters["Vs"]/self.parameters["Vr"].conjugate())eliftype1=="s"andtype2=="r":self.parameters["Vr"]=(-self.parameters["B"]*self.parameters["Ss"]+self.parameters["D"]*abs(self.parameters["Vs"])**2)self.parameters["Vr"]=(self.parameters["Vr"]/self.parameters["Vs"].conjugate())defprint_data(self):r"""Print the data of the circle."""ifself.operating_pointisNone:returnself.center,self.radiusifself.parameters["power_circle_type"]=="receiving":self._cal_parameters("r","s")ifself.parameters["power_circle_type"]=="sending":self._cal_parameters("s","r")forkey,valueinself.parameters.items():print(key," => ",value)def__call__(self)->dict:r"""Return the data of the circle."""ifself.parameters["power_circle_type"]=="receiving":self._cal_parameters("r","s")ifself.parameters["power_circle_type"]=="sending":self._cal_parameters("s","r")returnself.parametersdefplot(self):r"""Plot the circle."""circle_x=[]circle_y=[]fordatainself.circle.parametric_equation(theta_resolution=1e-5):[x,y]=datacircle_x.append(x)circle_y.append(y)c_x=self.center.xc_y=self.center.yop_x=self.operating_point.xop_y=self.operating_point.y# plot Circle and Diameter_plt.plot(circle_x,circle_y)_plt.plot([c_x-self.radius,c_x+self.radius],[c_y,c_y],"g--")_plt.plot([c_x,c_x],[c_y-self.radius,c_y+self.radius],"g--")_plt.plot([c_x,op_x],[c_y,op_y],"y*-.")_plt.plot([op_x,op_x],[op_y,c_y],"b*-.")_plt.scatter(op_x,op_y,marker="*",color="r")_plt.title(f"{self.parameters['power_circle_type'].capitalize()} Power Circle")_plt.xlabel("Active Power")_plt.ylabel("Reactive Power")_plt.grid()return_plt
[docs]defreceiving_end_power_circle(Vr:complex=None,A:complex=None,B:complex=None,Pr:float=None,Qr:float=None,Sr:complex=None,power_factor:float=None,Vs:complex=None,)->PowerCircle:""" Construct Receiving End Power Circle. Examples -------- >>> import math, cmath >>> from electricpy import visu >>> visu.receiving_end_power_circle( ... A=cmath.rect(0.895, math.radians(1.4)), ... B=cmath.rect(182.5, math.radians(78.6)), ... Vr=cmath.rect(215, 0), ... Pr=50, ... power_factor=-0.9 ... ) .. image:: /static/ReceivingEndPowerCircleExample.png Parameters ---------- Vr: complex Receiving End Voltage, default = None. A: complex Transmission System ABCD Parameters, A, default = None. B: complex, Transmission System ABCD Parameters, B, default = None. Pr: float, optional Receiving End Real Power, default = None Qr: float, optional Receiving End Reactive Power, default = None Sr: complex, optional Receiving End Apparent Power, default = None power_factor: float, optional System End Power Factor, default = None Vs: complex, optional Sending End Voltage, default = None Returns ------- Receiving End Power Circle: PowerCircle """try:assertVrisnotNoneandAisnotNoneandBisnotNoneexceptAssertionError:raiseValueError("Not enough attributes to build Receiving end power circle at least"" provide `Vr`, `A`, `B`")ifnot(((PrisnotNoneandQrisnotNone)or(SrisnotNoneandpower_factorisnotNone))or((PrisnotNoneandpower_factorisnotNone)or(QrisnotNoneandpower_factorisnotNone))):raiseValueError("Not enough attributes for marking an operating point on Receiving ""End Power Circle")returnPowerCircle("receiving",**{"Vr":Vr,"A":A,"B":B,"Pr":Pr,"Qr":Qr,"Sr":Sr,"Vs":Vs,"power_factor":power_factor,},)
[docs]defsending_end_power_circle(Vs:complex=None,B:complex=None,D:complex=None,Ps:float=None,Qs:float=None,Ss:complex=None,power_factor:float=None,Vr:complex=None,)->PowerCircle:""" Construct Receiving End Power Circle. Parameters ---------- Vs: complex Sending End Voltage B: complex Transmission System ABCD Parameters, A D: complex Transmission System ABCD Parameters, B Ps: float, optional Sending End Real Power, default = None Qs: float, optional Sending End Reactive Power, default = None Ss: complex, optional Sending End Apparent Power, default = None power_factor: float, optional System Power Factor, default = None Vr: complex, optional Receiving End Voltage, default = None Returns ------- Sending End Power Circle: PowerCircle """ifnot(VsisnotNoneandBisnotNoneandDisnotNone):raiseValueError("Not enough attributes to build Sending end power circle at least ""provide `Vs`, `B`, `D`")ifnot(((PsisnotNoneandQsisnotNone)or(SsisnotNoneandpower_factorisnotNone))or((PsisnotNoneandpower_factorisnotNone)or(QsisnotNoneandpower_factorisnotNone))):raiseValueError("Not enough attributes for marking an operating point on Sending ""End Power Circle")returnPowerCircle("sending",**{"Vr":Vr,"B":B,"D":D,"Ps":Ps,"Qs":Qs,"Ss":Ss,"Vs":Vs,"power_factor":power_factor,},)
[docs]classSeriesRLC():r""" Frequency Response for an RLC (Resistive, Inductive, Capacitive) Load. Generate unique information about an RLC circuit. Using this class, you may generate a variety of useful statistics including resonance frequency, bandwidth, lower and upper cuttoff frequencies, and more. Each of the specific parameters are evaluated as follows. **Resonance Frequency:** .. math:: \text{resonance_frequency} = \frac{1}{\sqrt{L * C} \cdot 2 \pi} **Bandwidth:** .. math:: \text{bandwidth} = \frac{R}{L \cdot 2 \pi} **Quality Factor:** .. math:: \text{quality_factor} = 2\pi \frac{\text{freq}}{R} Given the characteristics listed below, and the Python code described in the associated example, the following plot will be generated. * Resistance: 5 ohms * Inductance: 0.4 henreys * Capacitance: 25.3e-6 farads * Frequency: 50 Hz .. image:: /static/series-rlc-r5-l0.4.png * Resistance: 10 ohms * Inductance: 0.5 henreys * Capacitance: 25.3e-6 farads * Frequency: 50 Hz .. image:: /static/series-rlc-r10-l0.5.png Examples -------- >>> from electricpy.visu import SeriesRLC >>> rlc_component = SeriesRLC( ... resistance=5, inductance=0.4, capacitance=25.3e-6, frequency=50 ... ) >>> rlc_component.resonance_frequency 50.029927713857425 >>> rlc_component.bandwidth 1.9894367886486917 >>> plot_1 = rlc_component.graph( ... lower_frequency_cut=0.1, upper_frequency_cut=100, samples=1000 ... ) >>> plot_1.show() >>> plot_2 = SeriesRLC( ... resistance=10, inductance=0.5, capacitance=25.3e-6, frequency=50 ... ).graph( ... lower_frequency_cut=0.1, upper_frequency_cut=100, samples=1000, ... show_legend=True, ... ) >>> plot_2.show() Parameters ---------- resistance: float Resistance (in Ohm) of the circuit. inductance: float Inductance (in Henry) of the circuit. capacitance: float Capacitance (in Hz) of the circuit. frequency: float Frequency (in Hz) at which the output gain should be evaluated. """
[docs]def__init__(self,resistance:float,inductance:float,capacitance:float,frequency:float)->None:"""Form the Frequency Response Analysis System."""self.resistance=resistanceself.inductance=inductanceself.capacitance=capacitanceself.frequency=frequency
@propertydefresonance_frequency(self):"""Resonance Frequency (in Hz) of the Described RLC Circuit."""return1/(_np.sqrt(self.inductance*self.capacitance)*2*_np.pi)@propertydefbandwidth(self):"""Bandwidth of the Described RLC Circuit."""returnself.resistance/(2*_np.pi*self.inductance)@propertydefquality_factor(self):"""Quality Factor of the Described RLC Circuit."""return2*_np.pi*self.frequency/self.resistance@propertydeflower_cutoff_frequency(self):"""Lower Cutoff Frequency (in Hz) of the Described RLC Circuit."""x=(-self.resistance)/(2*self.inductance)resonance_angular_frequency=2*_np.pi*self.resonance_frequencyreturn(x+_np.sqrt(x**2+resonance_angular_frequency**2))/(2*_np.pi)@propertydefupper_cutoff_frequency(self):"""Upper Cutoff Frequency (in Hz) of the Described RLC Circuit."""x=(self.resistance)/(2*self.inductance)resonance_angular_frequency=2*_np.pi*self.resonance_frequencyreturn(x+_np.sqrt(x**2+resonance_angular_frequency**2))/(2*_np.pi)defoutput_gain(self,frequency:float):""" Evaluate Output Gain of Described RLC Circuit at a Particular Frequency. Parameters ---------- frequency: float Frequency (in Hz) at which the output gain should be evaluated. """ang_frq=2*_np.pi*frequencycurrent_impedence=(self.resistance**2+(ang_frq*self.inductance-1/(ang_frq*self.capacitance))**2)return(self.resistance)/(_np.sqrt(current_impedence))deflegend(self):"""Generate a Legend for the Graph."""f1,f2=self.lower_cutoff_frequency,self.upper_cutoff_frequencyf=self.resonance_frequencyreturn["Gain",f"Resonance frequency ({f}Hz)",f"Lower cutoff frequency ({f1}Hz)",f"Upper cutoff frequency ({f2}Hz)",f"Bandwidth ({f2-f1}Hz)",f"Quality factor {self.quality_factor}",]defgraph(self,lower_frequency_cut:float,upper_frequency_cut:float,samples:int=10000,show_legend:bool=False,):""" Generate a Plot to Represent all Data Respective of the RLC Circuit. Parameters ---------- lower_frequency_cut: float Minimum frequency to demonstrate as a boundary of the X-axis of the plot. upper_frequency_cut: float Maximum frequency to demonstrate as a boundary of the X-axis of the plot. samples: float, optional Number of samples over which the plot should be formed. Defaults to 1000. show_legend: bool, optional Control to enable or disable the display of the legend. Defaults to False. """x=_np.linspace(lower_frequency_cut,upper_frequency_cut,samples)y=self.output_gain(x)_plt.title("Frequency response of series RLC circuit")_plt.grid(visible=True)_plt.plot(x,y,label="Gain")_plt.ylabel("Gain")_plt.xlabel("Frequency (Hz)")f1,f2=self.lower_cutoff_frequency,self.upper_cutoff_frequencyf=self.resonance_frequency_plt.scatter([f1,f2],[_np.sqrt(0.5),_np.sqrt(0.5)],marker="*",c="black",label="_nolegend_",)half_power_gain=_np.sqrt(0.5)_plt.plot([f,f],[0,1],ls="-.")_plt.plot([f1,f1],[half_power_gain,0],ls="-.")_plt.plot([f2,f2],[half_power_gain,0],ls="-.")_plt.plot([f1,f2],[half_power_gain,half_power_gain],ls="-.")_plt.plot([0,f],[half_power_gain,half_power_gain],label="_nolegend_",ls="--")_plt.plot([0,f],[1,1],label="_nolegend_",ls="--")_plt.plot([0,0],[half_power_gain,1],label="Quality factor",c="black")_plt.scatter([0],[half_power_gain],label="_nolegend_",c="black",marker="v")_plt.scatter([0],[1],label="_nolegend_",c="black",marker="^")ifshow_legend:_plt.legend(self.legend(),loc='best')return_plt