#!/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
from matplotlib import pyplot as plt
from copy import copy, deepcopy
import LogClass
#############################################################################
# constants
#############################################################################
_CLASSNAME = "BeamWeightData"
# Keywords for the beam-weight type (determination type)
_Types = {
"ND" : 'Not defined' ,
"CFM" : 'Conjungate field match' ,
"MD" : 'Maximum directivity' ,
"SNR" : 'Maximum S/N ratio (from noise Resistance)' ,
"MSNR" : 'Maximum S/N ratio (from lin. equations)'
}
#############################################################################
# classes
#############################################################################
[docs]
class EfficiencyData :
"""
Class with all efficiencies belonging to the beam-weights
"""
def __init__( self ) :
self.Eta_a = 1. # Aperture efficiency
self.Eta_spill = 1. # Spill-over efficiency
self.Eta_rad = 1. # Radiation efficiency
self.Eta_total = 1. # total efficiency
[docs]
def setEff ( self, ae = -1., sp = -1., rd = -1., to = -1.) :
if ae > 0. :
self.Eta_a = ae
if sp > 0. :
self.Eta_spill = sp
if rd > 0. :
self.Eta_rad = rd
if to > 0. :
self.Eta_total = to
[docs]
class NoiseData :
"""
Class with all noise and signal temperatures belonging to the beam-weights
"""
def __init__( self ) :
self.T_sig = 0. # Signal temperature [K]
self.T_sky = 0. # Sky noise contribution [K]
self.T_spill = 0. # Spill-over noise contribution [K]
self.T_loss = 0. # Loss noise contribution [K]
self.T_LNA = 0. # LNA noise temperature [K]
self.T_rec = 0. # Receiver noise temperature [K]
self.T_sys = 0. # system noise temperature [K]
self.T_sys_oe = 0. # T_sys over eta [K]
[docs]
def setTemp ( self, tsig = 0., tsky = 0., tspill = 0., tloss = 0., tlna = 0., trec = 0., tsys = 0., tsysoe = 0. ) :
if tsig > 0. :
self.T_sig = tsig
if tsky > 0. :
self.T_sky = tsky
if tspill > 0. :
self.T_spill = tspill
if tloss > 0. :
self.T_loss = tloss
if tlna > 0. :
self.T_lna = tlna
if trec > 0. :
self.T_rec = trec
if tsys > 0. :
self.T_sys = tsys
if tsysoe > 0. :
self.T_sys_oe = tsysoe
[docs]
class BeamWeights :
"""
Class to handle beam-weight data
"""
#####################################
# Keywords for the file
__KEYS = {
"Comment" : '#' ,
"FileType" : '# Beam data:' ,
"Wavelength" : 'Wavelength:' ,
"Theta" : 'Theta:' ,
"Phi" : 'Phi:' ,
"Type" : 'Type:' ,
"Eta" : 'Efficiencies:' ,
"Noise" : 'Noise' ,
"Data" : 'Element:'
}
#####################################
# internal data and init method
# init method
def __init__( self, log = None, wl = 214., t= 0., p = 0. ):
"""
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 = _Types['ND'] # Beam-weight type (determination algorithm)
self.Wavelength = wl # Wavelength at which the simulation was run [mm]
self.Theta = t # Theta angle of the pointing vector [deg]
self.Phi = p # Phi angle of the pointing vector [deg]
self.Efficiency = EfficiencyData() # actual efficiency values
self.Noise = NoiseData() # actual noise performance of the beam-weights
self.Data = [] # complex weights
[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)
#####################################
# File-IO methods
# write file
[docs]
def writeFile( self, filename):
"""
Method to write the beam-weight data to a file.
Parameters:
filename (string): Name of the file to be written.
"""
self.printOut("Writing File %s" % filename, self.log.FLOW_TYPE)
fh = open(filename, 'w') # open file for writing
# write header block
fh.write(self.__KEYS['Comment'] + " PAF-Simulator Beam-weight file\n" )
fh.write(self.__KEYS['Comment'] + " \n" )
fh.write(self.__KEYS['Comment'] + " File-Header\n" )
fh.write(self.__KEYS['FileType'] + "\n" )
fh.write(self.__KEYS['Comment'] + "# Above: type Identifier (only lines after Type-ID are parsed during reading\n" )
fh.write(self.__KEYS['Comment'] + " \n" )
fh.write(self.__KEYS['Type'] + " %s (algorithm used for calculation)\n" % self.Type)
fh.write(self.__KEYS['Comment'] + " Exitation data (Wavelength, I0 and input impedance)\n")
fh.write(self.__KEYS['Wavelength'] + " %.3f mm\n" % self.Wavelength )
fh.write(self.__KEYS['Theta'] + " %.5f [deg]\n" % self.Theta )
fh.write(self.__KEYS['Phi'] + " %.5f [deg]\n" % self.Phi )
fh.write(self.__KEYS['Comment'] + " \n" )
fh.write(self.__KEYS['Comment'] + " Performance data\n")
fh.write(self.__KEYS['Eta'] + " %.3f %.3f %.3f " % (self.Efficiency.Eta_a,
self.Efficiency.Eta_spill,
self.Efficiency.Eta_rad,) )
fh.write( "(Apperture efficiency, spill-over efficiency, radiation efficiency)\n")
fh.write(self.__KEYS['Noise'] + " %.3f %.3f %.3f %.3f %.3f %.3f %.3f " % (self.Noise.T_sig,
self.Noise.T_sky,
self.Noise.T_spill,
self.Noise.T_loss,
self.Noise.T_rec,
self.Noise.T_sys,
self.Noise.T_sys_oe))
fh.write( "(Tsig, Tsky, Tspill, Tloss, Trec, Tsys, Tsys/eta_a)\n")
fh.write(self.__KEYS['Comment'] + " Data-Block (Element-number amplitude phase):\n" )
# write data block
for element in range (0, len(self.Data)) :
fh.write(self.__KEYS['Data'] + " %d %.6f %.6f\n" % ( element,
np.absolute(self.Data[element]),
np.angle (self.Data[element]) ) )
# write end block
fh.write(self.__KEYS['Comment'] + " \n" )
fh.write(self.__KEYS['Comment'] + " end of file\n" )
fh.close()
[docs]
def readFile( self, filename) :
"""
Method to read an impedance matrix data file.
Parameters:
filename (string): Name of the input file
Returns:
idOK (boolean): True on success, False on error.
"""
Data = [] # preliminary data array
self.Data = [] # delete existing data
self.printOut("Reading File %s" % filename, self.log.FLOW_TYPE)
idOK = False
with open(filename, 'r') as f : # open file for reading
for line in f : # and loop all lines in the file
line = line.strip() # remove CR / NL / EOF from line end
columns = line.split() # split line
if ( len(columns) == 0) : continue
if line == self.__KEYS['FileType'] : # check for correct type-ID
idOK = True
if not idOK : continue # continue with next line if type-ID was not set so far
# Parse header line
if columns[0] == self.__KEYS['Type'] :
self.Type = columns[1]
continue
if columns[0] == self.__KEYS['Wavelength'] :
self.Wavelength = float(columns[1])
continue
if columns[0] == self.__KEYS['Theta'] :
self.Theta = float(columns[1])
continue
if columns[0] == self.__KEYS['Phi'] :
self.Phi = float(columns[1])
continue
if columns[0] == self.__KEYS['Eta'] :
self.Efficiency.setEff(ae = float(columns[1]), sp = float(columns[2]), rd = float(columns[3]))
continue
if columns[0] == self.__KEYS['Noise'] :
self.Noise.setTemp(tsig = float(columns[1]),
tsky = float(columns[2]),
tspill = float(columns[3]),
tloss = float(columns[4]),
trec = float(columns[5]),
tsys = float(columns[6]),
tsysoe = float(columns[7])
)
continue
# read beam-weight data
if columns[0] == self.__KEYS['Data'] :
elem = int(float(columns[1]))
amp = float(columns[2])
ph = float(columns[3])
Data.append([elem, amp*np.exp(1j*ph)])
f.close()
# sort data array
if idOK :
self.Data = np.zeros(len(Data), dtype = complex)
for el in Data :
self.Data[el[0]] = el[1]
else :
self.printOut(" Error while reading", self.log.ERROR_TYPE, False)
return idOK
#####################################
# plot data
[docs]
def plot( self, figname = "", show = False, freq = -1 ) :
"""
Plotting the Beam-weights.
Parameters:
figname (string): filename of the created plot
show (boolean): flag if plot is shown (True) or safed to disk
"""
self.printOut("Plotting Beam-Weights", self.log.FLOW_TYPE)
if freq < 0 :
f = ""
else :
f = "%.2fGHz : " % freq
fig, ax1 = plt.subplots()
plt.title("%s Beam-Weight data : " % (f + self.Type))
x = np.asarray(range(0, len(self.Data)+2))
amp = np.zeros(len(self.Data)+2)
amp[1:-1] = np.absolute(self.Data)
amp = amp / np.max(amp)
phase = np.zeros(len(self.Data)+2)
phase[1:-1] = np.angle(self.Data, deg=True)
ax1.step(x, amp, where='mid', color='blue')
plt.fill_between(x, amp, step="mid", alpha=0.6, color ='blue')
ax1.set_ylabel("relative Amplitude", color='blue')
ax1.set_xlabel("Element-Number")
for label in ax1.get_yticklabels():
label.set_color('blue')
ax2 = ax1.twinx()
ax2.set_ylim(-180., 180.)
ax2.step(x, phase, where='mid', color='red', linestyle='--')
ax2.set_ylabel("Phase [deg]", color='red')
for label in ax2.get_yticklabels():
label.set_color('red')
if figname != "" :
fig.savefig(figname)
if show :
plt.show()
else :
plt.show()
plt.close()
#####################################
# modify data
[docs]
def normalize(self, norm = 1. + 0*1j) :
self.printOut("Normalizing weights to %.3f %+.3fi" % (norm.real, norm.imag), self.log.FLOW_TYPE)
self.Data = np.asarray(self.Data)
n = np.sqrt(np.dot(self.Data, self.Data.conj()))
if n != 0. :
self.Data = self.Data *norm / n
return n