Voltages and Currents in Circuits

The scikit-rf Circuit object allows one to deduce the voltages and currents at all the intersections of the circuit for a given power (and phase) excitation at circuit’s ports. Here, few examples are examined, in order to clarify the conventions used for current directions.

[1]:
%matplotlib inline
[2]:
# standard imports
import numpy as np
import matplotlib.pyplot as plt
import skrf as rf
[3]:
rf.stylely()

A simple transmission line

To start, let’s assume a simple transmission line excited by a generator. Let’s assume the generator is matched to the line (\(Z_s=Z_0\)) and the line connected to a matched load (\(Z_0=Z_L\)), all 50 Ohm.

6dd9a53b008d47bd8083d3f8df5a4ce3

What is the RF currents and voltages at the input and output of this line for a given input power (and phase)?

[4]:
P_f = 1  # forward power in Watt
Z = 50  # source internal impedance, line characteristic impedance and load impedance
L = 10  # line length in [m]

freq = rf.Frequency(2, 2, 1, unit='GHz')
line_media = rf.media.DefinedGammaZ0(freq, z0=Z)  # lossless line medium
line = line_media.line(d=L, unit='m', name='line')  # transmission line Network

Assuming the source generates an input power of \(P_f\) with a phase \(\phi\), with such a line the voltage and current at the entrance of the line are:

\[V_{in} = \sqrt{2 Z_0 P_f} e^{j \phi}\]
\[I_{in} = \sqrt{2 \frac{P_f}{Z_0}} e^{j \phi}\]
[5]:
V_in = np.sqrt(2*Z*P_f)
I_in = np.sqrt(2*P_f/Z)
print(f'Input voltage and current: {V_in} V and {I_in} A')
Input voltage and current: 10.0 V and 0.2 A

The voltage and current evolve along the transmission line. The voltage and current at the output of the line can be calculated using the transmission line tools provided with scikit-rf:

[6]:
theta = rf.theta(line_media.gamma, freq.f, L)  # electrical length
V_out, I_out = rf.tlineFunctions.voltage_current_propagation(V_in, I_in, Z, theta)
print(f'Output voltage and current: {V_out} V and {I_out} A')
Output voltage and current: [-8.39071529+5.44021111j] V and [-0.16781431+0.10880422j] A

Let’s perform the same calculation using Circuit. First, one needs to define the circuit, that is to create input/output ports and to connect these ports to the transmission line Network we’ve already created. Then, we can build the circuit:

[7]:
port1 = rf.Circuit.Port(frequency=freq, name='port1', z0=50)
port2 = rf.Circuit.Port(frequency=freq, name='port2', z0=50)
cnx = [
    [(port1, 0), (line, 0)],
    [(port2, 0), (line, 1)]
]
crt = rf.Circuit(cnx)

It’s always a good practice to check if the circuit’s graph is as expected. In this case, the graph is pretty simple: 2 ports connected to a 2-ports Network.

[8]:
crt.plot_graph(network_labels=True, edge_labels=True, inter_labels=True)
../../_images/examples_circuit_Voltages_and_Currents_in_Circuits_13_0.png

Circuit provides two methods to determine voltages and currents at the circuit input/output ports (also known as “external ports”). These methods take as input the power and phase inputs at each ports:

[9]:
power = [1, 0]  # 1 Watt at port1 and 0 at port2
phase = [0, 0]  # 0 radians
[10]:
V_at_ports = crt.voltages_external(power, phase)
print(V_at_ports)
[[10.        +0.j         -8.39071529+5.44021111j]]
[11]:
I_at_ports = crt.currents_external(power, phase)
print(I_at_ports)
[[0.2       +0.j         0.16781431-0.10880422j]]

The results are similar to the previous, except the sign of the current at port2 which is reversed.

This is normal, as the currents_external() method defines the positive direction of a current as the direction which “enters” into the circuit. More details about this convention at the bottom of this example.

A coaxial T with different characteristic impedances

As a more advanced example, we’ve built in a full-wave software (here ANSYS HFSS, but other are fine too) the following structure: a coaxial T, defined with different coaxial cross-sections (and such different characteristic impedances).

3bd1cb1156604c18bc7dfd814d0935e1

All the three ports are excited with different power and phase to complicate a bit, as the following:

port

power [W]

phase [deg]

#1

1

-10

#2

2

-20

#3

3

+60

Full-wave reference solution

The voltages and currents are evaluated in the full-wave software directly. Voltage is obtained by integrating the E fields along a straight line going from the inner to the outer conductors in the port’s cross-sections. Current is obtained by integrating the H fields along a circle enclosing the inner conductor in port’s cross-sections. Note the circles are directed in order to define the positive current direction as the direction inward the ports (right-hand rule).

The full-wave solutions are given here for reference, at 3 frequencies:

[12]:
import pandas as pd  # convenient to read .csv files
pd.read_csv('circuit_vi_HFSS_Voltages.csv')
[12]:
Phase [deg] Freq [MHz] mag(V1) [V] mag(V2) [V] mag(V3) [V] ang_deg(V1) [deg] ang_deg(V2) [deg] ang_deg(V3) [deg]
0 0 10 19.333738 20.846031 15.272200 -10.650177 -9.289474 -9.877413
1 0 50 7.495888 12.520379 14.048313 -29.855289 -44.233339 168.419884
2 0 100 13.378541 9.825921 41.581058 -38.869964 -35.275652 102.383086
[13]:
pd.read_csv('circuit_vi_HFSS_Currents.csv')
[13]:
Phase [deg] Freq [MHz] mag(I1) [A] mag(I2) [A] mag(I3) [A] ang_deg(I1) [deg] ang_deg(I2) [deg] ang_deg(I3) [deg]
0 0 10 0.021591 0.137606 0.512142 156.244427 -90.890034 80.224971
1 0 50 0.275976 0.389991 0.622469 2.596668 6.317826 44.672585
2 0 100 0.220526 0.424061 0.384085 33.833593 -8.087971 -4.331087

In a Circuit simulator

The voltages and currents can also be derived using a Circuit simulator, like for example:

ea5d7847b7df4bc58397489f9ad857c5

(where, again, the current probes are oriented in such a way that the current is positive when flowing in the Network). This gives essentially the same results:

[14]:
pd.read_csv('circuit_vi_Designer_Voltages.csv')
[14]:
Freq [MHz] mag(V(V1)) [V] mag(V(V2)) [V] mag(V(V3)) [V] ang_deg(V(V1)) [deg] ang_deg(V(V2)) [deg] ang_deg(V(V3)) [deg]
0 10 19.342985 20.849054 15.290036 -10.644150 -9.283262 -9.870625
1 50 7.499412 12.522681 14.064961 -29.854300 -44.230084 168.423657
2 100 13.385809 9.828158 41.631139 -38.866437 -35.272131 102.384693
[15]:
pd.read_csv('circuit_vi_Designer_Currents.csv')
[15]:
Freq [MHz] mag(I(I1)) [A] mag(I(I2)) [A] mag(I(I3)) [A] ang_deg(I(I1)) [deg] ang_deg(I(I2)) [deg] ang_deg(I(I3)) [deg]
0 10 0.021481 0.137779 0.509421 156.253941 -90.892977 80.229605
1 50 0.274448 0.389875 0.619155 2.599727 6.286661 44.674195
2 100 0.219239 0.423718 0.381925 33.836727 -8.152957 -4.333328

With Circuit

Now let’s build the same circuit with scikit-rf Circuit:

[16]:
# Importing the 3-port .s3p file exported from full-wave simulation
coaxial_T = rf.Network('circuit_vi_Coaxial_T.s3p')
# pay attention to the port's characteristic impedance
# it should match the Network characteristic impedances otherwise this will generate mismatches
port1 = rf.Circuit.Port(coaxial_T.frequency, 'port1', coaxial_T.z0[:,0])
port2 = rf.Circuit.Port(coaxial_T.frequency, 'port2', coaxial_T.z0[:,1])
port3 = rf.Circuit.Port(coaxial_T.frequency, 'port3', coaxial_T.z0[:,2])
# connexion list
cnx = [
    [(port1, 0), (coaxial_T, 0)],
    [(port2, 0), (coaxial_T, 1)],
    [(port3, 0), (coaxial_T, 2)]
]
# building the circuit
crt = rf.Circuit(cnx)
[17]:
# let's check if our connexion list is correctly defined
crt.plot_graph(network_labels=True, edge_labels=True, inter_labels=True)
../../_images/examples_circuit_Voltages_and_Currents_in_Circuits_28_0.png

The voltages and currents at the ports for the given excitation is:

[18]:
power = [1, 2, 3]  # input power in watts at ports 1, 2 and 3
phase = np.deg2rad([-10, -20, +60])  # input phase in rad at ports 1, 2 and 3

voltages = crt.voltages_external(power, phase)
currents = crt.currents_external(power, phase)
[19]:
# just for a better rendering in the notebook
pd.concat([
    pd.DataFrame(np.abs(voltages), columns=['mag V1', 'mag V2', 'mag V3'], index=crt.frequency.f/1e6),
    pd.DataFrame(np.angle(voltages, deg=True), columns=['Phase V1', 'Phase V2', 'Phase V3'], index=crt.frequency.f/1e6)
], axis=1)
[19]:
mag V1 mag V2 mag V3 Phase V1 Phase V2 Phase V3
10.0 19.342535 20.848763 15.289548 -10.650167 -9.289472 -9.877400
50.0 7.499300 12.522020 14.064485 -29.855280 -44.233336 168.419396
100.0 13.384629 9.827209 41.628636 -38.869951 -35.275642 102.382983
[20]:
# just for a better rendering in the notebook
pd.concat([
    pd.DataFrame(np.abs(currents), columns=['mag I1', 'mag I2', 'mag I3'], index=crt.frequency.f/1e6),
    pd.DataFrame(np.angle(currents, deg=True), columns=['Phase I1', 'Phase I2', 'Phase I3'], index=crt.frequency.f/1e6)
], axis=1)
[20]:
mag I1 mag I2 mag I3 Phase I1 Phase I2 Phase I3
10.0 0.021471 0.137796 0.509429 156.241420 -90.898041 80.225072
50.0 0.274422 0.389839 0.619125 2.597008 6.284260 44.671994
100.0 0.219233 0.423683 0.381901 33.834350 -8.154641 -4.333857

These results matches well the one given by the full-wave calculations, hourra.

external vs internal ports?

You have maybe noticed in the Circuit documentation that we often talk about “internal” or “inner” ports, and of “external” or “outer” ports. The external ports corresponds to the Circuit.Port() Networks defined when building a Circuit. The internal ports are all the other connexions inside the Circuit

The Circuit algorithm allows one to have access to the voltages and current at the internal connexions inside the circuit. In the previous examples, there is not too much internal ports as we’ve connected a N-port directly to external ports. However, in more complex usages we can have a lot (look to the other Circuit examples).

In Circuit, voltages and currents are peak values. While voltages are defined in a non-ambiguous manner, positive currents can be defined in a way or another, leading to a 180 degree ambiguity. To solve this, we have chosen the following definition: internal currents are defined such as they are measured positive when flowing into Networks.

Hence, you find that “external” current are sign opposite of “internal” ones at the corresponding ports, because the internal currents are actually flowing into the ports Networks.

[21]:
# internals currents (currents at all connexions)
# in this example, there are 6 internal connexions (3 pairs of connexions)
crt.currents(power, phase)
[21]:
array([[ 0.0196517 -0.00865047j, -0.0196517 +0.00865047j,
         0.00215969+0.13777892j, -0.00215969-0.13777892j,
        -0.08649001-0.50203344j,  0.08649001+0.50203344j],
       [-0.27414049-0.0124343j ,  0.27414049+0.0124343j ,
        -0.38749668-0.04267229j,  0.38749668+0.04267229j,
        -0.44028632-0.43527388j,  0.44028632+0.43527388j],
       [-0.18210634-0.12206774j,  0.18210634+0.12206774j,
        -0.41939939+0.0600975j ,  0.41939939-0.0600975j ,
        -0.38080855+0.02885945j,  0.38080855-0.02885945j]])
[22]:
# This gives the indices of the "external" ports
crt.port_indexes
[22]:
[0, 2, 4]
[23]:
# So we can keep only "external" ports
crt.currents(power, phase)[:, crt.port_indexes]
[23]:
array([[ 0.0196517 -0.00865047j,  0.00215969+0.13777892j,
        -0.08649001-0.50203344j],
       [-0.27414049-0.0124343j , -0.38749668-0.04267229j,
        -0.44028632-0.43527388j],
       [-0.18210634-0.12206774j, -0.41939939+0.0600975j ,
        -0.38080855+0.02885945j]])
[24]:
# note the sign difference due to the convention chosen for internal ports
crt.currents_external(power, phase)
[24]:
array([[-0.0196517 +0.00865047j, -0.00215969-0.13777892j,
         0.08649001+0.50203344j],
       [ 0.27414049+0.0124343j ,  0.38749668+0.04267229j,
         0.44028632+0.43527388j],
       [ 0.18210634+0.12206774j,  0.41939939-0.0600975j ,
         0.38080855-0.02885945j]])

The figure below illustrates these differences using the previous example:

92ada1e6f2ca43f69a00ffc2fb7e904f

[ ]: