Download This Notebook: NetworkSet.ipynb

NetworkSet

Introduction

The NetworkSet object represents an unordered set of networks. It provides methods iterating and slicing the set, sorting by datetime, calculating statistical quantities, and displaying uncertainty bounds on plots.

Creating a NetworkSet

Lets take a look in the data/ folder, there are some redundant measurements of a network called ro, which is a radiating open waveguide.

ls data/ro*

-a----       14/02/2021     12:35           8031 ro,1.s1p
-a----       14/02/2021     12:35           8030 ro,2.s1p
-a----       14/02/2021     12:35           8031 ro,3.s1p
-a----       14/02/2021     12:35          46592 ro_spreadsheet.xls

The files ro,1.s1p , ro,2.s1p, … are redundant measurements on which we would like to calculate statistics using the NetworkSet class.

A NetworkSet is created from a list or dict of Network’s. So first we need to load all of the touchstone files into Networks. This can be done quickly with rf.read_all, The argument contains is used to load only files which match a given substring.

[1]:
import skrf as rf

rf.read_all(rf.data.pwd, contains='ro')
[1]:
{'ro,1': 1-Port Network: 'ro,1',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j],
 'ro,2': 1-Port Network: 'ro,2',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j],
 'ro,3': 1-Port Network: 'ro,3',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]}

This can be passed directly to the NetworkSet constructor,

[2]:
from skrf import NetworkSet

ro_dict = rf.read_all(rf.data.pwd, contains='ro')
ro_ns = NetworkSet(ro_dict, name='ro set')
ro_ns
[2]:
3-Networks NetworkSet: [1-Port Network: 'ro,1',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j], 1-Port Network: 'ro,2',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j], 1-Port Network: 'ro,3',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]]

A NetworkSet can also be constructed directly from: - a directory containing Touchstone files: NetworkSet.from_dir(), - a zipfile of touchstones files: NetworkSet.from_zip(), - a dictionnary of s-parameters: NetworkSet.from_s_dict(), - a (G)MDIF (.mdf) file: NetworkSet.from_mdif(), - a CITI (.cti) file: NetworkSet.from_citi().

Accessing Network Methods

The Network elements in a NetworkSet can be accessed like the elements of list,

[3]:
ro_ns[0]
[3]:
1-Port Network: 'ro,1',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]

Most Network methods are also methods of NetworkSet. These methods are called on each Network element individually. For example to plot the log-magnitude of the s-parameters of each Network.

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

ro_ns.plot_s_db()
[4]:
[None, None, None]
../_images/tutorials_NetworkSet_12_1.png

Statistical Properties

Statistical quantities can be calculated by accessing properties of the NetworkSet. To calculate the complex average of the set, access the mean_s property

[5]:
ro_ns.mean_s
[5]:
1-Port Network: 'ro set',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]

Note

Because the statistical operator methods are generated upon initialization their API is not explicitly documented in this manual.

The naming convention of the statistical operator properties are NetworkSet.{function}_{parameter}, where function is the name of the statistical function, and parameter is the Network parameter to operate on. These methods return a Network object, so they can be saved or plotted in the same way as you would with a Network. To plot the log-magnitude of the complex mean response

[6]:
ro_ns.mean_s.plot_s_db(label='ro')
../_images/tutorials_NetworkSet_17_0.png

Or to plot the standard deviation of the complex s-parameters,

[7]:
ro_ns.std_s.plot_s_re(y_label='Standard Deviations')
../_images/tutorials_NetworkSet_19_0.png

Using these properties it is possible to calculate statistical quantities on the scalar components of the complex network parameters. To calculate the mean of the phase component,

[8]:
ro_ns.mean_s_deg.plot_s_re()
../_images/tutorials_NetworkSet_21_0.png

Plotting Uncertainty Bounds

Uncertainty bounds can be plotted through the methods

[9]:
ro_ns.plot_uncertainty_bounds_s_db()
../_images/tutorials_NetworkSet_23_0.png
[10]:
ro_ns.plot_uncertainty_bounds_s_deg()
../_images/tutorials_NetworkSet_24_0.png

Note

The uncertainty bounds plotted above are calculated after the complex number has been projected onto the specified scalar component. Thus, the first plot represents uncertainty in the magnitude component only.

Reading and Writing

To write all Networks of a NetworkSet out to individual touchstones,

[11]:
ro_ns.write_touchstone(dir='data/')
[11]:
[None, None, None]

For temporary data storage, NetworkSets can be saved and read from disk using the functions rf.read and rf.write

[12]:
rf.write('ro set.ns', ro_ns)
[13]:
ro_ns = rf.read('ro set.ns')
ro_ns
[13]:
3-Networks NetworkSet: [1-Port Network: 'ro,1',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j], 1-Port Network: 'ro,2',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j], 1-Port Network: 'ro,3',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]]

Export to Excel, csv, or html

NetworkSets can also be exported to other filetypes. The format of the output; real/imag, mag/phase is adjustable, as is the output type; csv, excel, html. For example to export mag/phase for each network into an Excel spreadsheet for your boss[s]

[14]:
ro_ns.write_spreadsheet('data/ro_spreadsheet.xls', form='db')
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In [14], line 1
----> 1 ro_ns.write_spreadsheet('data/ro_spreadsheet.xls', form='db')

File ~/checkouts/readthedocs.org/user_builds/scikit-rf/envs/v0.24.0/lib/python3.10/site-packages/skrf/networkSet.py:935, in NetworkSet.write_spreadsheet(self, *args, **kwargs)
    921 """
    922 Write contents of network to a spreadsheet, for your boss to use.
    923
   (...)
    932
    933 """
    934 from . io.general import networkset_2_spreadsheet
--> 935 networkset_2_spreadsheet(self, *args, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/scikit-rf/envs/v0.24.0/lib/python3.10/site-packages/skrf/io/general.py:797, in networkset_2_spreadsheet(ntwkset, file_name, file_type, *args, **kwargs)
    795     if not file_name.endswith('.xlsx'):
    796         file_name += '.xlsx'
--> 797     with ExcelWriter(file_name) as writer:
    798         [network_2_spreadsheet(k, writer, sheet_name=k.name, *args, **kwargs) for k in ntwkset]
    799 else:

File ~/checkouts/readthedocs.org/user_builds/scikit-rf/envs/v0.24.0/lib/python3.10/site-packages/pandas/io/excel/_openpyxl.py:56, in OpenpyxlWriter.__init__(self, path, engine, date_format, datetime_format, mode, storage_options, if_sheet_exists, engine_kwargs, **kwargs)
     43 def __init__(
     44     self,
     45     path: FilePath | WriteExcelBuffer | ExcelWriter,
   (...)
     54 ) -> None:
     55     # Use the openpyxl module as the Excel writer.
---> 56     from openpyxl.workbook import Workbook
     58     engine_kwargs = combine_kwargs(engine_kwargs, kwargs)
     60     super().__init__(
     61         path,
     62         mode=mode,
   (...)
     65         engine_kwargs=engine_kwargs,
     66     )

ModuleNotFoundError: No module named 'openpyxl'

More info on this can be found in the function, skrf.io.general.network_2_spreadsheet

Named Parameters

If all the Network objects of a NetworkSet have a params property containing a dictionnary of the named parameters and values associated to each Network, it is possible to select the Networks corresponding to a subset of named parameters using the .sel() method.

The following example illustrates this feature.

[15]:
# dummy named parameters and values 'a', 'X' and 'c'
import numpy as np

params = [
        {'a':0, 'X':10, 'c':'A'},
        {'a':1, 'X':10, 'c':'A'},
        {'a':2, 'X':10, 'c':'A'},
        {'a':1, 'X':20, 'c':'A'},
        {'a':0, 'X':20, 'c':'A'},
        ]
# create a NetworkSet made of dummy Networks, each define for set of parameters
freq1 = rf.Frequency(75, 110, 101, 'ghz')
ntwks_params = [rf.Network(frequency=freq1, s=np.random.rand(len(freq1),2,2),
                               name=f'ntwk_{m}', comment=f'ntwk_{m}', params=params)
                            for (m, params) in enumerate(params) ]
ns = rf.NetworkSet(ntwks_params)
print(ns)
5-Networks NetworkSet: [2-Port Network: 'ntwk_0',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_1',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_2',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_3',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_4',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]]

Selecting the sub-NetworkSet matching scalar parameters can be made as:

[16]:
ns.sel({'a': 1})
[16]:
2-Networks NetworkSet: [2-Port Network: 'ntwk_1',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_3',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]]
[17]:
ns.sel({'a': 0, 'X': 10})
[17]:
1-Networks NetworkSet: [2-Port Network: 'ntwk_0',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]]

Selecting the sub-NetworkSet matching a range of parameters also works:

[18]:
ns.sel({'a': 0, 'X': [10,20]})
[18]:
2-Networks NetworkSet: [2-Port Network: 'ntwk_0',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_4',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]]
[19]:
ns.sel({'a': [0,1], 'X': [10,20]})
[19]:
4-Networks NetworkSet: [2-Port Network: 'ntwk_0',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_1',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_3',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j], 2-Port Network: 'ntwk_4',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]]

The various named parameter keys and values of the NetworkSet can be retrieved using the dims and coords properties:

[20]:
ns.dims
[20]:
dict_keys(['a', 'X', 'c'])
[21]:
ns.coords
[21]:
{'a': [0, 1, 2], 'X': [10, 20], 'c': ['A']}

Interpolating between the Networks of a NetworkSet

It is possible to create new Networks interpolated from the Networks contained in a NetworkSet. If no params properties have been defined for each Network of the NetworkSet, the interpolate_from_network() method can be used to specify a interpolating parameter.

[22]:
param_x = [1, 2, 3]  # a parameter associated to each Network
x0 = 1.5  # parameter value to interpolate for
interp_ntwk = ro_ns.interpolate_from_network(param_x, x0)
print(interp_ntwk)
1-Port Network: 'ro,1',  500.0-750.0 GHz, 201 pts, z0=[50.+0.j]

An illustrated example is given in the Examples section of the documentation.

It is also possible to interpolate using a named parameter when they have been defined:

[23]:
# Interpolated Network for a=1.2 within X=10 Networks:
ns.interpolate_from_params('a', 1.2, {'X': 10})
[23]:
2-Port Network: 'ntwk_0',  75.0-110.0 GHz, 101 pts, z0=[50.+0.j 50.+0.j]