Source code for src.Libs.Simulation.SnPDataClass

#!/usr/bin/env python3
"""
This file is part of the PAFFrontendSim.

The PAFFrontendSim is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation.

The PAFFrontendSim is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with the PAFFrontendSim. If not, see https://www.gnu.org/licenses/.
"""
#############################################################################
# import libs
#############################################################################
import numpy as np
import matplotlib.pyplot        as plt
from   copy                     import  deepcopy

import LogClass
from Simulation               import ImpedanceMatrixDataClass
from Simulation.constants     import _C

#############################################################################
# constants
#############################################################################
_CLASSNAME = "S-Parameter"

 #############################################################################
# classes
#############################################################################       
[docs] class SnPData : """ Class to read LNA data input files. The expected format is a Touchstone file for a 2 port network (.s2p). The data must be blocks of S-Parameters (9 columns) and/or Noise parameters (5 columns). The header of the file must specify the units as well as the impedance ("# ghz S db R 50" as an example). """ ##################################### # internal data and init method ##################################### # Keywords for the file __UNITS = { "HZ" : 1. , "KHZ" : 1.e3 , "MHZ" : 1.e6 , "GHZ" : 1.e9 } # init method def __init__ ( self, log = None): """ Initialize class. """ if log == None: self.log = LogClass.LogClass(_CLASSNAME) else: self.log = deepcopy(log) # logging class used for all in and output messages self.log.addLabel(self) self.Type = "S" # Type of the data (S, Y, Z, H, G) self.noPorts = 2 # number of ports self.RefR = 50. # reference resistance self.Param = [] # array with arrays of s-parameter values (matrix wise) self.freq = [] # array of belonging frequencies self.Noise = [] # noise data arra with arrays of # [min Tnoise, mag. S11_min_noise, angle S11_min_noise, noise Resistance/Rimpedance]
[docs] def printOut ( self, line, message_type, noSpace = True, lineFeed = True ) : """ Wrapper for the printOut method of the actual log class """ self.log.printOut(line, message_type, noSpace, lineFeed)
[docs] def readSnP ( self, filename ) : """ Method to read an TOUCHSTONE data file Parameters: filename : Name of the input file Returns error flag (true on success, false on error) """ def _extractData(allCols, setField): """ extract data from string array to matrix using the appropriate conversion function setField """ if len(allCols) > 1: self.printOut("Starting data-point %d" % len(self.Param), self.log.DEBUG_TYPE, False, False) freq = float(allCols[0]) # create and extract S-parameter matrix m = np.zeros((self.noPorts, self.noPorts), dtype = np.complex128) for p_idx in range(1, self.noPorts**2*2 +1, 2) : y = int(0.5*(p_idx-1) / self.noPorts) x = int(0.5*(p_idx-1) - y*self.noPorts) m[x,y] = setField(allCols[p_idx], allCols[p_idx+1]) self.Param.append(m) self.freq.append(freq) self.Param = [] # delete existing data self.freq = [] self.Noise = [] freqN = [] self.noPorts = 0 line = 0. self.printOut("Reading TOUCHSTONE File %s" % filename, self.log.FLOW_TYPE) getData = False # flag that the data body is reached getNoise = False # flag that data comming are noise data allCols = [] lastLength = -1 # format conversion fuction setField = lambda x,y : x*np.exp(1j*np.pi*y/180.) with open(filename, 'r', encoding='latin-1') as fh : # open file for reading for line in fh : # and loop all lines in the file line = line.strip() # remove CR / NL / EOF from line end columns = line.split() # split line if len(line) > 0 : if line[0] == '#' : # Read option line self.printOut(" Using the following data to interprete the file contend:", self.log.DATA_TYPE, False) getData = True # start data taking flag # establish unit conversion to GHz if len(columns) < 2 : ffscale = 1/1.e9 self.printOut(" Frequency units : Hz (default)", self.log.DATA_TYPE, False) else : ffscale = self.__UNITS.get(columns[1].upper(), 1.)/1.e9 self.printOut(" Frequency units : %s" % columns[1], self.log.DATA_TYPE, False) #selct data type (default: Magnitude) if len(columns) < 3 : self.Type = "S" self.printOut(" Data-Type : S (default)", self.log.DATA_TYPE, False) else : self.Type = columns[2].upper() self.printOut(" Data-Type : %s" % self.Type, self.log.DATA_TYPE, False) # select format conversion fuction if len(columns) > 3 : if columns[3].upper() == "DB" : setField = lambda x,y : (10**(x/20.))*np.exp(1j*np.pi*y/180.) self.printOut(" Data-Format : DB power", self.log.DATA_TYPE, False) elif columns[3].upper == "RI" : setField = lambda x, y : x+ 1j*y self.printOut(" Data-Format : Real / Imaginaty", self.log.DATA_TYPE, False) else : setField = lambda x,y : x*np.exp(1j*np.pi*y/180.) self.printOut(" Data-Format : Magnitude Angle", self.log.DATA_TYPE, False) else : self.printOut(" Data-Format : Magnitude Angle (default)", self.log.DATA_TYPE, False) # get reference impedance self.RefR = 50. if len(columns) > 5 : self.RefR = float(columns[5]) self.printOut(" Reference Impedance : %.1f Ohm" % self.RefR, self.log.DATA_TYPE, False) else : self.printOut(" Reference Impedance : 50 Ohm (default)", self.log.DATA_TYPE, False) continue if line[0] == '!' : line = "" #continue # comment if not getData : continue # filter '!' sign cols = [] for c in columns : if c[0] == '!' : break cols.append(float(c)) # getting noise data ? if getNoise == True : if len(freqN) == 0: freqN.append(float(allCols[0])) self.Noise.append([float(allCols[1]), float(allCols[2])*np.exp(1j*float(allCols[3])/180.*np.pi), float(allCols[4])]) freqN.append(float(cols[0])) self.Noise.append([float(cols[1]), float(cols[2])*np.exp(1j*float(cols[3])/180.*np.pi), float(cols[4])]) continue if (len(cols) <= lastLength-1) and ( len(cols) > 0) : allCols += cols continue # detect first line if lastLength < 0 : lastLength = len(cols) allCols = cols continue # determine number of ports if (len(allCols) > 0) and ( self.noPorts == 0) : # set number of ports self.noPorts = int(np.sqrt((len(allCols) - 1)/2.)) self.printOut("Found %d ports" % self.noPorts, self.log.DEBUG_TYPE, False) #extract data-point _extractData(allCols, setField) allCols = cols lastLength = len(cols) # check if noise data are starting if (len(self.freq) > 0) and (len(cols) > 0) and (self.freq[-1] > float(cols[0])) : getNoise = True if not getNoise : _extractData(allCols, setField) self.freq = np.asarray(self.freq) * ffscale fh.close() self.printOut("", self.log.DEBUG_TYPE, False) # interpolate frequency axis if len(freqN) < 2 : return getData freqN = np.asarray(freqN) * ffscale Noise = np.zeros((len(self.freq), 3), dtype = complex) self.Noise = np.asarray(self.Noise) Noise[:,0] = np.interp(self.freq, freqN, self.Noise[:,0]) Noise[:,1] = np.interp(self.freq, freqN, self.Noise[:,1]) Noise[:,2] = np.interp(self.freq, freqN, self.Noise[:,2]) self.Noise = Noise return getData
[docs] def export2ImpedanceMatrix ( self, frequency = -1.) : """ exports the s-parameters as impedance matrix """ # get index of closest frequency in list idx = np.argmin(np.abs(self.freq - frequency)) bw = 1000000. if len(self.freq) > 1 : bw = (self.freq[1]-self.freq[0])*1e9 M = ImpedanceMatrixDataClass.ImpedanceMatrix(wl = _C/self.freq[idx]*1e-6, z= self.RefR, bw = bw, log = self.log) M.Data = self.Param[idx]**2. M.noElements = self.noPorts return M