Sorting Network Sets

Frequently a set of Networks is recorded while changing some other variable; like voltage, or current or time. So… now you have this set of data and you want to look at how some feature evolves, or calculate some representative statics. This example demonstrates how to do this using NetworkSets.

Generate some Data

For the purpose of this example we use a predefined skrf.Media object to generate some networks, and save them as a series of touchstone files. Each file is named with a timestamp, generated with the convenience function rf.now_string().

[1]:
from time import sleep
import skrf as rf
%matplotlib inline
from pylab import *
rf.stylely()


!rm -rf tmp
!mkdir tmp

wg = rf.wr10 # just a dummy media object to generate data

for k in range(10):
    # timestamp generated with `rf.now_string()`
    ntwk = wg.random(name=rf.now_string()+'.s1p')
    ntwk.s = k*ntwk.s
    ntwk.write_touchstone(dir='tmp')
    sleep(.1)

Lets take a look at what we made

[2]:
ls tmp
2024.03.18.13.17.25.594757.s1p  2024.03.18.13.17.26.137735.s1p
2024.03.18.13.17.25.702739.s1p  2024.03.18.13.17.26.246606.s1p
2024.03.18.13.17.25.811496.s1p  2024.03.18.13.17.26.355220.s1p
2024.03.18.13.17.25.920228.s1p  2024.03.18.13.17.26.464372.s1p
2024.03.18.13.17.26.028829.s1p  2024.03.18.13.17.26.572985.s1p

Not sorted (default)

When created using NetworkSet.from_dir(), the NetworkSet’s stores each Network randomly

[3]:
ns = rf.NS.from_dir('tmp')
ns.ntwk_set
[3]:
[1-Port Network: '2024.03.18.13.17.25.594757',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.702739',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.811496',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.920228',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.028829',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.137735',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.246606',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.355220',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.464372',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.572985',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j]]

Sort it

[4]:
ns.sort()
ns.ntwk_set
[4]:
[1-Port Network: '2024.03.18.13.17.25.594757',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.702739',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.811496',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.920228',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.028829',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.137735',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.246606',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.355220',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.464372',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.572985',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j]]

Sorting using key argument

You can also pass a function through the key argument, which allows you to sort on arbitrary properties. For example, we could sort based on the sub-second field of the name,

[5]:
ns = rf.NetworkSet.from_dir('tmp')
ns.sort(key = lambda x: x.name.split('.')[0])
ns.ntwk_set
[5]:
[1-Port Network: '2024.03.18.13.17.25.594757',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.702739',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.811496',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.25.920228',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.028829',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.137735',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.246606',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.355220',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.464372',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j],
 1-Port Network: '2024.03.18.13.17.26.572985',  75.0-110.0 GHz, 1001 pts, z0=[50.+0.j]]

Extracting Datetimes

You can also convert the ntwk names to datetime objects, in case you want to plot something with pandas or do some other processing. There is a companion function to rf.now_string() which is rf.now_string_2_dt(). How creative..

[6]:
ns.sort()
dt_idx = [rf.now_string_2_dt(k.name ) for k in ns]
dt_idx
[6]:
[datetime.datetime(2024, 3, 18, 13, 17, 25, 594757),
 datetime.datetime(2024, 3, 18, 13, 17, 25, 702739),
 datetime.datetime(2024, 3, 18, 13, 17, 25, 811496),
 datetime.datetime(2024, 3, 18, 13, 17, 25, 920228),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 28829),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 137735),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 246606),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 355220),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 464372),
 datetime.datetime(2024, 3, 18, 13, 17, 26, 572985)]

Put into a Pandas DataFrame and Plot

The next step is to slice the network set along the time axis. For example we may want to look at S11 phase, at a few different frequencies. This can be done with the following script. Note that NetworkSets can be sliced by frequency with human readable strings, just like Networks.

[7]:
import pandas as pd
dates = pd.DatetimeIndex(dt_idx)

# create a function to pull out S11 in degrees at a specific frequency

s_deg_at = lambda s:{s: [k[s].s_deg[0,0,0] for k in ns]}

for f in ['80ghz', '90ghz','100ghz']:
    df =pd.DataFrame(s_deg_at(f), index=dates)
    df.plot(ax=gca())
title('Phase Evolution in Time')
ylabel('S11 (deg)')
[7]:
Text(0, 0.5, 'S11 (deg)')
../../_images/examples_networksets_Sorting_Network_Sets_20_1.png

Visualizing Behavior with signature

It may be of use to visualize the evolution of a scalar component of the network set over all frequencies. This can be done with a little bit of array manipulation and imshow. For example if we take the magnitude in dB for each network, and create 2D matrix from this,

[8]:
mat = array([k.s_db.flatten() for k in ns])
mat.shape
/home/docs/checkouts/readthedocs.org/user_builds/scikit-rf/envs/latest/lib/python3.10/site-packages/skrf/mathFunctions.py:268: RuntimeWarning: divide by zero encountered in log10
  out = 20 * npy.log10(z)
[8]:
(10, 1001)

This array has shape ( ‘Number of Networks’ , ‘Number frequency points’). This can be visualized with imshow. Most of the code below just adds labels, and axis-scales.

[9]:
freq = ns[0].frequency

# creates x and y scales
extent = [freq.f_scaled[0], freq.f_scaled[-1], len(ns) ,0]

#make the image
imshow(mat, aspect='auto',extent=extent,interpolation='nearest')

# label things
grid(0)
freq.labelXAxis()
ylabel('Network #')
cbar = colorbar()
cbar.set_label('Magnitude (dB)')
../../_images/examples_networksets_Sorting_Network_Sets_25_0.png

This process is automated with the method NetworkSet.signature(). It even has a vs_time parameter which will automatically create the DateTime index from the Network’s names, if they were written by rf.now_string()

[10]:
ns.signature(component='s_db', vs_time=True,cbar_label='Magnitude (dB)')
../../_images/examples_networksets_Sorting_Network_Sets_27_0.png