Source code for src.Libs.Simulation.CSTDataClass

#!/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 sys
import argparse
import numpy                 as np
import matplotlib.pyplot     as plt
from   copy                  import copy, deepcopy

import LogClass
from Simulation           import FarFieldDataClass
from Simulation.constants import _CSTInstallationPath, _CSTProjectPath

# import CST
sys.path.append(_CSTInstallationPath + "/LinuxAMD64/python_cst_libraries/cst/")  # TODO this might be outdated
try :
    import interface     as csti
except :
    print("No CST-interface, installation path should be :", _CSTInstallationPath)
    csti = None
try:
    import results       as cstr
except :
    print("No CST results, installation path should be :", _CSTInstallationPath)
    cstr = None

# TODO separate method 2 (reading CST project data) into its own method

#############################################################################
# constants
#############################################################################
_CLASSNAME   = "CSTData"

#############################################################################
# Class definition
#############################################################################
[docs] class CST : """ This module provides two methods to read farfield data generated by CST (https://www.3ds.com/products/simulia/cst-studio-suite). Method 1 (readFarFieldSources): Reading farfield files (.ffs) exported by CST This method requires no additional libraries and no local installation of the CST suite. It does require .ffs-files. CST has an export feature that allows farfield data to be stored in said format. Method 2: Using the official CST Python library to access project files (.cst) An installation of CST contains Python libraries which are able to read the proprietary project files. This requires a local installation of CST and the project files containing your desired farfield data. Be aware that the Python libraries provided by CST only support specific Python versions. Make sure that you run this module with a supported version of Python when using the official CST Python library. The PAF Frontend simulator requires some form of farfield data, though it doesn't have to be generated by CST. For this reason, this module is optional and internal methods to generate farfield data may be used instead (see FarFieldDataClass.py). """ def __init__ ( self, log = None, project = "" ) : """ Initialize class. Parameters : project (string): Name of the CST project (folder name within the _CSTProjectPath folder). If empty, an empty object is created. log (LogClass): Logging class to be used """ 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.printOut("Initializing CST data-access :", self.log.FLOW_TYPE) if cstr == None : self.printOut("no CST-Lib present!", self.log.WARNING_TYPE, False) self.printOut("actual CST installation path : %s" % _CSTInstallationPath, self.log.DEBUG_TYPE, False) self.printOut("actual CST project path : %s" % _CSTProjectPath, self.log.DEBUG_TYPE, False) self.printOut("opening CST project : %s" % project, self.log.DEBUG_TYPE, False) self.folder = _CSTProjectPath + project try : self.ItemList = self._getItemList() # List of all electrical data available except : self.ItemList = [] self.printOut("found %d data-sets in 3D results" % len(self.ItemList), self.log.DEBUG_TYPE, False) # extract reference impedance self.Impedance = -1. ZRef = self._getItem("ZRef 1") if ZRef != None : zref = ZRef.get_data() if len(zref [0]) > 1 : self.Impedance = np.absolute(zref[0][1]) self.printOut("extracted a reference impedance of %.1f Ohm" % self.Impedance, self.log.DEBUG_TYPE, False) if self.Impedance < 0 : self.Impedance = 50. self.printOut("no reference impedance to extract, using %.1f Ohm" % self.Impedance, self.log.DEBUG_TYPE, False)
[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)
############################################################################# # 1D results ############################################################################# def _getItemList ( self ) : """ Returns all 1D results available for this project. """ self.printOut("Getting item list of CST-Project", self.log.FLOW_TYPE) if cstr == None : return [] # get all items from item tree project = cstr.ProjectFile(self.folder + ".cst", allow_interactive= True) results3D = project.get_3d() # 3D-results available items = results3D.get_tree_items() iList = [] # filter item tree for electrical and field data for i in items : columns = i.split("\\") if ( (len(columns) > 1) and ( columns[1] != "Optimizer") and ( columns[1] != "Materials") and ( columns[1] != "Port Information") and ( columns[1] != "Adaptive Meshing") ) : iList.append(i) #self.printOut(" found item : %s" % i, False) return iList def _getItem ( self, itemName ) : """ get the data-set of the item with name 'itemName' Parameters : itemName : full or part name of the item (first occurance of the string in the list will be returned). Returns : cst.results.ResultItem or None """ # search for itemName for i in self.ItemList : if i.find(itemName) > -1 : project = cstr.ProjectFile(self.folder + ".cst", allow_interactive= True) return project.get_3d().get_result_item(i) return None
[docs] def getItemData ( self, itemName, minX = None, maxX = None ) : """ Get the data of the item with name 'itemName'. Parameters: itemName (string): Full or part name of the item (first occurance of the string in the list will be returned). Returns: List (list): of x-data, y-data, plot-title, x-axis label, y-axis label or None """ result = self._getItem(itemName) if result == None: return None xdata = result.get_xdata() ydata = result.get_ydata() xfiltered = [] yfiltered = [] if minX == None : minX = np.amin(xdata) - 1. if maxX == None : maxX = np.amax(xdata) + 1. for idx, x in enumerate(xdata) : if ( x >= minX ) and ( x <= maxX ) : xfiltered.append(x) yfiltered.append(ydata[idx ]) return [xfiltered, yfiltered, result.title, result.xlabel, result.ylabel]
[docs] def plotItem ( self, itemName, unit = "Mag", figname = "", show = False ) : """ Plot the data of the item with name 'itemName'. Breakdown of the units: "Mag" : using a linear scale of the magnutude of the value (if complex, amplitude is used) "dB" : using dB scale (power) of the magnutude of the value (if complex, amplitude is used) "Phase" : using the phase (only for complex y-data) Parameters: itemName (string): Full or part name of the item (first occurance of the string in the list will be returned). unit (string): Identifier to switch between amplitude, phase and liner / dB scale figname (string): File-name of the figure (if given figure is only shown on screen if show == True show (boolean): Flag to show plot on screen even when being written to disk (True) """ self.printOut("Plotting data set belonging to item '%s'" % itemName, self.log.FLOW_TYPE) data = self.getItemData ( itemName ) if data == None : self.printOut("item with name '%s' does not exist" % itemName, self.log.ERROR_TYPE, False) return # transform result to numpy arrays data[0] = np.asarray(data[0]) data[1] = np.asarray(data[1]) # check for complex numbers and use the absolute value in case if isinstance(data[1][0], complex) or isinstance(data[1][0], np._complex) : if unit == "Phase" : data[1] = np.unwrap(np.angle(data[1]))*180./np.pi data[4] = "Phase [deg]" else : data[1] = np.absolute(data[1]) if unit == "dB" : data[1] = np.log10(data[1])*20. data[4] = "power [dB]" fig, ax = plt.subplots() ax.plot(data[0], data[1], 'k-') ax.set_title (data[2]) ax.set_xlabel(data[3]) ax.set_ylabel(data[4]) if figname != "" : fig.savefig(figname) if show : plt.show() else : plt.show() plt.close()
############################################################################# # 3D results #############################################################################
[docs] def readFarFieldSources ( self, cstImpedance = -1) : """ Method to read a CST farfield source file (.ffs). Currently, each farfield source file must contain only one frequency. Parameters: cstImpedance (int): Override CST simulation reference impedance (negative, use simulation setting) Returns: dataValid (boolean): error flag (true on success, false on error) FarFields (list): list of far-field data classes """ self.printOut("Reading Far-Field Source data", self.log.FLOW_TYPE) if cstImpedance <= 0. : cstImpedance = self.Impedance else : self.printOut("overriding reference impedance with %.1f Ohm" % cstImpedance, self.log.DEBUG_TYPE, False) FarFields = [] # delete all data dataValid = False # data valid flag lcount = 0 # line-counter lnFreq = -1 # line with the number of frequency packages lExp = -10 # line with experimental data (Power, frequency) lNum = -10 # line with number of elements data lCenter = -10 # line with center position lzAxis = -10 # line with z-axis data lxAxis = -10 # line with x-axis data nPhi = 0 # number of Phi values nTh = 0 # number of Theta values dataSet = -1 # actual data set freqList = [] with open(self.folder + "/Result/Farfield Source [1].ffs" , 'r') as f : # open file for line in f : # and loop all lines in the file line = line.strip() # remove CR / NL / EOF from line end if line == "// #Frequencies" : lnFreq = lcount + 1 dataValid = False if line == "// Radiated/Accepted/Stimulated Power , Frequency" : # parse line contend for experimental data lExp = lcount # set line-number of the frequency entry dataValid = False self.printOut("reading individal setting for each frequency", self.log.DEBUG_TYPE, False) if line == "// Position" : # parse line contend for center position lCenter = lcount+1 # set line-number of the center position dataValid = False if line == "// xAxis" : # parse line contend for center position lxAxis = lcount+1 # set line-number of the center position dataValid = False if line == "// zAxis" : # parse line contend for center position lzAxis = lcount+1 # set line-number of the center position dataValid = False if line == "// >> Total #phi samples, total #theta samples" : # parse line contend for number of Phi & Theta values lNum = lcount + 1 # set line number of the sampling data dataValid = False if line == "// >> Phi, Theta, Re(E_Theta), Im(E_Theta), Re(E_Phi), Im(E_Phi):" : # parse line contend for start of the data block dataValid = True # indicate start of the data-block dataSet += 1 self.printOut("reading data set for frequency %.1f GHz" % freqList[dataSet], self.log.DEBUG_TYPE, False) for i in range(nPhi): # pre-define 2d-array row = [] for j in range(nTh): row.append([]) FarFields[dataSet].FarField.append(row) if dataValid : # if data are valid columns = line.split() # extract values if ( len(columns) == 0 ) or (columns[0] == "//") : continue c0 = float(columns[0]) c1 = float(columns[1]) iPh = int ( (nPhi-1)*float(c0)/360.) # calculate array index for Phi and Theta iTh = int ( (nTh -1)*float(c1)/180.) FarFields[dataSet].FarField[iPh][iTh] = [ c0, c1, # compose complex Ephi and Eth-data (float(columns[4])+1j*float(columns[5])) , # (Eth comes first in the data-line, but array (float(columns[2])+1j*float(columns[3])) ] # is [Phi, Theta, Ephi, Eth]!) # extract number of frequenciy data sets in the file if lcount == lnFreq : columns = line.split() nF = int(columns[0]) self.printOut("found %d frequency settings" % nF, self.log.DEBUG_TYPE, False) for f_idx in range(0, nF) : FarFields.append(FarFieldDataClass.FarFieldData( log = self.log )) # extract experimental data if (lExp > 0) and (lcount > lExp) and (lcount < lExp+5*len(FarFields)) : # at correct line-number extract experimental data f_idx = int((lcount-lExp) / 5) columns = line.split() if (lcount - lExp) % 5 == 1 : FarFields[f_idx].S21 = float(columns[0]) # extract S21 if (lcount - lExp) % 5 == 2 : FarFields[f_idx].S11 = float(columns[0]) # extract S11 if (lcount - lExp) % 5 == 3 : FarFields[f_idx].I0 = np.sqrt(2.*float(columns[0])/cstImpedance) # extract I0 FarFields[f_idx].S21 = np.sqrt(FarFields[f_idx].S21 / float(columns[0])) FarFields[f_idx].S11 = np.sqrt(1. - FarFields[f_idx].S11 / float(columns[0])) if (lcount - lExp) % 5 == 4 : freq = float(columns[0])/1e9 FarFields[f_idx].Wavelength = _C / float(columns[0]) * 1000. # or Wavelength in mm freqList.append(freq) self.printOut("found data for %.1f GHz" % freq, self.log.DEBUG_TYPE, False) # extract number of data-points if lcount == lNum : # at correct line-number extract array dimensions columns = line.split() nPhi = int(columns[0]) nTh = int(columns[1]) # extract center position if lcount == lCenter : # at correct line-number extract center positon columns = line.split() PhaseCenter = [ float(columns[0])*1000., float(columns[1])*1000., float(columns[2])*1000. ] for ff in FarFields : ff.PhaseCenter = PhaseCenter # extract x-axis orientation if lcount == lxAxis : # at correct line-number extract x-axis columns = line.split() OrientationX = [ float(columns[0]), float(columns[1]), float(columns[2]) ] for ff in FarFields : ff.OrientationX = OrientationX # extract z-axis orientation if lcount == lzAxis : # at correct line-number extract z-axis columns = line.split() OrientationZ = [ float(columns[0]), float(columns[1]), float(columns[2]) ] for ff in FarFields : ff.OrientationZ = OrientationZ lcount += 1 # increment line-counter f.close() # set internal variables of the FarFields for ff in FarFields : ff.moveCenter (0., 0., 0.) # absolute center not given, move it to zero ff.movePhaseCenter(0., 0., 0.) ff.ReferenceDist = 1000. # reference distance in CST is 1m ff.Gain = 1. ff.Phase = 0. ff.Bandwidth = 1000000. # default CST calculation bandwidth (1MHz) ff.Type = 'CST' # set calculation type return dataValid, FarFields
############################################################################# # Main function; # ############################################################################# if __name__=="__main__": pass # end