TODO:
add a link to where in this book semiconductor RLCs are covered
add a link to where tank filters are covered
get a power engineer familiar with housing and 3phase to review this cause I am not one
get the scales on the Delta circuit fixed and figure out the spike
#%%writefile AC_2_Codes.py
#Library import statements
from skidl.pyspice import *
#can you say cheeky
import PySpice as pspice
#becouse it's written by a kiwi you know
import lcapy as kiwi
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import YouTubeVideo, display
import traceback
import warnings
WARNING: KICAD_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched.
#import dc code from parral folder
import sys
sys.path.insert(1, '../DC_1/')
from DC_1_Codes import get_skidl_spice_ref
#notebook specific loading control statements
%matplotlib inline
#tool to log notebook internals
#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information skidl, PySpice,lcapy, sympy, numpy, matplotlib, pandas, scipy
Software | Version |
---|---|
Python | 3.7.6 64bit [GCC 7.3.0] |
IPython | 7.12.0 |
OS | Linux 4.19.104 microsoft standard x86_64 with debian bullseye sid |
skidl | 0.0.31.dev0 |
PySpice | 1.4.3 |
lcapy | 0.75.dev0 |
sympy | 1.6.2 |
numpy | 1.18.1 |
matplotlib | 3.3.0 |
pandas | 1.1.4 |
scipy | 1.4.1 |
Thu Jan 28 00:37:31 2021 MST |
Capacitors and Inductors¶
Now that we’re in AC simulations SPICE no longer ignores capacitors and inductors as opens and shorts respectively. So real quick how do we invoke them in SKiDl. (note where focusing here on basic standard capacitors, not semiconductor RLCs which are going to get there own discussion further along in this chapter) Recalling that to invoke a resistor
#create dummy nets for demo
net_1=Net('N1); net_2=Net('N2')
#create the resistor
r1=R(ref='R1', value=1@u_Ohm)
#connect the resistor
r1[1, 2]+=net_1, net_2
that would yield a SPICE netlist statement of
R1 N1 N2 1Ohm
Where to be honest we could have let SKiDl taken care of the reference and omitted it when the resistor was instantiated. For capacitors and inductors, the procedure is the same
Capacitor:
#create dummy nets for demo
net_1=Net('N1); net_2=Net('N2')
#create the capacitor
c1=C(ref='C1', value=1@u_F)
#connect the capacitor
c1['p', 'n']+=net_1, net_2
that would yield a SPICE netlist statment of
C1 N1 N2 1F
Inductor:
#create dummy nets for demo
net_1=Net('N1); net_2=Net('N2')
#create the inductor
l1=L(ref='L1', value=1@u_H)
#connect the inductor
l1['1', '2']+=net_1, net_2
that would yield a SPICE netlist statement of
L1 N1 N2 1H
So there is no real difference except for a major subtle detail of convention. Notice that the capacitor was connected to the netlist via a polarity port statement c1['p', 'n']+=net_1, net_2
whereas the resistor and inductor were connected with just numbering. While technically SKiDl doesn’t care if we use numbers or named terminals as long as there a one-to-one mapping inside SKiDl (see the appendix adlkfjaldjkfkj and or look at skidl/skidl/libs/pyspice_sklib.py) The reason for the difference is because ~99% of resistors in the world are unpolarized and ~90% of all inductor are also unpolarized. That being said when dealing with coupled inductors (transformers, ect) the “dot” goes to the first terminal, more on this in section adlkfalkjdfkj. Whereas more than 50% of capacitors are polarized. And when you pass DC (or otherwise) in the reviser polarization of a capacitor you can degrade if not let lose the magic black smoke as it were. (insert an image of popped and budged electrolytic caps ) So to prevent that in this book at least
In this section, we are just going to start working with AC circuits in SPICE. And we’re going to start simple, but with something that is very poorly talked about and this is multiphase power circuits. Where only going to be scratching the surface of how to simulate these types of circuits in SPICE. And more often than not a lot of these multiphase circuits are driving motors. So below we have a crud model of a single armature of induction motor from an electronics StackOverflow quarry that will work well enough for these demonstrations. As it turns out getting a macro model of an electrical motor is no small feet.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 single_arm_mod class
#used as a test load for multiphasic power circuits in this section.
class single_arm_mod():
"""
holding class for SKiDl Package and lcapy schematic of a
inductor motor amatuer model from
https://electronics.stackexchange.com/questions/234290/using-a-single-phase-induction-motor-equivalent-circuit-in-ltspice
used as a test load for multiphase power circuits
"""
@package
def SKiDl_pack(P=Net(), N=Net()):
"""
SkiDl package to create a single-phase inductive motor armature
Terminals:
P: Positive terminal
N: Negative terminal
Returns:
returns a model based on Handbook of Electric Power Calculations, Third Edition
By H. Wayne Beaty Section 6: SINGLE-PHASE MOTORS with values sourced
from a stackexchange quarry (see docstring right under class)
"""
#top loop elements
l1=L(value=31.6/314@u_H)
r1=R(value=0.8@u_Ohm)
l2=L(value=4.34/314@u_H)
r2=R(value=2.21/.005@u_Ohm)
#dummy R for dealing with loop issues
#rd=R(value=0@u_Ohm)
#bottom loop elements
l3=L(value=31.6/314@u_H)
r3=R(value=0.8@u_Ohm)
l4=L(value=4.34/314@u_H)
r4=R(value=2.21/.005@u_Ohm)
#conect terminal loop
P & l1['p', 'n'] & l3['p', 'n'] & N
#connect top loop
l1['p'] & r1 & l2 & r2 & l1['n']
#connect bottom loop
l3['p'] & r3 & l4 & r4 & l3['n']
def draw_me():
"""
Lcapy schematic of the SKiDl package in this class
Returns:
returns a model based on Handbook of Electric Power Calculations, Third Edition
By H. Wayne Beaty Section 6: SINGLE-PHASE MOTORS with values sourced
from a stackexchange quarry (see docstring right under class)
"""
schematic=kiwi.Circuit()
#termanl loop
schematic.add('W P 1;right')
schematic.add('L1 1 3; down')
schematic.add('W 3 3_2; down')
schematic.add('L3 3_2 2; down=0.5')
schematic.add('W N 2;right')
#top loop
schematic.add('R1 1 4; right')
schematic.add('L2 4 5; right')
schematic.add('R2 5 3_1; down')
schematic.add('W 3 3_1; right')
#bottom loop
schematic.add('R3 3_2 6; right')
schematic.add('L4 6 7; right')
schematic.add('R4 7 2_1; down')
schematic.add('W 2 2_1; right=2')
schematic.draw()
single_arm_mod.draw_me()
So starting off what is a SKiDl Package vs a SKiDl subcircuit that was introduced in the last chapter. SKiDl subcircuits are accessible collections of elements that we have real ease of manipulation which is why we will use subcircuits to build testbenchs. Whereas SKiDl packages are more static invoke packages more akin to so-called SPICE Macro models. Both of them allow one to create functions(methods) that have ports and have pass-through parameters to their invocation. But after the invoking of a package, the internal elements are not accessible outside of the package but do appear in the final Netlist.
Then the next thing to address is this use of classes. We use classes as a collected binding of interrelated variables (called attributes) and functions (called methods) that work together via the self
keyword in a class. This allows us to keep the things in our code organized along with OOP for inheritance. Here we are just using a class as a storge item to hold our package and is easily invokable in python schematic of the partial circuit that the package generates.
AC Sources¶
So far we have talked about DC source as a separate entry and that is because SKiDl and PySpice treat them as separate entries but SPICE and specifically ngspice treat DC AC and most transient source as a single element and makes the distinction via the arguments. Lets then look at the ngspice statement for an independent source form section 4.1 [typical location in the ngspice manual] of
VXXXXXXX N+ N- <<DC> DC/TRAN VALUE > <AC <ACMAG <ACPHASE >>> <DISTOF1 <F1MAG <F1PHASE >>> <DISTOF2 <F2MAG <F2PHASE >>>
Where broken down VXXXXXXX N+ N-
is the name of the source and it’s two terminals. Next is <<DC> DC/TRAN VALUE >
This is the DC source that we have been invoking with V( dc_value=) in SkiDl and PySpice. This is also where the transient simulation control statement goes, more on that in a second. Then we have <AC <ACMAG <ACPHASE >>>
These are the .ac simulation parameters of a source. For now we will ignore <DISTOF1 <F1MAG <F1PHASE >>> <DISTOF2 <F2MAG <F2PHASE >>>
for chapter lkadjfkj on distortion. But in addition to the above general statement, any independent source may have tagged onto it the transient simulation statement such as Sinusoidal, exponential, pulse, etc. Thus the PySpices and SKiDl authors compounded the DC AC and transient Sinusoidal source to one invocation in SkiDl and PySPice, the SINV & SINI souces. Where then all the other transient sources have their own invocation that will be discuses in the chapter on transient simulations. For this chapter what needs to be known is that linear independent DC voltage source is a subset of independent Sinusoidal sources that not only have the add .ac simulation values but also the transient simulation values. Thus the SINV statement is
skidl_SINV=SINEV(ref='1',
#transit sim statements
offset=5,amplitude=5, frequency=5 , delay=5, damping_factor=5,
#ac sim statements
ac_phase=np.deg2rad(-90), ac_magnitude=5,
#dc outset for DC, AC, Transient simulations
dc_offset=5)
resulting in
V1 N1 N2 DC 5V AC 5V -90.0rad SIN(5V 5V 5Hz 5s 5Hz)
and similarly SINI is
skidl_SINI=SINEI(ref='1',
#transit sim statements
offset=5,amplitude=5, frequency=5 , delay=5, damping_factor=5,
#ac sim statements
ac_phase=np.deg2rad(-90), ac_magnitude=5,
#dc outset for DC, AC, Transient simulations
dc_offset=5)
I1 N1 N2 DC 5A AC 5A -90.0rad SIN(5A 5A 5Hz 5s 5Hz)
[ the argument ac_phase=np.deg2rad(-90)
is experimental and has to be provided in rads but converts to deg internal but is reported in rad, hence why it’s experimental due to the unit u_deg outputting to Celsius ]
In this chapter, we will only focus on the arguments ac_phase
, ac_magnitude
, dc_offset
, and will deal with the transient control argument in the transient simulation chapter. Also, linear dependent sources do not need anything special done to them for .ac simulations or any other simulation for that matter since they merely read in a signal and apply a scaled output of the instantaneous single applied to the control side.
Also note that Voltage sources can not be put into parallel in inductors and the same goes for current sources and capacitors, without inciting a singular matrix error in spice. The remedy is to add a 0ohm series resistor between the source and the field element.
\(V_{max}\), Fourier terms, and RMS¶¶
For a recollection of what RMS values and how they relate to max value see ALL ABOUT ELECTRONICS YT video “RMS (Root Mean Square) Value and Average Value of AC Signals”
YouTubeVideo('qDHsokTcgck', width=500, height=400, )
For now, just understand that .ac simulation returns max value per frequency which is the same as the Fourier values.
Single Phase Cirucit¶
For modeling a single spice circuit we need only have all our sources have that same value of ac_phase. Here we will just use a single source in parral with our inductive motor armature to see how a .ac simulation works.
reset()
#create a sine source with max 120V
vs=SINEV(ac_magnitude=120@u_V)
#branch loop issue, neet dummy resistor to fix singularity
rdummy=R(ref='dummy', value=0@u_Ohm)
vs['p', 'n']+=rdummy[1], gnd
#create and hook up the inductive motor armature
arm1=single_arm_mod.SKiDl_pack(); arm1['P', 'N']+=rdummy[2], gnd
circ=generate_netlist()
print(circ)
.title
V1 N_1 0 DC 0V AC 120V 0.0rad SIN(0V 1V 50Hz 0s 0Hz)
Rdummy N_1 N_2 0Ohm
L1 N_2 N_4 0.10063694267515924H
R2 N_2 N_5 0.8Ohm
L2 N_5 N_6 0.013821656050955413H
R3 N_6 N_4 442.0Ohm
L3 N_4 0 0.10063694267515924H
R4 N_4 N_7 0.8Ohm
L4 N_7 N_8 0.013821656050955413H
R5 N_8 0 442.0Ohm
No errors or warnings found during netlist generation.
#create sim
sim=circ.simulator()
#run an ac sim from 48 to 62 Hz linearly over 100 data points
ac_vals=sim.ac(start_frequency=48@u_Hz, stop_frequency=62@u_Hz, number_of_points=100, variation='lin')
One should have noticed that for the source since we intend to use it purely for .ac simulations no frequency of the source we specified. This is because of how .ac simulations work. First, an operating point simulation is done to linearize any nonlinear sources (we will talk about this at length in later chapters when we start working with nonlinear devices). Next, the starting frequency is set to all sinusoidal sources, where non-sinusoidal sources are ignored via their respective means. Then the circuit’s response to the first applied frequency is recorded. Then the simulation moves on to the next simulation frequency and repeats. The recoded results are sinusoids given in terms of complex representation. Thus what an ac simulation is doing from a signals and systems point of view is sampling the Fourier space response of a linearized version of the circuit under test. Thus the arguments for the ac simulation’s start_frequency
(inclusive and non-inclusive) and stop_frequency
( non-inclusive) set the lower and upper bound of the circuit Fourier space that the .ac simulation is going to look through. Then number_of_points tells the simulation how many points to sample in accordance with the sampling scheme set by variation
. We will discuss the other sampling schemes in the next section but for this section will stick with linear where the starting frequency is inclusive so that frequency samples are the same as np.linspace
The results are returned in the same manner as when using .dc expect we replace the .sweep in the return of .dc with .frequnacy in a .ac simulation as shown below
#store the results in a dataframe
#store the frequency to the index
ac_data_base_df=pd.DataFrame(index=ac_vals.frequency.as_ndarray())
#name the index
ac_data_base_df.index.name='freq[Hz]'
#store the voltage at the positive side of the source
ac_data_base_df['Source_v_[V]']=ac_vals[node(rdummy[2])].as_ndarray()
#store the current from the source
ac_data_base_df['Source_i_[A]']=-ac_vals['V1'].as_ndarray()
ac_data_base_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 119.999863+0.001978j | 0.135522-1.978116j |
48.141415 | 119.999863+0.001972j | 0.135522-1.972313j |
48.282829 | 119.999863+0.001967j | 0.135521-1.966544j |
48.424244 | 119.999863+0.001961j | 0.135521-1.960809j |
48.565655 | 119.999863+0.001955j | 0.135521-1.955107j |
... | ... | ... |
61.434345 | 119.999863+0.001546j | 0.135501-1.546182j |
61.575756 | 119.999863+0.001543j | 0.135501-1.542638j |
61.717171 | 119.999863+0.001539j | 0.135501-1.539111j |
61.858585 | 119.999863+0.001536j | 0.135501-1.535600j |
62.000000 | 119.999863+0.001532j | 0.135501-1.532105j |
100 rows × 2 columns
As we see these are complex values representing the frequency response of 120V sinusoid with frequency controlled by the ac simulation. And thus, we naively plot them we will have issues with matplotlib not knowing how to interpret complex values thusly.
#quick plot of the voltage and current vs the frequency
#since the values are Fourier terms there going to be complex
ac_data_base_df.plot(subplots=True, sharex=True, grid=True);
/home/iridium/anaconda3/lib/python3.7/site-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
/home/iridium/anaconda3/lib/python3.7/site-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
Thus, to analyze these Fourier components we need to decode the information contained in the complex sinusoid representation. We start by looking at the magnitude representation achieved by applying np.abs
below
#make a magnitude version of the data and plot it
ac_data_mag_df=ac_data_base_df.apply(np.abs, axis=0)
ac_data_mag_df.plot(subplots=True, sharex=True, grid=True);
ac_data_mag_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 119.999863 | 1.982753 |
48.141415 | 119.999863 | 1.976964 |
48.282829 | 119.999863 | 1.971208 |
48.424244 | 119.999863 | 1.965486 |
48.565655 | 119.999863 | 1.959798 |
... | ... | ... |
61.434345 | 119.999863 | 1.552108 |
61.575756 | 119.999863 | 1.548578 |
61.717171 | 119.999863 | 1.545064 |
61.858585 | 119.999863 | 1.541567 |
62.000000 | 119.999863 | 1.538085 |
100 rows × 2 columns
Thus, showing that the linear dependent voltage source is indeed holding the proper voltage of 120V regardless of the frequency but that the current being drawn from the source is a function of the operating frequency. And for this section, the simulation frequency is chosen to represent a sweep through \(\pm2\)Hz of the two major power gid operating frequency of 50 and 60 Hz.
Next, we look at the phase information in terms of radians, where radians are seldom used in power analysis. We do this via np.angle
#look at the phase in terms of rads; not typically done in power
ac_data_degrad_df=ac_data_base_df.apply(np.angle, axis=0)
ac_data_degrad_df.plot(subplots=True, sharex=True, grid=True);
ac_data_degrad_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000016 | -1.502393 |
48.141415 | 0.000016 | -1.502192 |
48.282829 | 0.000016 | -1.501992 |
48.424244 | 0.000016 | -1.501791 |
48.565655 | 0.000016 | -1.501591 |
... | ... | ... |
61.434345 | 0.000013 | -1.483383 |
61.575756 | 0.000013 | -1.483184 |
61.717171 | 0.000013 | -1.482984 |
61.858585 | 0.000013 | -1.482785 |
62.000000 | 0.000013 | -1.482585 |
100 rows × 2 columns
We can look at the same thing in terms of degrees by adding the argument deg=True
to np.angle
#look at the phase in degrees
ac_data_degang_df=ac_data_base_df.apply(np.angle, axis=0, deg=True)
ac_data_degang_df.plot(subplots=True, sharex=True, grid=True);
ac_data_degang_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000944 | -86.080757 |
48.141415 | 0.000942 | -86.069267 |
48.282829 | 0.000939 | -86.057793 |
48.424244 | 0.000936 | -86.046303 |
48.565655 | 0.000933 | -86.034813 |
... | ... | ... |
61.434345 | 0.000738 | -84.991608 |
61.575756 | 0.000737 | -84.980179 |
61.717171 | 0.000735 | -84.968735 |
61.858585 | 0.000733 | -84.957298 |
62.000000 | 0.000732 | -84.945862 |
100 rows × 2 columns
This shows that there is some slight phase in the voltage with respect to the ground. But this is mostly numerical issues. What is mostly happening is that the current is lagging the voltage at the ground from 86 to 85 degrees. This is obvious given that the load is fully resistive inductive.
There is one more representation of the phase and that is phase unwrapped. This most for other simulations in this chapter but we will discuss it here. When using np.deg in degrees it will cycle back on itself every 360 degrees. We don’t get this in the radian interpretation. Thus, we have also have phase degree unwrapped which accumulates the angle phase to that its value can go beyond 360 degrees. Here it doesn’t do us much good but in the next sections, this view will become extremely important.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 angle_phase_unwrap lambda function
#lambda function to find the unwrapped phase from complex values
angle_phase_unwrap= lambda x: np.rad2deg(np.unwrap(np.angle(x)))
#look at the phase in degrees unwrapped, since the phase here did not go
#beyond 2pi doesn't do anything
ac_data_deganguw_df=ac_data_base_df.apply(angle_phase_unwrap, axis=0)
ac_data_deganguw_df.plot(subplots=True, sharex=True, grid=True);
ac_data_deganguw_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000944 | -86.080758 |
48.141415 | 0.000942 | -86.069270 |
48.282829 | 0.000939 | -86.057788 |
48.424244 | 0.000936 | -86.046300 |
48.565655 | 0.000933 | -86.034811 |
... | ... | ... |
61.434345 | 0.000738 | -84.991609 |
61.575756 | 0.000737 | -84.980175 |
61.717171 | 0.000735 | -84.968735 |
61.858585 | 0.000733 | -84.957294 |
62.000000 | 0.000732 | -84.945861 |
100 rows × 2 columns
Power Measurments¶¶
Since with SPICE we have access to current and voltage and with Python we have a better claualtor then pretty much any SPICE utlity ever lets make some tooling to find all the typical power terms:
the instaniues power: \(p[W]=v \cdot i [V \cdot A]\)
the averge or avtive Power: \(P[W]=\dfrac{\Re( v \cdot i^*)}{2} [V \cdot A]\)
the reactive power: \(Q[VAR]=\dfrac{\Im( v \cdot i^*)}{2} [V \cdot A]\)
the complex power: \(S[VAR]=\dfrac{ v \cdot i^*}{2} [V \cdot A]\)
the apparent power: \(s[VAR]=\left| \dfrac{ v \cdot i^*}{2} \right| [V \cdot A]\)
the power factor: \(pf[]=\dfrac{P}{s} [W/VA]\)
the power factor angle: \(\phi_{pf}[deg/rad]=\arccos\left(\dfrac{P}{s}\right) [W/VA]\)
the parrel compentions capcitor value per frequancy: \(C[F]=\frac{2 P \cdot \left( \tan{\left(\theta_{i} \right)} - \tan{\left(\theta_{f} \right)}\right)}{\omega |V|^{2}}\)
the parrel compentions inductor value per frequancy: \(L[H]= \frac{|V|^{2}}{2 P \cdot \omega \left( \tan{\left(\theta_{i} \right)} - \tan{\left(\theta_{f} \right)}\right)} \)
For a refresher on most of these terms ALL ABOUT ELELECTINCS YT video “What is Power Factor? What is Leading & Lagging Power factor ? Power Factor Correction Methods”
YouTubeVideo('iDYWfBGwT1w', width=500, height=400, )
Complex power and an E&M side note¶¶
The reactive power Q is often treated as a fictitious power term; It’s not. Poynting’s theorem, that theorem that defines photon power in classical E&M is $\(-\oint_{S} (E \times H) \cdot dS=\iiint_{V} \left[ \dfrac{\mu}{2}\dfrac{\partial|H|^2}{\partial t} +\dfrac{\epsilon}{2}\dfrac{\partial|E|^2}{\partial t} + E\cdot J \right]dV \)$ Where recalling that Poynting’s theorem is also about power flux for E&M filds we can break it down to
\(-\oint_{S} (E \times H) \cdot dS\) = the complex power \(S\), aka the full elemtromatic power inclosed in a volume
\(\iiint_{V} E\cdot J dV\) = the average or active power \(P\) this is the dissipative power aka the ohmic power
\(\iiint_{V} \left[ \dfrac{\mu}{2}\dfrac{\partial|H|^2}{\partial t} +\dfrac{\epsilon}{2}\dfrac{\partial|E|^2}{\partial t} \right] dV\) is the reactive poewr \(Q\), or the power stored in the E & M fields. Wich are physical empoided by captiors and inductors in our circuits.
One of the consequences of this is that the power factor tells us is whether the E field or the M field is containing the majority of the reactive power.
With respect to a periodic steady-state signal like a sinusoid, it is true that the time average of the reactive power is zero. But as All ABOUT ELELCTROINCS showed what is happening during the period is that the power in the field is being pulled from the source during the first half of the period and then returned to the source in the second half. In other words, the reactive power is sloshing back and forth. This is not a good thing, especially at the source. So what we do is add a compensating reciprocal element to bring the power factor to close to zero. Wich is the same as saying that an LC oscillator is created so that the reactive power sloshing about is in a local tank (parrel LC elements are also called tank circuits) instead of with the whole circuit. Also if anyone says reactive power is fictitious ask why we then need flyback diodes on inductors. Cause I can ensure you a driving sinusoid is removed from the circuit the reactive power stored in the inductor will very quickly become active power in the circuit if not compensated for.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 power_calcs_ac class
#class to store power calculations and aid functions as staticmethods
class power_calcs_ac():
"""
Because I am too lazy to create one that works with time
based data as well this only works with the .ac Fourier like data
"""
@staticmethod
def instantaneous_power_calc(voltage, current):
"""
Staticmethod to compute the instantaneous power:
$$p[W]=v \cdot i [V \cdot A]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called instantaneous power in Watts
"""
return voltage*current
@staticmethod
def averge_power_calc(voltage, current):
"""
Staticmethod to compute the so-called average power:
$$P[W]=\dfrac{\Re( v \cdot i^*)}{2} [V \cdot A]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called average power in Watts
"""
Power=voltage*np.conj(current)
Power=np.real(Power)/2
return Power
@staticmethod
def reactive_power_calc(voltage, current):
"""
Staticmethod to compute the so-called reactive power:
$$Q[VAR]=\dfrac{\Im( v \cdot i^*)}{2} [V \cdot A]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called reactive power in Volt-Ampere Reactive [VAR]
"""
Qpower=voltage*np.conj(current)
Qpower=np.imag(Qpower)/2
return Qpower
@staticmethod
def complex_power_calc(voltage, current):
"""
Staticmethod to compute the so-called complex power:
$$S[VA]=\dfrac{ v \cdot i^*}{2} [V \cdot A]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whos length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called apparent power in Volt-Ampere [VA]
"""
Spower=voltage*np.conj(current)
Spower=Spower/2
return Spower
@staticmethod
def apparent_power_calc(voltage, current):
"""
Staticmethod to compute the so-called apparent power:
$$S[VA]=\left| \dfrac{ v \cdot i^*}{2} \right| [V \cdot A]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called complex power in Volt-Ampere [VA]
"""
Spower=power_calcs_ac.complex_power_calc(voltage, current)
spower=np.abs(Spower)
return spower
@staticmethod
def power_factor_calc(voltage, current):
"""
Staticmethod to compute the so-called power factor via:
$$pf[]=\dfrac{P}{s} [W/VA]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whos length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
Returns:
returns an array with the so-called power factor that is unitless
"""
Ppower=power_calcs_ac.averge_power_calc(voltage, current)
spower=power_calcs_ac.apparent_power_calc(voltage, current)
pf=Ppower/spower
return pf
@staticmethod
def power_factor_angle_calc(voltage, current, deg=True):
"""
Staticmethod to compute the so-called power factor angle via:
$$\phi_{pf}[deg/rad]=\arccos\left(\dfrac{P}{s}\right) [W/VA]$$
Args:
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
deg (bool; True): When True returns the power factor angle in degrees, else in radians
Returns:
returns an array with the so-called power factor angle in degrees when `deg` is true, else in radians
"""
pf=power_calcs_ac.power_factor_calc(voltage, current)
pf_ang=np.arccos(pf)
if deg:
pf_ang=np.rad2deg(pf_ang)
return pf_ang
@staticmethod
def Cparallel_comp(new_pf_ang, voltage, current, frequancy):
"""
Statcmethod to calculate the parallel compensating capacitor for inductive loads
based on the following equation
$$ C[F]=\frac{2 P \cdot \left( \tan{\left(\theta_{i} \right)} - \tan{\left(\theta_{f} \right)}\right)}{\omega |V|^{2}} $$
Args:
new_pf_ang (float; deg):new power factor angle goal
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whose length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
frequency (np.float): the frequencies matching the current and voltage arrays
Returns:
returns an array with the needed compensation to reach the target power factor in Farads
"""
Power=power_calcs_ac.averge_power_calc(voltage, current)
int_pf_ang=power_calcs_ac.power_factor_angle_calc(voltage, current)
deltaQ=Power*(np.tan(np.deg2rad(int_pf_ang))-np.tan(np.deg2rad(new_pf_ang)))
Vmag=np.abs(voltage)
angfreq=2*np.pi*frequancy
C=(2*deltaQ)/(angfreq*Vmag**2)
return C
@staticmethod
def Lparallel_comp(new_pf_ang, voltage, current, frequancy):
"""
Statcmethod to calculate the parallel compensating inductor for capacitive loads
based on the following equation
$$L[H]= \frac{|V|^{2}}{2 P \cdot \omega \left( \tan{\left(\theta_{i} \right)} - \tan{\left(\theta_{f} \right)}\right)} $$
Args:
new_pf_ang (float; deg):new power factor angle goal
voltage (np.float or np.complex array): the voltage in terms of
Fourier terms whos length must match the `current` argument
current (np.float or np.complex array): the current in terms of
Fourier terms whose length must match the `voltage` argument
frequency (np.float): the frequencies matching the current and voltage arrays
Returns:
returns an array with the needed compensation to reach the target power factor in Henrys
"""
Power=power_calcs_ac.averge_power_calc(voltage, current)
int_pf_ang=power_calcs_ac.power_factor_angle_calc(voltage, current)
deltaQ=Power*(np.tan(np.deg2rad(int_pf_ang))-np.tan(np.deg2rad(new_pf_ang)))
Vmag=np.abs(voltage)
angfreq=2*np.pi*frequancy
L=(Vmag**2)/(2*angfreq*deltaQ)
return L
@staticmethod
def autogen_powercalcs(source_dataframe, base_term):
"""
Static method to automatically calculate and add the dataframe the
instantaneous_power_calc, averge_power_calc, reactive_power_calc, complex_power_calc, apparent_power_calc,
power_factor_calc power_factor_angle_calc methods in this class
Args:
source_dataframe (pandas.dataframe): pandas dataframe containing the AC frequencies
in the index, columns in the forms of `<base_term>_v_[V]` and `<base_term>_i_[A]`
base_term (string): the based string as part of the column names of
`<base_term>_v_[V]` and `<base_term>_i_[A]`
that are the source columns that will be sourced to calculate the other measurements
Returns:
adds the following:
source_dataframe[f'{base_term}_p_[W]'], source_dataframe[f'{base_term}_P_[W]'], source_dataframe[f'{base_term}_Q_[VAR]']
source_dataframe[f'{base_term}_S_[VA]'] ,source_dataframe[f'{base_term}_s_[VA]'], source_dataframe[f'{base_term}_pf_[]'],
source_dataframe[f'{base_term}_pfang_[deg]']
"""
voltage_col=source_dataframe[f'{base_term}_v_[V]']; current_col=source_dataframe[f'{base_term}_i_[A]']
source_dataframe[f'{base_term}_p_[W]']=power_calcs_ac.instantaneous_power_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_P_[W]']=power_calcs_ac.averge_power_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_Q_[VAR]']=power_calcs_ac.reactive_power_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_S_[VA]']=power_calcs_ac.complex_power_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_s_[VA]']=power_calcs_ac.apparent_power_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_pf_[]']=power_calcs_ac.power_factor_calc(voltage_col, current_col)
source_dataframe[f'{base_term}_pfang_[deg]']=power_calcs_ac.power_factor_angle_calc(voltage_col, current_col)
@staticmethod
def make_power_plot(source_dataframe, base_term, frequncy_scale='linear'):
"""
Staticmethod to assist in the rapid plot creation of the three common
AC powers and the power factor angle in a single 2x2 subplot
Args:
source_dataframe (pandas.dataframe): pandas dataframe containing the AC frequencies
in the index, columns in the forms of
`<base_term>_P_[W]`, `<base_term>_Q_[VAR]`, `<base_term>_s_[VA]`, `<base_term>_pfang_[deg]`
base_term (string): the based string as part of the column names of
`<base_term>_P_[W]`, `<base_term>_Q_[VAR]`, `<base_term>_s_[VA]`, `<base_term>_pfang_[deg]`
that are the source columns that create the plot
frequncy_scale (string; 'linear'): the frequency scale that that data was collected against, must
be `linear` for a linear ac sweep or `decade` for a logarithm decade collection that will make the
plot be semilogx
Returns:
creates a 2x2 plot fo the power terms
TODO:
-add octave scale
-make plot generation cleaner between the different frequency scales
-make the pf_deg work for rad as well
-make it work with dataframes missing power terms, have it make
them on the fly
"""
assert frequncy_scale in ['linear', 'decade'], 'frequncy_scale must be linear or decade'
fig, [[Pplot, Qplot], [splot, pfangplot]]=plt.subplots(nrows=2, ncols=2, sharex=True)
plots=[Pplot, Qplot, splot, pfangplot]
x=source_dataframe.index
x_label=source_dataframe.index.name
if frequncy_scale=='linear':
if f'{base_term}_P_[W]' in source_dataframe.columns:
Pplot.plot(x, source_dataframe[f'{base_term}_P_[W]'])
if f'{base_term}_Q_[VAR]' in source_dataframe.columns:
Qplot.plot(x, source_dataframe[f'{base_term}_Q_[VAR]'])
if f'{base_term}_s_[VA]' in source_dataframe.columns:
splot.plot(x, source_dataframe[f'{base_term}_s_[VA]'])
if f'{base_term}_pfang_[deg]' in source_dataframe.columns:
pfangplot.plot(x, source_dataframe[f'{base_term}_pfang_[deg]'])
elif frequncy_scale in ['decade']:
if f'{base_term}_P_[W]' in source_dataframe.columns:
Pplot.semilogx(x, source_dataframe[f'{base_term}_P_[W]'])
if f'{base_term}_Q_[VAR]' in source_dataframe.columns:
Qplot.semilogx(x, source_dataframe[f'{base_term}_Q_[VAR]'])
if f'{base_term}_s_[VA]' in source_dataframe.columns:
splot.semilogx(x, source_dataframe[f'{base_term}_s_[VA]'])
if f'{base_term}_pfang_[deg]' in source_dataframe.columns:
pfangplot.semilogx(x, source_dataframe[f'{base_term}_pfang_[deg]'])
#set the ylabels
Pplot.set_ylabel('P_[W]')
Qplot.set_ylabel('Q_[VAR]')
splot.set_ylabel('s_[VA]')
pfangplot.set_ylabel('pf_ang_[deg]')
#set the xlabel, ticklabel controls, grid for all the subplots
for plot in plots:
plot.set_xlabel(x_label)
plot.ticklabel_format(useOffset=False, axis='y')
plot.grid()
fig.suptitle(f'"{base_term}" power plots')
plt.tight_layout()
#make a seperate dataframe to stowe power values in
ac_data_power_df=ac_data_base_df.copy()
ac_data_power_df.columns
Index(['Source_v_[V]', 'Source_i_[A]'], dtype='object')
With all the power calculation that are typically needed stowed away with handy utility methods to automate using them and plotting results lets put them to work
#make the power calculations
power_calcs_ac.autogen_powercalcs(ac_data_power_df, 'Source')
ac_data_power_df.columns
ac_data_power_df.head()
Source_v_[V] | Source_i_[A] | Source_p_[W] | Source_P_[W] | Source_Q_[VAR] | Source_S_[VA] | Source_s_[VA] | Source_pf_[] | Source_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||
48.000000 | 119.999863+0.001978j | 0.135522-1.978116j | 16.266512-237.373428j | 8.129342 | 118.686989 | 8.129342+118.686989j | 118.965065 | 0.068334 | 86.081703 |
48.141415 | 119.999863+0.001972j | 0.135522-1.972313j | 16.266457-236.677048j | 8.129339 | 118.338799 | 8.129339+118.338799j | 118.617691 | 0.068534 | 86.070206 |
48.282829 | 119.999863+0.001967j | 0.135521-1.966544j | 16.266403-235.984772j | 8.129333 | 117.992645 | 8.129333+117.992645j | 118.272354 | 0.068734 | 86.058716 |
48.424244 | 119.999863+0.001961j | 0.135521-1.960809j | 16.266350-235.296524j | 8.129330 | 117.648521 | 8.129330+117.648521j | 117.929047 | 0.068934 | 86.047234 |
48.565655 | 119.999863+0.001955j | 0.135521-1.955107j | 16.266294-234.612274j | 8.129325 | 117.306396 | 8.129325+117.306396j | 117.587738 | 0.069134 | 86.035744 |
#make the plot of the power and power factor angle
power_calcs_ac.make_power_plot(ac_data_power_df, 'Source')
As can be seen, our power factor is clearly inductive and motor armature has a huge reactive power. Meaning that it is sourcing and returning a lot of energy in the inductor armature inductors and source.
Single Phase Circuit with Capacitive Compensation¶¶
As we saw the super inductiveness of this circuit means that armature is drawing sloshing a lot of energy to and from the circuit. We compensate for that by adding a parallel capacitor to the amateur. This capacitor buffers the sloshing from the source once the circuit has settled down into a steady state. So that the inductors are building up their magnetic field they are sourcing that energy from the capacitor, and similarly when the inductor’s magnetic fields are collapsing and sourcing power to the shunt capacitor. This sloshing is known as resonance and will be looked at more closely in the next section and the next chapter.
One of the calculation tools that was added to power_calcs_ac
was Cparallel_comp
that calculated the needed parallel capacitance given a new target power factor angle. For the most part, the ideal power factor is zero.
#find the compensation capacitance for each freq in the sweep
ac_data_power_df['Ccomp_[F]']=power_calcs_ac.Cparallel_comp(-10, ac_data_power_df['Source_v_[V]'], ac_data_power_df['Source_i_[A]'], ac_data_power_df.index)
#get the mean value of the capacitor and where that mean frequency is
#dont do this in real life you will see why
ccomp_value=ac_data_power_df['Ccomp_[F]'].mean()
mean_freq=ac_data_power_df.index.values.mean()
print(f'mean cap at mean freq:{ccomp_value:2e}[F]@{mean_freq}Hz')
#make a plot of the compensation capacitance and the found mean
fig, ax = plt.subplots()
ac_data_power_df.plot(y='Ccomp_[F]', ax=ax)
ax.set_ylabel('Capatiance_[F]')
ax.plot(mean_freq, ccomp_value, '*r', label='mean_C')
ax.legend()
plt.title('PF->0deg compenstaion capactor vs Freqauncy');
mean cap at mean freq:4.291683e-05[F]@55.0Hz
What we see is that the capacitance need is a function of the frequency and has a fairly large range just within the span of 24Hz. Since we are staying basic here let’s just pick the average capacitance as the shunt compensation capacitance value to use and repeat the simulation and analysis to see what happens.
#resimulat the circuit but now with the shunt compensation capacitance
reset()
vs=SINEV(ac_magnitude=120@u_V)
#branch loop issue, neet dummy resistor to fix singularly
rdummy=R(ref='dummy', value=0@u_Ohm)
vs['p', 'n']+=rdummy[1], gnd
#compansating capacitor
ccomp=C(ref='comp', value=ccomp_value@u_F); ccomp['p', 'n']+=rdummy[2], gnd
arm1=single_arm_mod.SKiDl_pack(); arm1['P', 'N']+=ccomp['p'], gnd
circ=generate_netlist()
print(circ)
.title
V1 N_1 0 DC 0V AC 120V 0.0rad SIN(0V 1V 50Hz 0s 0Hz)
Rdummy N_1 N_3 0Ohm
Ccomp N_3 0 4.2916828429243215e-05
L1 N_3 N_5 0.10063694267515924H
R2 N_3 N_6 0.8Ohm
L2 N_6 N_7 0.013821656050955413H
R3 N_7 N_5 442.0Ohm
L3 N_5 0 0.10063694267515924H
R4 N_5 N_8 0.8Ohm
L4 N_8 N_9 0.013821656050955413H
R5 N_9 0 442.0Ohm
No errors or warnings found during netlist generation.
#repreform the sim
sim=circ.simulator()
ac_vals=sim.ac(start_frequency=48@u_Hz, stop_frequency=62@u_Hz, number_of_points=100, variation='lin')
#recoloect the VI data
ac_data_base_df=pd.DataFrame(index=ac_vals.frequency.as_ndarray())
ac_data_base_df.index.name='freq[Hz]'
ac_data_base_df['Source_v_[V]']=ac_vals[node(rdummy[2])].as_ndarray()
ac_data_base_df['Source_i_[A]']=-ac_vals['V1'].as_ndarray()
#ac_data_base_df['Source_p_[W]']=ac_data_base_df['Source_v_[V]']*ac_data_base_df['Source_i_[A]']
ac_data_base_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 119.999863+0.000425j | 0.135491-0.424911j |
48.141415 | 119.999863+0.000415j | 0.135491-0.414532j |
48.282829 | 119.999863+0.000404j | 0.135490-0.404186j |
48.424244 | 119.999863+0.000394j | 0.135490-0.393875j |
48.565655 | 119.999863+0.000384j | 0.135490-0.383597j |
... | ... | ... |
61.434345 | 119.999863-0.000442j | 0.135483+0.441739j |
61.575756 | 119.999863-0.000450j | 0.135483+0.449858j |
61.717171 | 119.999863-0.000458j | 0.135483+0.457961j |
61.858585 | 119.999863-0.000466j | 0.135483+0.466048j |
62.000000 | 119.999863-0.000474j | 0.135483+0.474119j |
100 rows × 2 columns
#look at the raw data
ac_data_base_df.plot(subplots=True, sharex=True, grid=True);
/home/iridium/anaconda3/lib/python3.7/site-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
/home/iridium/anaconda3/lib/python3.7/site-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
#look at the magnitude
ac_data_mag_df=ac_data_base_df.apply(np.abs, axis=0)
ac_data_mag_df.plot(subplots=True, sharex=True, grid=True);
ac_data_mag_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 119.999863 | 0.445990 |
48.141415 | 119.999863 | 0.436113 |
48.282829 | 119.999863 | 0.426291 |
48.424244 | 119.999863 | 0.416528 |
48.565655 | 119.999863 | 0.406822 |
... | ... | ... |
61.434345 | 119.999863 | 0.462049 |
61.575756 | 119.999863 | 0.469817 |
61.717171 | 119.999863 | 0.477582 |
61.858585 | 119.999863 | 0.485342 |
62.000000 | 119.999863 | 0.493097 |
100 rows × 2 columns
#look at the phase in radians
ac_data_degrad_df=ac_data_base_df.apply(np.angle, axis=0)
ac_data_degrad_df.plot(subplots=True, sharex=True, grid=True);
ac_data_degrad_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000004 | -1.262120 |
48.141415 | 0.000003 | -1.254890 |
48.282829 | 0.000003 | -1.247351 |
48.424244 | 0.000003 | -1.239483 |
48.565655 | 0.000003 | -1.231265 |
... | ... | ... |
61.434345 | -0.000004 | 1.273200 |
61.575756 | -0.000004 | 1.278268 |
61.717171 | -0.000004 | 1.283161 |
61.858585 | -0.000004 | 1.287888 |
62.000000 | -0.000004 | 1.292457 |
100 rows × 2 columns
#look at the phase in degrees
ac_data_degang_df=ac_data_base_df.apply(np.angle, axis=0, deg=True)
ac_data_degang_df.plot(subplots=True, sharex=True, grid=True);
ac_data_degang_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000203 | -72.314148 |
48.141415 | 0.000198 | -71.899910 |
48.282829 | 0.000193 | -71.467941 |
48.424244 | 0.000188 | -71.017143 |
48.565655 | 0.000183 | -70.546303 |
... | ... | ... |
61.434345 | -0.000211 | 72.949005 |
61.575756 | -0.000215 | 73.239349 |
61.717171 | -0.000219 | 73.519691 |
61.858585 | -0.000223 | 73.790527 |
62.000000 | -0.000226 | 74.052322 |
100 rows × 2 columns
##look at the unwraped-phase
ac_data_deganguw_df=ac_data_base_df.apply(angle_phase_unwrap, axis=0)
ac_data_deganguw_df.plot(subplots=True, sharex=True, grid=True);
ac_data_deganguw_df
Source_v_[V] | Source_i_[A] | |
---|---|---|
freq[Hz] | ||
48.000000 | 0.000203 | -72.314150 |
48.141415 | 0.000198 | -71.899906 |
48.282829 | 0.000193 | -71.467944 |
48.424244 | 0.000188 | -71.017145 |
48.565655 | 0.000183 | -70.546299 |
... | ... | ... |
61.434345 | -0.000211 | 72.949002 |
61.575756 | -0.000215 | 73.239347 |
61.717171 | -0.000219 | 73.519692 |
61.858585 | -0.000223 | 73.790529 |
62.000000 | -0.000226 | 74.052324 |
100 rows × 2 columns
#perform all the power calcs
ac_data_power_df=ac_data_base_df.copy()
power_calcs_ac.autogen_powercalcs(ac_data_power_df, 'Source')
ac_data_power_df.columns
ac_data_power_df.head()
Source_v_[V] | Source_i_[A] | Source_p_[W] | Source_P_[W] | Source_Q_[VAR] | Source_S_[VA] | Source_s_[VA] | Source_pf_[] | Source_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||
48.000000 | 119.999863+0.000425j | 0.135491-0.424911j | 16.259047-50.989166j | 8.129342 | 25.494640 | 8.129342+25.494640j | 26.759352 | 0.303794 | 72.314354 |
48.141415 | 119.999863+0.000415j | 0.135491-0.414532j | 16.259020-49.743675j | 8.129338 | 24.871895 | 8.129338+24.871895j | 26.166721 | 0.310675 | 71.900101 |
48.282829 | 119.999863+0.000404j | 0.135490-0.404186j | 16.258995-48.502270j | 8.129333 | 24.251188 | 8.129333+24.251188j | 25.577456 | 0.317832 | 71.468132 |
48.424244 | 119.999863+0.000394j | 0.135490-0.393875j | 16.258968-47.264908j | 8.129330 | 23.632507 | 8.129330+23.632507j | 24.991627 | 0.325282 | 71.017326 |
48.565655 | 119.999863+0.000384j | 0.135490-0.383597j | 16.258944-46.031555j | 8.129325 | 23.015831 | 8.129325+23.015831j | 24.409309 | 0.333042 | 70.546478 |
#plot of the powers with a compenstating capcitor
power_calcs_ac.make_power_plot(ac_data_power_df, 'Source')
We see that while we have overall lowered the Reactive power from as high as 120VAR to around \(\pm20\)[VAR]. We have only succeeded in getting the power factor to near zero at the mean frequency of 55Hz where our mean capacitance was 4.29e-05F. Just within a sweep of 24Hz encompassing 50/60Hz we see that single frequency matching is not effective. If this was a digital single with today’s data rates and the frequency range that ideal squarewave encompass this would have been even less effective. Just with power alone this single example where naively using the mean shows that except in the tightest frequency banded systems single-frequency matching doesn’t work and instead broadband matching is needed. But we will get to those circuits much much later in this book.
Two-Phase System¶¶
The North American two-phase power system in the residential panel is one of the strangest things in electrical engineering that has ever been conceived. If you’re not familiar with it I recommend watching the YT video “The US electrical system is not 120V” from Technology Connections. Also if you’re not familiar with Ground Fault Interpreter (gfi) I recommend watching his YT video on that The GFCI/RCD: A Simple but Life-Saving Protector
YouTubeVideo('jMmUoZh3Hq4', width=500, height=400)
YouTubeVideo('ILBjnZq0n8s', width=500, height=400)
For a shorter video about neutral and ground, I recommend the YT video The difference between neutral and ground on the electric panel from grayfurnaceman.
YouTubeVideo('-n8CiU_6KqE', width=500, height=400)
Suffice to say that the neutral branch is the nominal current return path. Where inside the residence at the primary panel earth ground is tied to neutral. Where then all subsequent grounds normally are isolated chaise ground branches. These isolated branches also lead to earth ground but are typically a current return in place of the neutral in a fault situation typical when there is conduction to the chassis. Where this typically happens in an electrical shock instance hence the GFI.
Below we create a rudimentary model of such a North American 2 phase panel. Where we start only at the panel, not the transformer. And in the model, we have the neutral resistively separated from the ground. Whereat the source side this resistance Rearth
is default set to zero. It can be changed to loosely model the earth ground bonding issue that can occur where then the neutral floats. On the other side of the neutral branch, there is a separation resistor Rgfi
that is set by default to be open to represent an open chassis ground return path. But can be changed to loosely mimic a ground fault condition. We will play with this in the chapter on transient circuits when we replace that resistor with a switch. So for now we will leave it open. Finally, the neutral and each of the phases have a wire model resistor (RaA
, RbB
, RnN
) These are by default set to O Oms; however, they can be changed to facilitate three things. The source built-in resistance, the interconnection resistance, and or an open fault in the connects. Again, we will not explore those situations here but they are baked into this testbench.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 two_phase_panel class
#class to create a SPICE model of a North American two-phase power panel
#with ground branch and gfi branch in addition to neutral branch
class two_phase_panel:
"""
Class to contain the needed Skidl package (method) to invoke the circuit for simulations
and drawing just the packaged circuit via lcapy.
The circuit in hand is a model of a typical North American 2Phase home panel with resistor separated
neutral from the ground along with open separation to a parallel gfi path for chassis ground to the earth ground
"""
@subcircuit
def SKiDl_circ(self, Load_A_posterm, Load_A_neuterm,
Load_B_posterm, Load_B_neuterm,
RaA=0@u_Ohm, RbB=0@u_Ohm, RnN=0@u_Ohm,
Rearth=0@u_Ohm, Rgfi=1e16@u_Ohm):
"""
SKiDl subcircuit of a primitive representation of a US 120 2 phase circuit
panel with resistor separated ground from neutral and open from ground return
Terminals:
Load_A_posterm: Positive terminal for the A phase load
Load_A_neuterm: Neutral terminal for the A phase load
Load_B_posterm: Positive terminal for the B phase load
Load_B_neuterm: Neutral terminal for the B phase load
Args:
RaA (float; 0@u_Ohm; ohms): Resistance between the A source and A terminal
RbB (float; 0@u_Ohm; ohms): Resistance between the B source and B terminal
RnN (float; 0@u_Ohm; ohms): Resistance between the N source and N terminal
Rearth (float; 0@u_Ohm; ohms): Resistance between the earth and neutral
Rgfi (float; 1e16@u_Ohm; ohms): Resistance between neutral and chassis ground gfi branch
Returns:
returns a SKiDl subcircuit. Some of the internal elements SkiDl objects
are also stored in `self.panel_internals` which is a dictionary with
names for the objects as keys and the SkiDl object's elements as corresponding values
"""
#ngspice treats net a == net A; so add underbar to save from singular matrix
net_a=Net('a'); net_A=Net('A_')
net_b=Net('b'); net_B=Net('B_')
net_n=Net('n'); net_N=Net('N_')
#create wire models to deal with voltage source parallel
#to inductor singularities
phase_a_wire=R(ref='aA', value=RaA); phase_a_wire[1, 2]+=net_a, net_A
phase_b_wire=R(ref='bB', value=RbB); phase_b_wire[1, 2]+=net_b, net_B
phase_n_wire=R(ref='nN', value=RnN); phase_n_wire[1, 2]+=net_n, net_N
#bus neutral to earth
#create wire (resistor) to isolate neutral and earth ground
#can be used to model floating neutral
earth_n_sep=R(ref='gndbus', value=Rearth)
#floating neutral monitor
earth_ammeter=V(ref='amm_earth', dc_value=0@u_V)
earth_n_sep[1, 2]+=net_n, earth_ammeter['p']
earth_ammeter['n']+=gnd
#gfi branch
#isolation from netureal and chaiesse groud at load
gfi_monter=V(ref='amm_gfi', dc_value=0@u_V)
#gfi branch currernt monter
chiasse_n_sep=R(ref='neu_chassie', value=Rgfi);
net_N & gfi_monter & chiasse_n_sep & gnd
#power behind the pannel
vs_Aphase=SINEV(ref='A', ac_phase=0, ac_magnitude=120@u_V)
vs_Aphase['P', 'N']+=net_a, net_n
vs_Bphase=SINEV(ref='B', ac_phase=np.deg2rad(-90), ac_magnitude=120@u_V)
vs_Bphase['N', 'P']+=net_b, net_n
#supciruct connections
net_A+=Load_A_posterm
net_B+=Load_B_posterm
net_N+=Load_A_neuterm, Load_B_neuterm
#stowe away the elements to read from
self.panel_internals={'AM_gfi':gfi_monter, 'AM_earth':earth_ammeter,
'APhase_Source':vs_Aphase,'BPhase_Source': vs_Bphase}
def draw_me(self):
"""
method to draw a representation of this subcircuit with
load representation with lcapy
"""
schematic=kiwi.Circuit()
#supplies
schematic.add('VA a n; down, l=$0^{\circ}$')
schematic.add('VB n b; down, l=$-90^{\circ}$')
#earth ground leg
schematic.add('W n n_1; left')
schematic.add('Rgn n_1, n_2; down, f_>=Idump')
schematic.add('AMearth n_2 0_1; down')
schematic.add('W 0_1 0; down=0.2, ground')
#wire legs
schematic.add('RaA a A_; right=2.5, f_>=Isource')
schematic.add('RbB b B_; right=2.5, f_>=Isource')
schematic.add('RnN n N_; right=2.5, f<_=Ireturn')
#loads
schematic.add('RloadA A_ N_; down')
schematic.add('RloadB N_ B_; down')
#chaisse gfi branch
schematic.add('W N_ N1; right')
schematic.add('Ropen N1 N2; down')
schematic.add('AMgfi N2 0_2; down, f_>=Igfi')
schematic.add('W 0_2 0_3; down=0.2, cground')
schematic.draw()
USPanel=two_phase_panel()
USPanel.draw_me()
reset()
#whats on the 220 outlet
arm1=single_arm_mod.SKiDl_pack()
arm2=single_arm_mod.SKiDl_pack()
USPanel.SKiDl_circ(arm1['P'], arm1['N'], arm2['P'], arm2['N'])
circ=generate_netlist()
print(circ)
.title
RaA a A_ 0Ohm
RbB b B_ 0Ohm
RnN n N_ 0Ohm
Rgndbus n N_5 0Ohm
Vamm_earth N_5 0 0V
Vamm_gfi N_ N_6 0V
Rneu_chassie N_6 0 1e+16Ohm
VA a n DC 0V AC 120V 0.0rad SIN(0V 1V 50Hz 0s 0Hz)
VB n b DC 0V AC 120V -90.0rad SIN(0V 1V 50Hz 0s 0Hz)
L1 A_ N_7 0.10063694267515924H
R6 A_ N_8 0.8Ohm
L2 N_8 N_9 0.013821656050955413H
R7 N_9 N_7 442.0Ohm
L3 N_7 N_ 0.10063694267515924H
R8 N_7 N_10 0.8Ohm
L4 N_10 N_11 0.013821656050955413H
R9 N_11 N_ 442.0Ohm
L5 B_ N_12 0.10063694267515924H
R10 B_ N_13 0.8Ohm
L6 N_13 N_14 0.013821656050955413H
R11 N_14 N_12 442.0Ohm
L7 N_12 N_ 0.10063694267515924H
R12 N_12 N_15 0.8Ohm
L8 N_15 N_16 0.013821656050955413H
R13 N_16 N_ 442.0Ohm
No errors or warnings found during netlist generation.
sim=circ.simulator()
ac_vals=sim.ac(start_frequency=48@u_Hz, stop_frequency=62@u_Hz, number_of_points=100, variation='lin')
ac_data_base_df=pd.DataFrame(index=ac_vals.frequency.as_ndarray())
ac_data_base_df.index.name='freq[Hz]'
ac_data_base_df['SourceA_v_[V]']=ac_vals[node(USPanel.panel_internals['APhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceA_i_[A]']=-ac_vals[get_skidl_spice_ref(USPanel.panel_internals['APhase_Source'])].as_ndarray()
ac_data_base_df['SourceB_v_[V]']=ac_vals[node(USPanel.panel_internals['BPhase_Source']['N'])].as_ndarray()
ac_data_base_df['SourceB_i_[A]']=-ac_vals[get_skidl_spice_ref(USPanel.panel_internals['BPhase_Source'])].as_ndarray()
ac_data_base_df
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | |
---|---|---|---|---|
freq[Hz] | ||||
48.000000 | 120.000000+0.000000j | 0.135550-1.978080j | 0.000000+120.000000j | -1.978144-0.135559j |
48.141415 | 120.000000-0.000000j | 0.135549-1.972277j | 0.000000+120.000000j | -1.972341-0.135558j |
48.282829 | 120.000000+0.000000j | 0.135549-1.966508j | 0.000000+120.000000j | -1.966572-0.135558j |
48.424244 | 120.000000+0.000000j | 0.135548-1.960772j | 0.000000+120.000000j | -1.960836-0.135557j |
48.565655 | 120.000000+0.000000j | 0.135548-1.955071j | -0.000000+120.000000j | -1.955134-0.135557j |
... | ... | ... | ... | ... |
61.434345 | 120.000000+0.000000j | 0.135518-1.546158j | 0.000000+120.000000j | -1.546198-0.135525j |
61.575756 | 120.000000+0.000000j | 0.135517-1.542615j | -0.000000+120.000000j | -1.542654-0.135524j |
61.717171 | 120.000000-0.000000j | 0.135517-1.539088j | 0.000000+120.000000j | -1.539127-0.135524j |
61.858585 | 120.000000+0.000000j | 0.135517-1.535577j | 0.000000+120.000000j | -1.535616-0.135524j |
62.000000 | 120.000000-0.000000j | 0.135517-1.532082j | 0.000000+120.000000j | -1.532121-0.135524j |
100 rows × 4 columns
From here we will look that the power on each phase and across the phases
ac_data_AB=ac_data_base_df.copy()
ac_data_AB['Source_AB_v_[V]']=ac_data_AB['SourceA_v_[V]']-ac_data_AB['SourceB_v_[V]']
ac_data_AB['Source_AB_V_[V]']=np.abs(ac_data_AB['Source_AB_v_[V]'])
ac_data_AB['Source_AB_vphase_[deg]']=np.angle(ac_data_AB['Source_AB_v_[V]'], True)
ac_data_AB['Source_AB_i_[A]']=ac_data_AB['SourceA_i_[A]']-ac_data_AB['SourceB_i_[A]']
ac_data_AB['Source_AB_I_[A]']=np.abs(ac_data_AB['Source_AB_i_[A]'])
ac_data_AB['Source_AB_iphase_[deg]']=np.angle(ac_data_AB['Source_AB_i_[A]'], True)
ac_data_AB.plot(y=['Source_AB_V_[V]', 'Source_AB_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_AB.plot(y=['Source_AB_vphase_[deg]', 'Source_AB_iphase_[deg]'], subplots=True, sharex=True, grid=True)
array([<AxesSubplot:xlabel='freq[Hz]'>, <AxesSubplot:xlabel='freq[Hz]'>],
dtype=object)
power_calcs_ac.autogen_powercalcs(ac_data_AB, 'Source_AB')
power_calcs_ac.make_power_plot(ac_data_AB, 'Source_AB')
ac_data_AB.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | Source_AB_I_[A] | Source_AB_iphase_[deg] | Source_AB_p_[W] | Source_AB_P_[W] | Source_AB_Q_[VAR] | Source_AB_S_[VA] | Source_AB_s_[VA] | Source_AB_pf_[] | Source_AB_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||
48.000000 | 120.000000+0.000000j | 0.135550-1.978080j | 0.000000+120.000000j | -1.978144-0.135559j | 120.000000-120.000000j | 169.705627 | -45.0 | 2.113694-1.842521j | 2.804030 | -41.078873 | 32.540817-474.745789j | 237.372894 | -16.270409 | 237.372894-16.270409j | 237.929855 | 0.997659 | 3.921114 |
48.141415 | 120.000000-0.000000j | 0.135549-1.972277j | 0.000000+120.000000j | -1.972341-0.135558j | 120.000000-120.000000j | 169.705627 | -45.0 | 2.107890-1.836718j | 2.795843 | -41.067390 | 32.540634-473.353027j | 236.676514 | -16.270317 | 236.676514-16.270317j | 237.235107 | 0.997645 | 3.932633 |
48.282829 | 120.000000+0.000000j | 0.135549-1.966508j | 0.000000+120.000000j | -1.966572-0.135558j | 120.000000-120.000000j | 169.705627 | -45.0 | 2.102121-1.830950j | 2.787703 | -41.055908 | 32.540482-471.968445j | 235.984222 | -16.270241 | 235.984222-16.270241j | 236.544449 | 0.997632 | 3.944119 |
48.424244 | 120.000000+0.000000j | 0.135548-1.960772j | 0.000000+120.000000j | -1.960836-0.135557j | 120.000000-120.000000j | 169.705627 | -45.0 | 2.096385-1.825215j | 2.779611 | -41.044426 | 32.540329-470.591980j | 235.295990 | -16.270164 | 235.295990-16.270164j | 235.857834 | 0.997618 | 3.955522 |
48.565655 | 120.000000+0.000000j | 0.135548-1.955071j | -0.000000+120.000000j | -1.955134-0.135557j | 120.000000-120.000000j | 169.705627 | -45.0 | 2.090682-1.819514j | 2.771567 | -41.032948 | 32.540176-469.223511j | 234.611755 | -16.270088 | 234.611755-16.270088j | 235.175232 | 0.997604 | 3.967041 |
power_calcs_ac.autogen_powercalcs(ac_data_base_df, 'SourceA')
power_calcs_ac.make_power_plot(ac_data_base_df, 'SourceA')
ac_data_base_df.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceA_p_[W] | SourceA_P_[W] | SourceA_Q_[VAR] | SourceA_S_[VA] | SourceA_s_[VA] | SourceA_pf_[] | SourceA_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||
48.000000 | 120.000000+0.000000j | 0.135550-1.978080j | 0.000000+120.000000j | -1.978144-0.135559j | 16.265974-237.369553j | 8.132987 | 118.684776 | 8.132987+118.684776j | 118.963112 | 0.068366 | 86.079880 |
48.141415 | 120.000000-0.000000j | 0.135549-1.972277j | 0.000000+120.000000j | -1.972341-0.135558j | 16.265923-236.673187j | 8.132961 | 118.336594 | 8.132961+118.336594j | 118.615746 | 0.068566 | 86.068390 |
48.282829 | 120.000000+0.000000j | 0.135549-1.966508j | 0.000000+120.000000j | -1.966572-0.135558j | 16.265869-235.980927j | 8.132935 | 117.990463 | 8.132935+117.990463j | 118.270424 | 0.068766 | 86.056908 |
48.424244 | 120.000000+0.000000j | 0.135548-1.960772j | 0.000000+120.000000j | -1.960836-0.135557j | 16.265816-235.292694j | 8.132908 | 117.646347 | 8.132908+117.646347j | 117.927124 | 0.068966 | 86.045418 |
48.565655 | 120.000000+0.000000j | 0.135548-1.955071j | -0.000000+120.000000j | -1.955134-0.135557j | 16.265764-234.608475j | 8.132882 | 117.304237 | 8.132882+117.304237j | 117.585831 | 0.069165 | 86.033936 |
power_calcs_ac.autogen_powercalcs(ac_data_base_df, 'SourceB')
power_calcs_ac.make_power_plot(ac_data_base_df, 'SourceB')
ac_data_base_df.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceA_p_[W] | SourceA_P_[W] | SourceA_Q_[VAR] | SourceA_S_[VA] | SourceA_s_[VA] | SourceA_pf_[] | SourceA_pfang_[deg] | SourceB_p_[W] | SourceB_P_[W] | SourceB_Q_[VAR] | SourceB_S_[VA] | SourceB_s_[VA] | SourceB_pf_[] | SourceB_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | ||||||||||||||||||
48.000000 | 120.000000+0.000000j | 0.135550-1.978080j | 0.000000+120.000000j | -1.978144-0.135559j | 16.265974-237.369553j | 8.132987 | 118.684776 | 8.132987+118.684776j | 118.963112 | 0.068366 | 86.079880 | 16.267048-237.377335j | -8.133524 | -118.688667 | -8.133524-118.688667j | 118.967026 | -0.068368 | 93.920242 |
48.141415 | 120.000000-0.000000j | 0.135549-1.972277j | 0.000000+120.000000j | -1.972341-0.135558j | 16.265923-236.673187j | 8.132961 | 118.336594 | 8.132961+118.336594j | 118.615746 | 0.068566 | 86.068390 | 16.266991-236.680923j | -8.133495 | -118.340462 | -8.133495-118.340462j | 118.619637 | -0.068568 | 93.931725 |
48.282829 | 120.000000+0.000000j | 0.135549-1.966508j | 0.000000+120.000000j | -1.966572-0.135558j | 16.265869-235.980927j | 8.132935 | 117.990463 | 8.132935+117.990463j | 118.270424 | 0.068766 | 86.056908 | 16.266935-235.988617j | -8.133468 | -117.994308 | -8.133468-117.994308j | 118.274300 | -0.068768 | 93.943214 |
48.424244 | 120.000000+0.000000j | 0.135548-1.960772j | 0.000000+120.000000j | -1.960836-0.135557j | 16.265816-235.292694j | 8.132908 | 117.646347 | 8.132908+117.646347j | 117.927124 | 0.068966 | 86.045418 | 16.266880-235.300339j | -8.133440 | -117.650169 | -8.133440-117.650169j | 117.930977 | -0.068968 | 93.954697 |
48.565655 | 120.000000+0.000000j | 0.135548-1.955071j | -0.000000+120.000000j | -1.955134-0.135557j | 16.265764-234.608475j | 8.132882 | 117.304237 | 8.132882+117.304237j | 117.585831 | 0.069165 | 86.033936 | 16.266825-234.616089j | -8.133412 | -117.308044 | -8.133412-117.308044j | 117.589668 | -0.069168 | 93.966179 |
Three Phase Four Wire Y (Star) Topology ¶¶
To be complete we also model the two variations of three-phase circuit topologies commonly found. We will start with the so-called Y (or star) topology which from a circuit topology standpoint is just a variation of the North American two-phase. Where a third C phase is added and all the phase source negative terminal are bounded to the neutral with 120 degrees of separation between the phases. Thus the Y topology testbench below includes the same overall structures as the two_phase_panel
testbench has with resistor isolated neutral with separate branches to earth and chassis return.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 three_phaseY_panel class
#class to create a SPICE model of a three-phase Y source
#with ground branch and gfi branch in addition to neutral branch
class three_phaseY_panel:
"""
Class to contain the needed Skidl package (method) to invoke the circuit for simulations
and drawing just the packaged circuit via lcapy.
The circuit in hand is a model of a 3 phase Y (star) source circuit with resistor separated
neutral from the ground along with an open separation to a parallel gfi path for chassis ground to the earth ground
"""
def __init__(self):
pass
@subcircuit
def SKiDl_circ(self, Load_A_posterm, Load_A_neuterm,
Load_B_posterm, Load_B_neuterm,
Load_C_posterm, Load_C_neuterm,
RaA=0@u_Ohm, RbB=0@u_Ohm, RcC=0@u_Ohm, RnN=0@u_Ohm,
Rearth=0@u_Ohm, Rgfi=1e16@u_Ohm):
"""
SKiDl subcircuit of a primitive representation of a 3 phase Y (star) circuit
panel with resistor separated ground from neutral and open from ground return
Terminals:
Load_A_posterm: Positive terminal for the A phase load
Load_A_neuterm: Neutral terminal for the A phase load
Load_B_posterm: Positive terminal for the B phase load
Load_B_neuterm: Neutral termanl for the B phase load
Load_C_posterm: Positive terminal for the C phase load
Load_C_neuterm: Neutral terminal for the C phase load
Args:
RaA (float; 0@u_Ohm; ohms): Resistance between the A source and A terminal
RbB (float; 0@u_Ohm; ohms): Resistance between the B source and B terminal
RcC (float; 0@u_Ohm; ohms): Resistance between the C source and C terminal
RnN (float; 0@u_Ohm; ohms): Resistance between the N source and N terminal
Rearth (float; 0@u_Ohm; ohms): Resistance between the earth and neutral
Rgfi (float; 1e16@u_Ohm; ohms): Resistance between neutral and chassis ground gfi branch
Returns:
returns a SKiDl subcircuit. Some of the internal elements SkiDl objects
are also stored in `self.panel_internals` which is a dictionary with
names for the objects as keys and the SkiDl object's elements as corresponding values
"""
#ngspice treats net a == net A; so add underbar to save from singular matrix
net_a=Net('a'); net_A=Net('A_')
net_b=Net('b'); net_B=Net('B_')
net_c=Net('c'); net_C=Net('C_')
net_n=Net('n'); net_N=Net('N_')
#create wire models to deal with voltage source parallel
#to inductor singularities
phase_a_wire=R(ref='aA', value=RaA); phase_a_wire[1, 2]+=net_a, net_A
phase_b_wire=R(ref='bB', value=RbB); phase_b_wire[1, 2]+=net_b, net_B
phase_c_wire=R(ref='cC', value=RcC); phase_c_wire[1, 2]+=net_c, net_C
phase_n_wire=R(ref='nN', value=RnN); phase_n_wire[1, 2]+=net_n, net_N
#bus neutral to earth
#create wire (resistor) to isolate neutral and earth ground
#can be used to model floating neutral
earth_n_sep=R(ref='gndbus', value=Rearth)
#floating neutral monitor
earth_ammeter=V(ref='amm_earth', dc_value=0@u_V)
earth_n_sep[1, 2]+=net_n, earth_ammeter['p']
earth_ammeter['n']+=gnd
#gfi branch
#isolation from netureal and chaiesse groud at load
gfi_monter=V(ref='amm_gfi', dc_value=0@u_V)
#gfi branch currernt monter
chiasse_n_sep=R(ref='neu_chassie', value=Rgfi);
net_N & gfi_monter & chiasse_n_sep & gnd
#power behind the pannel
vs_Aphase=SINEV(ref='A', ac_phase=0, ac_magnitude=120@u_V)
vs_Aphase['P', 'N']+=net_a, net_n
vs_Bphase=SINEV(ref='B', ac_phase=np.deg2rad(-120), ac_magnitude=120@u_V)
vs_Bphase['P', 'N']+=net_b, net_n
vs_Cphase=SINEV(ref='C', ac_phase=np.deg2rad(120), ac_magnitude=120@u_V)
vs_Cphase['P', 'N']+=net_c, net_n
#supciruct connections
net_A+=Load_A_posterm
net_B+=Load_B_posterm
net_C+=Load_C_posterm
net_N+=Load_A_neuterm, Load_B_neuterm, Load_C_neuterm
#stowe away the elements to read from
self.panel_internals={'AM_gfi':gfi_monter, 'AM_earth':earth_ammeter,
'APhase_Source':vs_Aphase,'BPhase_Source': vs_Bphase, 'CPhase_Source': vs_Cphase}
def draw_me(self):
"""
method to draw a representation of this subcircuit with
load representation with lcapy
"""
schematic=kiwi.Circuit()
#supplies
schematic.add('VA a na; down=3, l=$0^{\circ}$')
schematic.add('W na nb; right')
schematic.add('VB b nb; down=2, l=$-120^{\circ}$')
schematic.add('W nb nc; right')
schematic.add('VC c nc; down, l=$120^{\circ}$')
#earth ground leg
schematic.add('W na n_1; left')
schematic.add('Rgn n_1, n_2; down, f_>=Idump')
schematic.add('AMearth n_2 0_1; down')
schematic.add('W 0_1 0; down=0.2, ground')
#wire legs
schematic.add('RaA a A_; right=2.5, f_>=Isource')
schematic.add('RbB b B_; right=2.5, f_>=Isource')
schematic.add('RcC c C_; right=2.5, f_>=Isource')
schematic.add('RnN nc N_C; right=2.5, f<_=Ireturn')
#loads
schematic.add('RloadC C_ N_C; down')
schematic.add('W N_C N_B; right')
schematic.add('RloadB B_ N_B; down')
schematic.add('W N_B N_A; right')
schematic.add('RloadA A_ N_A; down')
#chaisse gfi branch
schematic.add('W N_A N1; right')
schematic.add('Ropen N1 N2; down')
schematic.add('AMgfi N2 0_2; down, f_>=Igfi')
schematic.add('W 0_2 0_3; down=0.2, cground')
schematic.draw()
Y3=three_phaseY_panel()
Y3.draw_me()
reset()
arm1=single_arm_mod.SKiDl_pack()
arm2=single_arm_mod.SKiDl_pack()
arm3=single_arm_mod.SKiDl_pack()
Y3.SKiDl_circ(arm1['P'], arm1['N'], arm2['P'], arm2['N'], arm3['P'], arm3['N'])
circ=generate_netlist()
print(circ)
.title
RaA a A_ 0Ohm
RbB b B_ 0Ohm
RcC c C_ 0Ohm
RnN n N_ 0Ohm
Rgndbus n N_7 0Ohm
Vamm_earth N_7 0 0V
Vamm_gfi N_ N_8 0V
Rneu_chassie N_8 0 1e+16Ohm
VA a n DC 0V AC 120V 0.0rad SIN(0V 1V 50Hz 0s 0Hz)
VB b n DC 0V AC 120V -119.99999999999999rad SIN(0V 1V 50Hz 0s 0Hz)
VC c n DC 0V AC 120V 119.99999999999999rad SIN(0V 1V 50Hz 0s 0Hz)
L1 A_ N_9 0.10063694267515924H
R7 A_ N_10 0.8Ohm
L2 N_10 N_11 0.013821656050955413H
R8 N_11 N_9 442.0Ohm
L3 N_9 N_ 0.10063694267515924H
R9 N_9 N_12 0.8Ohm
L4 N_12 N_13 0.013821656050955413H
R10 N_13 N_ 442.0Ohm
L5 B_ N_14 0.10063694267515924H
R11 B_ N_15 0.8Ohm
L6 N_15 N_16 0.013821656050955413H
R12 N_16 N_14 442.0Ohm
L7 N_14 N_ 0.10063694267515924H
R13 N_14 N_17 0.8Ohm
L8 N_17 N_18 0.013821656050955413H
R14 N_18 N_ 442.0Ohm
L9 C_ N_19 0.10063694267515924H
R15 C_ N_20 0.8Ohm
L10 N_20 N_21 0.013821656050955413H
R16 N_21 N_19 442.0Ohm
L11 N_19 N_ 0.10063694267515924H
R17 N_19 N_22 0.8Ohm
L12 N_22 N_23 0.013821656050955413H
R18 N_23 N_ 442.0Ohm
No errors or warnings found during netlist generation.
sim=circ.simulator()
ac_vals=sim.ac(start_frequency=48@u_Hz, stop_frequency=62@u_Hz, number_of_points=100, variation='lin')
ac_data_base_df=pd.DataFrame(index=ac_vals.frequency.as_ndarray())
ac_data_base_df.index.name='freq[Hz]'
ac_data_base_df['SourceA_v_[V]']=ac_vals[node(Y3.panel_internals['APhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceA_i_[A]']=-ac_vals[get_skidl_spice_ref(Y3.panel_internals['APhase_Source'])].as_ndarray()
ac_data_base_df['SourceB_v_[V]']=ac_vals[node(Y3.panel_internals['BPhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceB_i_[A]']=-ac_vals[get_skidl_spice_ref(Y3.panel_internals['BPhase_Source'])].as_ndarray()
ac_data_base_df['SourceC_v_[V]']=ac_vals[node(Y3.panel_internals['CPhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceC_i_[A]']=-ac_vals[get_skidl_spice_ref(Y3.panel_internals['CPhase_Source'])].as_ndarray()
ac_data_base_df
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | |
---|---|---|---|---|---|---|
freq[Hz] | ||||||
48.000000 | 120.000000+0.000000j | 0.135522-1.978116j | -60.000000-103.923050j | -1.780860+0.871693j | -60.000000+103.923050j | 1.645338+1.106423j |
48.141415 | 120.000000-0.000000j | 0.135522-1.972313j | -60.000000-103.923050j | -1.775834+0.868792j | -60.000000+103.923050j | 1.640313+1.103522j |
48.282829 | 120.000000+0.000000j | 0.135521-1.966544j | -60.000000-103.923050j | -1.770838+0.865907j | -60.000000+103.923050j | 1.635316+1.100637j |
48.424244 | 120.000000+0.000000j | 0.135521-1.960809j | -60.000000-103.923050j | -1.765871+0.863040j | -60.000000+103.923050j | 1.630350+1.097769j |
48.565655 | 120.000000-0.000000j | 0.135521-1.955107j | -60.000000-103.923050j | -1.760932+0.860189j | -60.000000+103.923050j | 1.625412+1.094918j |
... | ... | ... | ... | ... | ... | ... |
61.434345 | 120.000000+0.000000j | 0.135501-1.546182j | -60.000000-103.923050j | -1.406783+0.655743j | -60.000000+103.923050j | 1.271282+0.890438j |
61.575756 | 120.000000+0.000000j | 0.135501-1.542638j | -60.000000-103.923050j | -1.403714+0.653972j | -60.000000+103.923050j | 1.268213+0.888667j |
61.717171 | 120.000000+0.000000j | 0.135501-1.539111j | -60.000000-103.923050j | -1.400660+0.652208j | -60.000000+103.923050j | 1.265159+0.886903j |
61.858585 | 120.000000+0.000000j | 0.135501-1.535600j | -60.000000-103.923050j | -1.397619+0.650453j | -60.000000+103.923050j | 1.262118+0.885147j |
62.000000 | 120.000000+0.000000j | 0.135501-1.532105j | -60.000000-103.923050j | -1.394592+0.648705j | -60.000000+103.923050j | 1.259091+0.883400j |
100 rows × 6 columns
ac_data_ABC=ac_data_base_df.copy()
#AB voltage
ac_data_ABC['Source_AB_v_[V]']=ac_data_ABC['SourceA_v_[V]']-ac_data_ABC['SourceB_v_[V]']
ac_data_ABC['Source_AB_V_[V]']=np.abs(ac_data_ABC['Source_AB_v_[V]'])
ac_data_ABC['Source_AB_vphase_[deg]']=np.angle(ac_data_ABC['Source_AB_v_[V]'], True)
#AB current
ac_data_ABC['Source_AB_i_[A]']=ac_data_ABC['SourceA_i_[A]']-ac_data_ABC['SourceB_i_[A]']
ac_data_ABC['Source_AB_I_[A]']=np.abs(ac_data_ABC['Source_AB_i_[A]'])
ac_data_ABC['Source_AB_iphase_[deg]']=np.angle(ac_data_ABC['Source_AB_i_[A]'], True)
####################################################################################
#BC voltage
ac_data_ABC['Source_BC_v_[V]']=ac_data_ABC['SourceB_v_[V]']-ac_data_ABC['SourceC_v_[V]']
ac_data_ABC['Source_BC_V_[V]']=np.abs(ac_data_ABC['Source_BC_v_[V]'])
ac_data_ABC['Source_BC_vphase_[deg]']=np.angle(ac_data_ABC['Source_BC_v_[V]'], True)
#BC current
ac_data_ABC['Source_BC_i_[A]']=ac_data_ABC['SourceB_i_[A]']-ac_data_ABC['SourceC_i_[A]']
ac_data_ABC['Source_BC_I_[A]']=np.abs(ac_data_ABC['Source_BC_i_[A]'])
ac_data_ABC['Source_BC_iphase_[deg]']=np.angle(ac_data_ABC['Source_BC_i_[A]'], True)
####################################################################################
#CA voltage
ac_data_ABC['Source_CA_v_[V]']=ac_data_ABC['SourceC_v_[V]']-ac_data_ABC['SourceA_v_[V]']
ac_data_ABC['Source_CA_V_[V]']=np.abs(ac_data_ABC['Source_CA_v_[V]'])
ac_data_ABC['Source_CA_vphase_[deg]']=np.angle(ac_data_ABC['Source_CA_v_[V]'], True)
#CA current
ac_data_ABC['Source_CA_i_[A]']=ac_data_ABC['SourceC_i_[A]']-ac_data_ABC['SourceA_i_[A]']
ac_data_ABC['Source_CA_I_[A]']=np.abs(ac_data_ABC['Source_CA_i_[A]'])
ac_data_ABC['Source_CA_iphase_[deg]']=np.angle(ac_data_ABC['Source_CA_i_[A]'], True)
ac_data_ABC.plot(y=['Source_AB_V_[V]', 'Source_AB_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_AB_vphase_[deg]', 'Source_AB_iphase_[deg]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_BC_V_[V]', 'Source_BC_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_BC_vphase_[deg]', 'Source_BC_iphase_[deg]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_CA_V_[V]', 'Source_CA_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_CA_vphase_[deg]', 'Source_CA_iphase_[deg]'], subplots=True, sharex=True, grid=True)
array([<AxesSubplot:xlabel='freq[Hz]'>, <AxesSubplot:xlabel='freq[Hz]'>],
dtype=object)
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_AB')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_AB')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_CA_i_[A] | Source_CA_I_[A] | Source_CA_iphase_[deg] | Source_AB_p_[W] | Source_AB_P_[W] | Source_AB_Q_[VAR] | Source_AB_S_[VA] | Source_AB_s_[VA] | Source_AB_pf_[] | Source_AB_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 120.000000+0.000000j | 0.135522-1.978116j | -60.000000-103.923050j | -1.780860+0.871693j | -60.000000+103.923050j | 1.645338+1.106423j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.916382-2.849809j | ... | 1.509816+3.084540j | 3.434229 | 63.919239 | 641.109558-313.809387j | 24.393921 | 356.060944 | 24.393921+356.060944j | 356.895599 | 0.06835 | 86.080750 |
48.141415 | 120.000000-0.000000j | 0.135522-1.972313j | -60.000000-103.923050j | -1.775834+0.868792j | -60.000000+103.923050j | 1.640313+1.103522j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.911356-2.841105j | ... | 1.504791+3.075835j | 3.424202 | 63.930729 | 639.300293-312.764954j | 24.393890 | 355.016388 | 24.393890+355.016388j | 355.853485 | 0.06855 | 86.069267 |
48.282829 | 120.000000+0.000000j | 0.135521-1.966544j | -60.000000-103.923050j | -1.770838+0.865907j | -60.000000+103.923050j | 1.635316+1.100637j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.906359-2.832451j | ... | 1.499795+3.067181j | 3.414233 | 63.942215 | 637.501587-311.726593j | 24.393814 | 353.977966 | 24.393814+353.977966j | 354.817505 | 0.06875 | 86.057785 |
48.424244 | 120.000000+0.000000j | 0.135521-1.960809j | -60.000000-103.923050j | -1.765871+0.863040j | -60.000000+103.923050j | 1.630350+1.097769j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.901392-2.823848j | ... | 1.494829+3.058578j | 3.404322 | 63.953705 | 635.713501-310.694275j | 24.393784 | 352.945587 | 24.393784+352.945587j | 353.787567 | 0.06895 | 86.046295 |
48.565655 | 120.000000-0.000000j | 0.135521-1.955107j | -60.000000-103.923050j | -1.760932+0.860189j | -60.000000+103.923050j | 1.625412+1.094918j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.896453-2.815296j | ... | 1.489891+3.050025j | 3.394470 | 63.965187 | 633.935669-309.668030j | 24.393723 | 351.919220 | 24.393723+351.919220j | 352.763641 | 0.06915 | 86.034805 |
5 rows × 31 columns
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_BC')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_BC')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_AB_s_[VA] | Source_AB_pf_[] | Source_AB_pfang_[deg] | Source_BC_p_[W] | Source_BC_P_[W] | Source_BC_Q_[VAR] | Source_BC_S_[VA] | Source_BC_s_[VA] | Source_BC_pf_[] | Source_BC_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 120.000000+0.000000j | 0.135522-1.978116j | -60.000000-103.923050j | -1.780860+0.871693j | -60.000000+103.923050j | 1.645338+1.106423j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.916382-2.849809j | ... | 356.895599 | 0.06835 | 86.080750 | -48.787842+712.121887j | 24.393921 | 356.060944 | 24.393921+356.060944j | 356.895599 | 0.06835 | 86.080750 |
48.141415 | 120.000000-0.000000j | 0.135522-1.972313j | -60.000000-103.923050j | -1.775834+0.868792j | -60.000000+103.923050j | 1.640313+1.103522j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.911356-2.841105j | ... | 355.853485 | 0.06855 | 86.069267 | -48.787754+710.032776j | 24.393877 | 355.016388 | 24.393877+355.016388j | 355.853485 | 0.06855 | 86.069267 |
48.282829 | 120.000000+0.000000j | 0.135521-1.966544j | -60.000000-103.923050j | -1.770838+0.865907j | -60.000000+103.923050j | 1.635316+1.100637j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.906359-2.832451j | ... | 354.817505 | 0.06875 | 86.057785 | -48.787666+707.955872j | 24.393833 | 353.977936 | 24.393833+353.977936j | 354.817474 | 0.06875 | 86.057777 |
48.424244 | 120.000000+0.000000j | 0.135521-1.960809j | -60.000000-103.923050j | -1.765871+0.863040j | -60.000000+103.923050j | 1.630350+1.097769j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.901392-2.823848j | ... | 353.787567 | 0.06895 | 86.046295 | -48.787567+705.891113j | 24.393784 | 352.945557 | 24.393784+352.945557j | 353.787537 | 0.06895 | 86.046295 |
48.565655 | 120.000000-0.000000j | 0.135521-1.955107j | -60.000000-103.923050j | -1.760932+0.860189j | -60.000000+103.923050j | 1.625412+1.094918j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.896453-2.815296j | ... | 352.763641 | 0.06915 | 86.034805 | -48.787479+703.838440j | 24.393740 | 351.919220 | 24.393740+351.919220j | 352.763641 | 0.06915 | 86.034805 |
5 rows × 38 columns
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_CA')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_CA')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_BC_s_[VA] | Source_BC_pf_[] | Source_BC_pfang_[deg] | Source_CA_p_[W] | Source_CA_P_[W] | Source_CA_Q_[VAR] | Source_CA_S_[VA] | Source_CA_s_[VA] | Source_CA_pf_[] | Source_CA_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 120.000000+0.000000j | 0.135522-1.978116j | -60.000000-103.923050j | -1.780860+0.871693j | -60.000000+103.923050j | 1.645338+1.106423j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.916382-2.849809j | ... | 356.895599 | 0.06835 | 86.080750 | -592.321777-398.312439j | 24.393906 | 356.060944 | 24.393906+356.060944j | 356.895599 | 0.06835 | 86.080757 |
48.141415 | 120.000000-0.000000j | 0.135522-1.972313j | -60.000000-103.923050j | -1.775834+0.868792j | -60.000000+103.923050j | 1.640313+1.103522j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.911356-2.841105j | ... | 355.853485 | 0.06855 | 86.069267 | -590.512573-397.267761j | 24.393875 | 355.016388 | 24.393875+355.016388j | 355.853485 | 0.06855 | 86.069267 |
48.282829 | 120.000000+0.000000j | 0.135521-1.966544j | -60.000000-103.923050j | -1.770838+0.865907j | -60.000000+103.923050j | 1.635316+1.100637j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.906359-2.832451j | ... | 354.817474 | 0.06875 | 86.057777 | -588.713989-396.229309j | 24.393845 | 353.977936 | 24.393845+353.977936j | 354.817474 | 0.06875 | 86.057777 |
48.424244 | 120.000000+0.000000j | 0.135521-1.960809j | -60.000000-103.923050j | -1.765871+0.863040j | -60.000000+103.923050j | 1.630350+1.097769j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.901392-2.823848j | ... | 353.787537 | 0.06895 | 86.046295 | -586.925903-395.196838j | 24.393784 | 352.945587 | 24.393784+352.945587j | 353.787567 | 0.06895 | 86.046295 |
48.565655 | 120.000000-0.000000j | 0.135521-1.955107j | -60.000000-103.923050j | -1.760932+0.860189j | -60.000000+103.923050j | 1.625412+1.094918j | 180.000000+103.923050j | 207.8461 | 30.000002 | 1.896453-2.815296j | ... | 352.763641 | 0.06915 | 86.034805 | -585.148193-394.170380j | 24.393723 | 351.919189 | 24.393723+351.919189j | 352.763611 | 0.06915 | 86.034805 |
5 rows × 45 columns
Three Phase Three Wire \(\Delta\) Topology¶
Delta topologies are just weird, I have only had to work with them once when dealing with a 480V heater on an ancient giant diffusion pump. (if you don’t know what a diffusion pump is just watch Applied Science’s video on “Giant glass diffusion pump and cathode ray tube demo” ) ) The issue with them is that there is no neutral or ground. So because of that there being phased out when not needed. And there are ways to convert between them but to do it properly requires some specialty power engineering that is beyond this book. But for the analytical conversion see the YT videos by ALL ABOUT ELECTRONICS “Star to Delta Conversion (With Proof and Example)” & “Delta to Star Conversion (with proof and example)”.
YouTubeVideo('9b17eqCT4-g', width=500, height=400)
YouTubeVideo('OV0qi7yzKAM', width=500, height=400)
In recreating the Delta configuration in SPICE since there is no neutral or ground and SPICE requires one it can be a challenge to add one. One school of thought is to just add a ground to any of the phases. This not ideal since when measuring a Delta circuit in the wild the measurements are done between the phases and there is no ground involved. So instead we have opted to use the Delta circuit SPICE model employed by Alexander & Sadiki in their standard entry text “Fundamentals of Electric Circuits”. This method of modeling hides the ground behind open resistors connected to each phase. This is a far more robust and more representative way of modeling a Delta circuit. On top of that zero Ohm resistors are added to each of the three phases negative terminal to avoid a loop error in SPICE.
#%%writefile -a AC_2_Codes.py
#chapteer 2 section1 three_phaseDelta_panel class
#class to create a SPICE model of a three-phase Delta source
class three_phaseDelta_panel:
def __init__(self):
pass
@subcircuit
def SKiDl_circ(self, Aterm_power, Bterm_power, Cterm_power):
"""
SKiDl subcircuit of a primitive representation of a US 120 2 phase circuit
panel with resistor separated ground from neutral and open from ground return
Terminals:
Aterm_power: terminal for the A phase to load
Bterm_power: terminal for the B phase to load
Cterm_power: terminal for the C phase to load
Returns:
returns a SKiDl subcircuit. Some of the internal elements SkiDl objects
are also stored in `self.panel_internals` which is a dictionary with
names for the objects as keys and the SkiDl object's elements as corresponding values
"""
net_a=Net('a'); net_b=Net('b'); net_c=Net('c')
#things connected to terminal a
vs_ABphase=SINEV(ref='AB', ac_phase=0, ac_magnitude=120@u_V)
rabwire=R(ref='abwire', value=0@u_Ohm)
ragnd_open=R(ref='aopen', value=1e16@u_Ohm)
net_a & vs_ABphase['p', 'n'] & rabwire[1,2] & net_b
net_a & ragnd_open[1, 2] & gnd
#things connected to terminal b
vs_BCphase=SINEV(ref='BC', ac_phase=np.deg2rad(-120), ac_magnitude=120@u_V)
rbcwire=R(ref='bcwire', value=0@u_Ohm)
rbgnd_open=R(ref='bopen', value=1e16@u_Ohm)
net_b & vs_BCphase['p', 'n'] & rbcwire[1,2] & net_c
net_b & rbgnd_open[1, 2] & gnd
#things connected to terminal c
vs_CAphase=SINEV(ref='CA', ac_phase=np.deg2rad(120), ac_magnitude=120@u_V)
rcawire=R(ref='cawire', value=0@u_Ohm)
rcgnd_open=R(ref='copen', value=1e16@u_Ohm)
net_c & vs_CAphase['p', 'n'] & rcawire[1,2] & net_a
net_c & rcgnd_open[1, 2] & gnd
# make the connections to the rest of the circuit
net_a+=Aterm_power
net_b+=Bterm_power
net_c+=Cterm_power
#stowe away the elements to read from
self.panel_internals={
'ABPhase_Source':vs_ABphase,'BCPhase_Source': vs_BCphase, 'CAPhase_Source': vs_CAphase}
def draw_me(self):
"""
method to draw a representation of this subcircuit with
load representation with lcapy
"""
schematic=kiwi.Circuit()
schematic.add('W c1 c; right')
schematic.add('W c2 c1; right')
schematic.add('W c3 c2; right')
schematic.add('W vc c3; down')
schematic.add('Vca vc va; up, l=$120^{\circ}$')
schematic.add('Rcawire va a3; up')
schematic.add('W a3 a2; right')
schematic.add('W a2 a1; right')
schematic.add('W a1 a; right')
schematic.add('Ragnd a2 0; down')
schematic.add('Rcgnd c2 0; up')
schematic.add('Rbgnd 0 b1; right')
schematic.add('W 0 0_1; left=0.2, ground')
schematic.add('Vab a1 vb; down, l=$0^{\circ}$')
schematic.add('Rabwire vb b1; down')
schematic.add('Vbc b1 vc1; down, l=$-120^{\circ}$')
schematic.add('Rbcwire vc1 c1; down')
schematic.add('W b1 b; right')
schematic.draw()
D3=three_phaseDelta_panel()
D3.draw_me()
reset()
net_A=Net('A_'); net_B=Net('B_'); net_C=Net('C_')
arm1=single_arm_mod.SKiDl_pack()
arm1wire=R(value=0@u_Ohm)
arm2=single_arm_mod.SKiDl_pack()
arm2wire=R(value=0@u_Ohm)
arm3=single_arm_mod.SKiDl_pack()
arm3wire=R(value=0@u_Ohm)
net_A & arm2wire[2, 1] & arm2['N', 'P'] & net_C & arm1wire[2, 1] & arm1['N', 'P'] & net_B & arm3wire[2, 1] & arm3['N', 'P'] & net_A
D3.SKiDl_circ(net_A, net_B, net_C)
circ=generate_netlist()
print(circ)
WARNING: Merging two named nets (a and A_) into a.
WARNING: Merging two named nets (b and B_) into b.
WARNING: Merging two named nets (C_ and c) into C_.
.title
R1 N_2 C_ 0Ohm
R2 N_4 a 0Ohm
R3 N_6 b 0Ohm
VAB a N_7 DC 0V AC 120V 0.0rad SIN(0V 1V 50Hz 0s 0Hz)
Rabwire N_7 b 0Ohm
Raopen a 0 1e+16Ohm
VBC b N_8 DC 0V AC 120V -119.99999999999999rad SIN(0V 1V 50Hz 0s 0Hz)
Rbcwire N_8 C_ 0Ohm
Rbopen b 0 1e+16Ohm
VCA C_ N_9 DC 0V AC 120V 119.99999999999999rad SIN(0V 1V 50Hz 0s 0Hz)
Rcawire N_9 a 0Ohm
Rcopen C_ 0 1e+16Ohm
L1 b N_10 0.10063694267515924H
R10 b N_11 0.8Ohm
L2 N_11 N_12 0.013821656050955413H
R11 N_12 N_10 442.0Ohm
L3 N_10 N_2 0.10063694267515924H
R12 N_10 N_13 0.8Ohm
L4 N_13 N_14 0.013821656050955413H
R13 N_14 N_2 442.0Ohm
L5 C_ N_15 0.10063694267515924H
R14 C_ N_16 0.8Ohm
L6 N_16 N_17 0.013821656050955413H
R15 N_17 N_15 442.0Ohm
L7 N_15 N_4 0.10063694267515924H
R16 N_15 N_18 0.8Ohm
L8 N_18 N_19 0.013821656050955413H
R17 N_19 N_4 442.0Ohm
L9 a N_20 0.10063694267515924H
R18 a N_21 0.8Ohm
L10 N_21 N_22 0.013821656050955413H
R19 N_22 N_20 442.0Ohm
L11 N_20 N_6 0.10063694267515924H
R20 N_20 N_23 0.8Ohm
L12 N_23 N_24 0.013821656050955413H
R21 N_24 N_6 442.0Ohm
3 warnings found during netlist generation.
0 errors found during netlist generation.
sim=circ.simulator()
ac_vals=sim.ac(start_frequency=48@u_Hz, stop_frequency=62@u_Hz, number_of_points=100, variation='lin')
D3.panel_internals
{'ABPhase_Source':
SINEV (sinev, SINUSOIDALVOLTAGE, sinusoidalvoltage): Sinusoidal voltage source
Pin AB/1/p,anode,A,plus,+/PASSIVE
Pin AB/2/n,neg,minus,K,-,negative,C,m,cathode/PASSIVE,
'BCPhase_Source':
SINEV (sinev, SINUSOIDALVOLTAGE, sinusoidalvoltage): Sinusoidal voltage source
Pin BC/1/p,anode,A,plus,+/PASSIVE
Pin BC/2/n,neg,minus,K,-,negative,C,m,cathode/PASSIVE,
'CAPhase_Source':
SINEV (sinev, SINUSOIDALVOLTAGE, sinusoidalvoltage): Sinusoidal voltage source
Pin CA/1/p,anode,A,plus,+/PASSIVE
Pin CA/2/n,neg,minus,K,-,negative,C,m,cathode/PASSIVE}
ac_data_base_df=pd.DataFrame(index=ac_vals.frequency.as_ndarray())
ac_data_base_df.index.name='freq[Hz]'
ac_data_base_df['SourceA_v_[V]']=ac_vals[node(D3.panel_internals['ABPhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceA_i_[A]']=-ac_vals[get_skidl_spice_ref(D3.panel_internals['ABPhase_Source'])].as_ndarray()
ac_data_base_df['SourceB_v_[V]']=ac_vals[node(D3.panel_internals['BCPhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceB_i_[A]']=-ac_vals[get_skidl_spice_ref(D3.panel_internals['BCPhase_Source'])].as_ndarray()
ac_data_base_df['SourceC_v_[V]']=ac_vals[node(D3.panel_internals['CAPhase_Source']['P'])].as_ndarray()
ac_data_base_df['SourceC_i_[A]']=-ac_vals[get_skidl_spice_ref(D3.panel_internals['CAPhase_Source'])].as_ndarray()
ac_data_base_df
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | |
---|---|---|---|---|---|---|
freq[Hz] | ||||||
48.000000 | 108.802368-6.661806j | 0.135554-1.978112j | -11.197499-6.663784j | -1.780872+0.871663j | 48.800720+97.260139j | 1.645318+1.106449j |
48.141415 | 60.320335-35.100140j | 0.135554-1.972309j | -59.679527-35.102112j | -1.775846+0.868761j | 0.318696+68.821808j | 1.640293+1.103547j |
48.282829 | 60.007690-34.652252j | 0.135553-1.966540j | -59.992176-34.654221j | -1.770850+0.865877j | 0.006055+69.269691j | 1.635297+1.100662j |
48.424244 | 59.979683-34.625153j | 0.135553-1.960804j | -60.020184-34.627113j | -1.765883+0.863010j | -0.021949+69.296799j | 1.630330+1.097794j |
48.565655 | 59.959332-34.640480j | 0.135552-1.955102j | -60.040535-34.642433j | -1.760944+0.860159j | -0.042294+69.281471j | 1.625392+1.094943j |
... | ... | ... | ... | ... | ... | ... |
61.434345 | 59.987144-34.628605j | 0.135521-1.546178j | -60.012718-34.630150j | -1.406790+0.655724j | -0.014125+69.293556j | 1.271269+0.890454j |
61.575756 | 45.524448-59.687809j | 0.135521-1.542635j | -74.475418-59.689350j | -1.403721+0.653953j | -14.476818+44.234352j | 1.268200+0.888682j |
61.717171 | 60.025768-34.658192j | 0.135521-1.539107j | -59.974094-34.659729j | -1.400666+0.652189j | 0.024505+69.263969j | 1.265146+0.886918j |
61.858585 | 60.002949-34.631813j | 0.135520-1.535596j | -59.996918-34.633350j | -1.397626+0.650434j | 0.001686+69.290352j | 1.262105+0.885162j |
62.000000 | 60.023380-34.665222j | 0.135520-1.532102j | -59.976486-34.666752j | -1.394599+0.648687j | 0.022120+69.256943j | 1.259079+0.883415j |
100 rows × 6 columns
ac_data_ABC=ac_data_base_df.copy()
#AB voltage
ac_data_ABC['Source_AB_v_[V]']=ac_data_ABC['SourceA_v_[V]']-ac_data_ABC['SourceB_v_[V]']
ac_data_ABC['Source_AB_V_[V]']=np.abs(ac_data_ABC['Source_AB_v_[V]'])
ac_data_ABC['Source_AB_vphase_[deg]']=np.angle(ac_data_ABC['Source_AB_v_[V]'], True)
#AB current
ac_data_ABC['Source_AB_i_[A]']=ac_data_ABC['SourceA_i_[A]']-ac_data_ABC['SourceB_i_[A]']
ac_data_ABC['Source_AB_I_[A]']=np.abs(ac_data_ABC['Source_AB_i_[A]'])
ac_data_ABC['Source_AB_iphase_[deg]']=np.angle(ac_data_ABC['Source_AB_i_[A]'], True)
####################################################################################
#BC voltage
ac_data_ABC['Source_BC_v_[V]']=ac_data_ABC['SourceB_v_[V]']-ac_data_ABC['SourceC_v_[V]']
ac_data_ABC['Source_BC_V_[V]']=np.abs(ac_data_ABC['Source_BC_v_[V]'])
ac_data_ABC['Source_BC_vphase_[deg]']=np.angle(ac_data_ABC['Source_BC_v_[V]'], True)
#BC current
ac_data_ABC['Source_BC_i_[A]']=ac_data_ABC['SourceB_i_[A]']-ac_data_ABC['SourceC_i_[A]']
ac_data_ABC['Source_BC_I_[A]']=np.abs(ac_data_ABC['Source_BC_i_[A]'])
ac_data_ABC['Source_BC_iphase_[deg]']=np.angle(ac_data_ABC['Source_BC_i_[A]'], True)
####################################################################################
#CA voltage
ac_data_ABC['Source_CA_v_[V]']=ac_data_ABC['SourceC_v_[V]']-ac_data_ABC['SourceA_v_[V]']
ac_data_ABC['Source_CA_V_[V]']=np.abs(ac_data_ABC['Source_CA_v_[V]'])
ac_data_ABC['Source_CA_vphase_[deg]']=np.angle(ac_data_ABC['Source_CA_v_[V]'], True)
#CA current
ac_data_ABC['Source_CA_i_[A]']=ac_data_ABC['SourceC_i_[A]']-ac_data_ABC['SourceA_i_[A]']
ac_data_ABC['Source_CA_I_[A]']=np.abs(ac_data_ABC['Source_CA_i_[A]'])
ac_data_ABC['Source_CA_iphase_[deg]']=np.angle(ac_data_ABC['Source_CA_i_[A]'], True)
ac_data_ABC.plot(y=['Source_AB_V_[V]', 'Source_AB_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_AB_vphase_[deg]', 'Source_AB_iphase_[deg]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_BC_V_[V]', 'Source_BC_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_BC_vphase_[deg]', 'Source_BC_iphase_[deg]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_CA_V_[V]', 'Source_CA_I_[A]'], subplots=True, sharex=True, grid=True)
ac_data_ABC.plot(y=['Source_CA_vphase_[deg]', 'Source_CA_iphase_[deg]'], subplots=True, sharex=True, grid=True);
I don’t understand why there is a bit of a spike in this simulation around 59Hz.
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_AB')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_AB')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_CA_i_[A] | Source_CA_I_[A] | Source_CA_iphase_[deg] | Source_AB_p_[W] | Source_AB_P_[W] | Source_AB_Q_[VAR] | Source_AB_S_[VA] | Source_AB_s_[VA] | Source_AB_pf_[] | Source_AB_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 108.802368-6.661806j | 0.135554-1.978112j | -11.197499-6.663784j | -1.780872+0.871663j | 48.800720+97.260139j | 1.645318+1.106449j | 119.999870+0.001978j | 119.999870 | 0.000944 | 1.916427-2.849775j | ... | 1.509764+3.084561j | 3.434226 | 63.920181 | 229.976578-341.968811j | 114.982658 | 170.988190 | 114.982658+170.988190j | 206.053329 | 0.558024 | 56.080757 |
48.141415 | 60.320335-35.100140j | 0.135554-1.972309j | -59.679527-35.102112j | -1.775846+0.868761j | 0.318696+68.821808j | 1.640293+1.103547j | 119.999863+0.001972j | 119.999863 | 0.000942 | 1.911400-2.841070j | ... | 1.504739+3.075856j | 3.424198 | 63.931671 | 229.373383-340.924255j | 114.681091 | 170.465912 | 114.681091+170.465912j | 205.451645 | 0.558190 | 56.069267 |
48.282829 | 60.007690-34.652252j | 0.135553-1.966540j | -59.992176-34.654221j | -1.770850+0.865877j | 0.006055+69.269691j | 1.635297+1.100662j | 119.999863+0.001968j | 119.999863 | 0.000940 | 1.906403-2.832417j | ... | 1.499743+3.067202j | 3.414229 | 63.943153 | 228.773712-339.885895j | 114.381287 | 169.946701 | 114.381287+169.946701j | 204.853516 | 0.558356 | 56.057785 |
48.424244 | 59.979683-34.625153j | 0.135553-1.960804j | -60.020184-34.627113j | -1.765883+0.863010j | -0.021949+69.296799j | 1.630330+1.097794j | 119.999863+0.001961j | 119.999863 | 0.000936 | 1.901436-2.823814j | ... | 1.494777+3.058599j | 3.404318 | 63.954636 | 228.177551-338.853577j | 114.083237 | 169.430511 | 114.083237+169.430511j | 204.258865 | 0.558523 | 56.046295 |
48.565655 | 59.959332-34.640480j | 0.135552-1.955102j | -60.040535-34.642433j | -1.760944+0.860159j | -0.042294+69.281471j | 1.625392+1.094943j | 119.999863+0.001953j | 119.999863 | 0.000933 | 1.896497-2.815262j | ... | 1.489840+3.050045j | 3.394466 | 63.966122 | 227.584869-337.827301j | 113.786942 | 168.917343 | 113.786942+168.917343j | 203.667709 | 0.558689 | 56.034805 |
5 rows × 31 columns
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_BC')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_BC')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_AB_s_[VA] | Source_AB_pf_[] | Source_AB_pfang_[deg] | Source_BC_p_[W] | Source_BC_P_[W] | Source_BC_Q_[VAR] | Source_BC_S_[VA] | Source_BC_s_[VA] | Source_BC_pf_[] | Source_BC_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 108.802368-6.661806j | 0.135554-1.978112j | -11.197499-6.663784j | -1.780872+0.871663j | 48.800720+97.260139j | 1.645318+1.106449j | 119.999870+0.001978j | 119.999870 | 0.000944 | 1.916427-2.849775j | ... | 206.053329 | 0.558024 | 56.080757 | 181.165359+370.149933j | 114.982643 | 170.988174 | 114.982643+170.988174j | 206.053299 | 0.558024 | 56.080757 |
48.141415 | 60.320335-35.100140j | 0.135554-1.972309j | -59.679527-35.102112j | -1.775846+0.868761j | 0.318696+68.821808j | 1.640293+1.103547j | 119.999863+0.001972j | 119.999863 | 0.000942 | 1.911400-2.841070j | ... | 205.451645 | 0.558190 | 56.069267 | 180.562393+369.105316j | 114.681084 | 170.465897 | 114.681084+170.465897j | 205.451630 | 0.558190 | 56.069267 |
48.282829 | 60.007690-34.652252j | 0.135553-1.966540j | -59.992176-34.654221j | -1.770850+0.865877j | 0.006055+69.269691j | 1.635297+1.100662j | 119.999863+0.001968j | 119.999863 | 0.000940 | 1.906403-2.832417j | ... | 204.853516 | 0.558356 | 56.057785 | 179.962982+368.066803j | 114.381287 | 169.946701 | 114.381287+169.946701j | 204.853516 | 0.558356 | 56.057785 |
48.424244 | 59.979683-34.625153j | 0.135553-1.960804j | -60.020184-34.627113j | -1.765883+0.863010j | -0.021949+69.296799j | 1.630330+1.097794j | 119.999863+0.001961j | 119.999863 | 0.000936 | 1.901436-2.823814j | ... | 204.258865 | 0.558523 | 56.046295 | 179.367020+367.034363j | 114.083244 | 169.430511 | 114.083244+169.430511j | 204.258865 | 0.558523 | 56.046288 |
48.565655 | 59.959332-34.640480j | 0.135552-1.955102j | -60.040535-34.642433j | -1.760944+0.860159j | -0.042294+69.281471j | 1.625392+1.094943j | 119.999863+0.001953j | 119.999863 | 0.000933 | 1.896497-2.815262j | ... | 203.667709 | 0.558689 | 56.034805 | 178.774582+366.007904j | 113.786934 | 168.917343 | 113.786934+168.917343j | 203.667709 | 0.558689 | 56.034809 |
5 rows × 38 columns
power_calcs_ac.autogen_powercalcs(ac_data_ABC, 'Source_CA')
power_calcs_ac.make_power_plot(ac_data_ABC, 'Source_CA')
ac_data_ABC.head()
SourceA_v_[V] | SourceA_i_[A] | SourceB_v_[V] | SourceB_i_[A] | SourceC_v_[V] | SourceC_i_[A] | Source_AB_v_[V] | Source_AB_V_[V] | Source_AB_vphase_[deg] | Source_AB_i_[A] | ... | Source_BC_s_[VA] | Source_BC_pf_[] | Source_BC_pfang_[deg] | Source_CA_p_[W] | Source_CA_P_[W] | Source_CA_Q_[VAR] | Source_CA_S_[VA] | Source_CA_s_[VA] | Source_CA_pf_[] | Source_CA_pfang_[deg] | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
freq[Hz] | |||||||||||||||||||||
48.000000 | 108.802368-6.661806j | 0.135554-1.978112j | -11.197499-6.663784j | -1.780872+0.871663j | 48.800720+97.260139j | 1.645318+1.106449j | 119.999870+0.001978j | 119.999870 | 0.000944 | 1.916427-2.849775j | ... | 206.053299 | 0.558024 | 56.080757 | -411.141937-28.181168j | 114.982651 | 170.988190 | 114.982651+170.988190j | 206.053314 | 0.558024 | 56.080757 |
48.141415 | 60.320335-35.100140j | 0.135554-1.972309j | -59.679527-35.102112j | -1.775846+0.868761j | 0.318696+68.821808j | 1.640293+1.103547j | 119.999863+0.001972j | 119.999863 | 0.000942 | 1.911400-2.841070j | ... | 205.451630 | 0.558190 | 56.069267 | -409.935791-28.181015j | 114.681091 | 170.465912 | 114.681091+170.465912j | 205.451645 | 0.558190 | 56.069267 |
48.282829 | 60.007690-34.652252j | 0.135553-1.966540j | -59.992176-34.654221j | -1.770850+0.865877j | 0.006055+69.269691j | 1.635297+1.100662j | 119.999863+0.001968j | 119.999863 | 0.000940 | 1.906403-2.832417j | ... | 204.853516 | 0.558356 | 56.057785 | -408.736664-28.180893j | 114.381271 | 169.946686 | 114.381271+169.946686j | 204.853485 | 0.558356 | 56.057785 |
48.424244 | 59.979683-34.625153j | 0.135553-1.960804j | -60.020184-34.627113j | -1.765883+0.863010j | -0.021949+69.296799j | 1.630330+1.097794j | 119.999863+0.001961j | 119.999863 | 0.000936 | 1.901436-2.823814j | ... | 204.258865 | 0.558523 | 56.046288 | -407.544586-28.180771j | 114.083237 | 169.430511 | 114.083237+169.430511j | 204.258865 | 0.558523 | 56.046295 |
48.565655 | 59.959332-34.640480j | 0.135552-1.955102j | -60.040535-34.642433j | -1.760944+0.860159j | -0.042294+69.281471j | 1.625392+1.094943j | 119.999863+0.001953j | 119.999863 | 0.000933 | 1.896497-2.815262j | ... | 203.667709 | 0.558689 | 56.034809 | -406.359467-28.180664j | 113.786942 | 168.917358 | 113.786942+168.917358j | 203.667725 | 0.558689 | 56.034809 |
5 rows × 45 columns
Citations:¶
[1] stackexchange Electrical Engineering. “Using a single phase induction motor equivalent circuit in LTspice,” Stack Overflow Forum, July 30, 2019. Available: https://electronics.stackexchange.com/questions/234290/using-a-single-phase-induction-motor-equivalent-circuit-in-ltspice)
[2] ALL ABOUT ELECTRONICS. “RMS (Root Mean Square) Value and Average Value of AC Signals ” YouTube, Sep 24, 2017. [Video file]. Available: https://youtu.be/qDHsokTcgck. [Accessed: Dec 20, 2020].
[3] Makarov, S., Ludwig, R. and Bitar, S., 2016. Practical Electrical Engineering. 1st ed. Cham: Springer International Publishing, pp.564-566.
[4] ALL ABOUT ELECTRONICS. “What is Power Factor? What is Leading & Lagging Power factor ? Power Factor Correction Methods” YouTube, Nov 5, 2017. [Video file]. Available: https://youtu.be/iDYWfBGwT1w. [Accessed: Dec 20, 2020].
[5] Notaroš, B., 2011. Electromagnetics. Upper Saddle River: Prentice Hall, p.398.
[6] Technology Connections. “The US electrical system is not 120V” YouTube, Jun 22, 2020. [Video file]. Available: https://youtu.be/jMmUoZh3Hq4. [Accessed: Dec 20, 2020].
[7] Technology Connections. “The GFCI/RCD: A Simple but Life-Saving Protector” YouTube, Aug 17, 2018. [Video file]. Available: https://youtu.be/ILBjnZq0n8s. [Accessed: Dec 20, 2020].
[8] grayfurnaceman. “The difference between neutral and ground on the electric panel” YouTube, Jan 26, 2014. [Video file]. Available: https://youtu.be/-n8CiU_6KqE. [Accessed: Dec 20, 2020].
[9] ALL ABOUT ELECTRONICS. “Star to Delta Conversion (With Proof and Example)” YouTube, Mar 6, 2017. [Video file]. Available: https://youtu.be/9b17eqCT4-g. [Accessed: Dec 20, 2020].
[10] ALL ABOUT ELECTRONICS. “Delta to Star Conversion (with proof and example)” YouTube, Feb 28, 2017. [Video file]. Available: https://youtu.be/OV0qi7yzKAM. [Accessed: Dec 20, 2020].
[11] Alexander, C. and Sadiku, M., 2013. Fundamentals Of Electric Circuits. 5th ed. New York: McGraw-Hill, p.531.