├── sim ├── README.txt ├── directivity_patch_analytical.txt └── directivity_patch_sim.txt ├── results ├── README.txt └── directivity_steering_sweep_5x5_array.txt ├── requirements.txt ├── stk_scripts ├── stk_generate_antenna_file.py ├── stk_sat2sat_AER.py ├── stk_linkbudget_access_avg.py ├── stk_linkbudget_plots.py ├── stk_sat2sat_comparison_fixed_paa.py ├── stk_attitude_noise.py ├── stk_datarate_sweep_plots.py └── stk_attitude_noise_sweep.py ├── antenna_toolbox ├── array_analysis.py ├── full_paa_comparison.py ├── elementPhasePlot.py ├── rect_patch_comparison.py ├── sweep_steering_angle.py ├── RectangularPatch.py ├── RectangularArray.py └── Antenna.py ├── README.md ├── arraysize_vs_frequency_plot.py ├── antenna_design_s_parameter_plots.py ├── util.py ├── plotCSTHeatmap.py ├── quantization_error_phase_plot.py └── CSTResultLoader.py /sim/README.txt: -------------------------------------------------------------------------------- 1 | # Miscellaneous Simulation Data 2 | 3 | This folder contains some miscellaneous CST simulation results that are required by the scripts. -------------------------------------------------------------------------------- /results/README.txt: -------------------------------------------------------------------------------- 1 | This folder contains intermediate results during some parsing processes. 2 | The final results are stored in the overall simulation results folder outside this repository. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | apptools==4.4.0 2 | configobj==5.0.6 3 | cycler==0.10.0 4 | envisage==4.7.1 5 | kiwisolver==1.0.1 6 | matplotlib==3.0.2 7 | mayavi==4.6.2 8 | numpy==1.16.1 9 | pyface==6.0.0 10 | Pygments==2.3.1 11 | pyparsing==2.3.1 12 | python-dateutil==2.8.0 13 | six==1.12.0 14 | traits==5.0.0 15 | traitsui==6.0.0 16 | vtk==8.1.2 17 | -------------------------------------------------------------------------------- /results/directivity_steering_sweep_5x5_array.txt: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 0.000000000000000000e+00 6.859176695954637637e+01 2 | 3.000000000000000000e+01 0.000000000000000000e+00 6.859176695954637637e+01 3 | 6.000000000000000000e+01 0.000000000000000000e+00 6.859176695954637637e+01 4 | 9.000000000000000000e+01 0.000000000000000000e+00 6.859176695954637637e+01 5 | 0.000000000000000000e+00 3.000000000000000000e+01 4.973753253455490153e+01 6 | 3.000000000000000000e+01 3.000000000000000000e+01 5.380639266715221680e+01 7 | 6.000000000000000000e+01 3.000000000000000000e+01 5.383662182641920424e+01 8 | 9.000000000000000000e+01 3.000000000000000000e+01 5.015683314927943570e+01 9 | 0.000000000000000000e+00 6.000000000000000000e+01 2.936037612480857106e+01 10 | 3.000000000000000000e+01 6.000000000000000000e+01 2.080296239755599430e+01 11 | 6.000000000000000000e+01 6.000000000000000000e+01 2.077066703618418941e+01 12 | 9.000000000000000000e+01 6.000000000000000000e+01 2.856415467488608684e+01 13 | 0.000000000000000000e+00 9.000000000000000000e+01 4.170890577695170265e+01 14 | 3.000000000000000000e+01 9.000000000000000000e+01 2.094910751730522236e+01 15 | 6.000000000000000000e+01 9.000000000000000000e+01 2.034424606458002316e+01 16 | 9.000000000000000000e+01 9.000000000000000000e+01 4.066468269978460626e+01 17 | -------------------------------------------------------------------------------- /stk_scripts/stk_generate_antenna_file.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | import sys 11 | import math 12 | import csv 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | import matplotlib.ticker as ticker 16 | plt.style.use('seaborn-whitegrid') 17 | plt.rc('text', usetex=True) 18 | plt.rc('font', family='serif') 19 | plt.rcParams.update({'font.size': 14}) 20 | from scipy import interpolate 21 | from scipy.signal import argrelextrema 22 | 23 | from CSTResultLoader import * 24 | from util import * 25 | 26 | 27 | """ 28 | Convert the CST simulation results to a STK antenna file 29 | """ 30 | 31 | if __name__ == "__main__": 32 | result_loader = CSTResultLoader() 33 | 34 | directory = "../../sim_data/reference_antennas/" 35 | filenames = ["2x2_uniform_broadside_realized_gain.txt", 36 | "3x3_uniform_broadside_realized_gain.txt", 37 | "4x4_uniform_broadside_realized_gain.txt", 38 | "5x5_uniform_broadside_realized_gain.txt"] 39 | 40 | # Create STK antenna file for reference antennas 41 | for filename in filenames: 42 | result_loader.convertASCII2STKAntennaFile(directory,filename) 43 | 44 | 45 | -------------------------------------------------------------------------------- /antenna_toolbox/array_analysis.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import matplotlib.pyplot as plt 10 | 11 | # Own Modules 12 | from antenna_toolbox.RectangularArray import RectangularArray 13 | 14 | if __name__ == "__main__": 15 | 16 | f = 8e9 17 | sdlb = 30 18 | 19 | # dims = [3,3,4,5] 20 | # arrays = [] 21 | # for dim in dims: 22 | # paa = RectangularArray(dim,dim,0.5,0.5,f,arraytype="uniform") 23 | # paa.getArrayFactor(0,180,1,0,360,1,0,0) 24 | # arrays.append(paa) 25 | # 26 | # 27 | # plr = True 28 | # ax = plt.subplot(111,polar=plr) 29 | # for array in arrays: 30 | # array.plotArrayFactorCut("phi", [0],ax=ax,polar=plr,label="Uniform, " + str(array.Nelements_x) + "x" + str(array.Nelements_y)) 31 | # 32 | angles = [0, 22.5, 45, 67.5, 90] 33 | arrays = [] 34 | for angle in angles: 35 | paa = RectangularArray(5,5,0.5,0.5,f,arraytype="dt",sidelobe_level=30) 36 | paa.getArrayFactor(0,180,1,0,360,1,angle,0) 37 | arrays.append(paa) 38 | 39 | 40 | plr = True 41 | ax = plt.subplot(111,polar=plr) 42 | for i,array in enumerate(arrays): 43 | array.plotArrayFactorCut("phi", [0],ax=ax,polar=plr,label="Uniform, " + str(angles[i]) + "Deg") 44 | 45 | arrays[0].plotArrayFactor3D(logarithmic=False) 46 | 47 | 48 | plt.show() 49 | 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phased Array Antenna Analysis 2 | 3 | This is a bunch of python scripts to simulate, analyze and plot the results of different simulations conducted in STK and CST Microwave Studio. 4 | 5 | There are basically three different parts: 6 | 7 | - CST result analysis 8 | - STK result analysis 9 | - Python scripts for simulating the fields of phased array and patch antennas 10 | 11 | ## CST Result Analysis 12 | The main script that analyzes the CST results is *CSTResultLoader.py*. 13 | It takes the steering sweep simulation results from CST and parses it to plot Heatmaps. 14 | The *plotCSTHeatmap.py* can also be used but does not provide as much functionality (legacy script). 15 | 16 | ## STK Scripts 17 | The STK scripts are rather self-explanatory according to their title. 18 | Each of them has to be adjusted so that the path to the simulation results is correct. 19 | 20 | ## Antenna Toolbox 21 | The antenna toolbox consists of 3 class definitions: *Antenna.py*, *RectangularArray.py* and *RectangularPatch.py*, 22 | where the two latter inherit important methods as the plotting of the 3D pattern or pattern cuts from *Antenna.py*. 23 | All classes support the calculation of the normalized pattern function and antenna directivity. 24 | 25 | For example, an antenna with an arbitrary precomputed field can be instantiated by: 26 | ```Python 27 | patch = Antenna() 28 | patch.loadPatternFromFile("sim/patch_8Ghz_efield_ro5880_0.254.txt") 29 | ``` 30 | 31 | Using the following, a rectangular array can be instantiated. 32 | In this example, the previously created `patch` will be set as element pattern. 33 | ```python 34 | paa1 = RectangularArray(4,4,0.5,0.5,f,arraytype="uniform",sidelobe_level=30) 35 | paa1.setElement(patch) 36 | paa1.getArrayFactor(0,180,1,0,360,1,0,0) 37 | paa1.getField(0,0) 38 | paa1.calcDirectivity() 39 | ``` 40 | 41 | The *full_paa_comparison.py* script shows more functionality of this toolbox. 42 | -------------------------------------------------------------------------------- /arraysize_vs_frequency_plot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | 11 | import math 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | import matplotlib.ticker as ticker 15 | 16 | plt.style.use('classic') 17 | #plt.rc('text', usetex=True) 18 | plt.rc('font', family='serif') 19 | plt.rcParams.update({'font.size': 16}) 20 | 21 | import matplotlib.gridspec as gridspec 22 | from scipy.interpolate import interp2d 23 | import scipy.constants as const 24 | from util import * 25 | 26 | 27 | if __name__=="__main__": 28 | 29 | 30 | 31 | number_of_elements_max = np.arange(2,20,1) 32 | array_length_max = 0.1 # See Gomspace ANT2000 (S-band Patch) for reference 33 | 34 | max_directivity = lin2db(number_of_elements_max) 35 | 36 | freq_max = const.speed_of_light/2 * number_of_elements_max / array_length_max 37 | 38 | 39 | # Plot 40 | ax0 = plt.gca() 41 | ax1 = ax0.twinx() 42 | ax0.plot(number_of_elements_max,freq_max/1e9,"ko--") 43 | ax0.set_ylabel("Frequency [GHz]",color="k") 44 | ax0.set_xlabel("Array Size") 45 | ax0.set_xlim([number_of_elements_max.min(),number_of_elements_max.max() ]) 46 | ax1.plot(number_of_elements_max,max_directivity,"mo--") 47 | ax1.set_ylabel("Approx. Increase in Directivity [dB]",color="m") 48 | 49 | labels = [str(n) + "x" + str(n) for n in number_of_elements_max] 50 | ax0.set_xticks(number_of_elements_max) 51 | ax0.set_xticklabels(labels) 52 | #ax0.yaxis.set_major_locator(ticker.MultipleLocator(3)) 53 | labels = np.arange(3,36,3) 54 | ax0.set_yticks(labels[:-1]) 55 | ax0.set_yticklabels(labels[1:]) 56 | 57 | np.set_printoptions(precision=2) 58 | for i,n in enumerate(number_of_elements_max): 59 | print("(" + str(n)+"," + str(max_directivity[i]) + ")") 60 | 61 | labels = np.arange(6,33,3) 62 | ax1.set_yticks(labels[:-1]) 63 | ax1.set_yticklabels(labels[1:]) 64 | 65 | ax0.grid(True) 66 | ax1.grid(0) 67 | 68 | plt.show() 69 | -------------------------------------------------------------------------------- /stk_scripts/stk_sat2sat_AER.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import os 11 | import matplotlib.pyplot as plt 12 | import matplotlib.ticker as ticker 13 | import matplotlib.dates as mdates 14 | #plt.style.use('classic') 15 | #plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | #plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | import pandas 20 | import datetime 21 | 22 | def dateparse (time_in_secs): 23 | return datetime.datetime.fromtimestamp(float(time_in_secs)) 24 | 25 | 26 | 27 | """ 28 | Plot Azimuth, Elevation and Range for the Sat2Sat scenario 29 | """ 30 | 31 | 32 | if __name__ == "__main__": 33 | 34 | # Load Data 35 | directory= "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat/" 36 | filename = "Satellite-LeadingSat-Transmitter-Fixed2x2-To-Satellite-TrailingSat-Receiver-Fixed2x2 AER.csv" 37 | 38 | d = pandas.read_csv(directory + filename, 39 | delimiter=',', 40 | parse_dates=["Time (UTCG)"], 41 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in dates]) 42 | 43 | 44 | start = 0 45 | t = d["Time (UTCG)"][start:] 46 | r = d["Range (km)"][start:] 47 | el = d["Elevation (deg)"][start:] 48 | 49 | # Plot 50 | gs = gridspec.GridSpec(2, 1) 51 | plt.figure() 52 | ax0 = plt.subplot(gs[0]) 53 | ax1 = plt.subplot(gs[1],sharex=ax0) 54 | 55 | ax0.plot(t, r) 56 | ax0.set_ylabel("Range [km]") 57 | ax0.grid(True) 58 | 59 | ax1.plot(t, el+90) 60 | ax1.set_ylabel("Elevation [deg]") 61 | ax1.grid(True) 62 | 63 | ax1.xaxis.set_major_formatter(mdates.DateFormatter('%d %b %Y ')) 64 | ax1.xaxis.set_major_locator(mdates.DayLocator()) 65 | plt.gcf().autofmt_xdate() 66 | 67 | d["Elevation (deg)"] = d["Elevation (deg)"] + 90 68 | d.to_csv(directory + "AER_sat2sat.txt", index=None, sep=",", float_format='%.2f') 69 | 70 | 71 | plt.show() 72 | 73 | -------------------------------------------------------------------------------- /antenna_toolbox/full_paa_comparison.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | import matplotlib.pyplot as plt 11 | 12 | # Own Modules 13 | from antenna_toolbox.Antenna import Antenna 14 | from antenna_toolbox.RectangularArray import RectangularArray 15 | 16 | if __name__ == "__main__": 17 | 18 | 19 | 20 | f = 8e9 21 | patch = Antenna() 22 | patch.loadPatternFromFile("sim/patch_8Ghz_efield_ro5880_0.254.txt") 23 | 24 | paa1 = RectangularArray(4,4,0.5,0.5,f,arraytype="uniform",sidelobe_level=30) 25 | paa1.setElement(patch) 26 | paa1.getArrayFactor(0,180,1,0,360,1,0,0) 27 | paa1.getField(0,0) 28 | paa1.calcDirectivity() 29 | 30 | paa1_sim = Antenna() 31 | paa1_sim.loadPatternFromFile("sim/uniform/farfield_array_4x4_0-0.txt") 32 | paa1_sim.calcDirectivity() 33 | 34 | 35 | paa1.plotDirectivity3D(logarithmic=True,log_range=-40) 36 | paa1_sim.plotDirectivity3D(logarithmic=True,log_range=-40) 37 | 38 | plt.figure() 39 | plr = True 40 | ax = plt.subplot(111,polar=plr) 41 | paa1.plotDirectivityCut("phi",[0],ax=ax,polar=plr,labels=["Sim + Analytical"]) 42 | paa1_sim.plotDirectivityCut("phi",[0],ax=ax,polar=plr,labels=["Sim"]) 43 | 44 | # f = 8e9 45 | # e_r = 3.66 46 | # h = 0.101e-3 47 | # patch = RectangularPatch(e_r,h,f) 48 | # patch.getField(0,90,1,0,360,1) 49 | # 50 | # # Generate Arrays 51 | # dims = [2,3,4,5] 52 | # arrays = [] 53 | # for dim in dims: 54 | # paa = RectangularArray(dim,dim,0.5,0.5,f,arraytype="uniform") 55 | # paa.setElement(patch) 56 | # paa.getField(0,0) 57 | # paa.calcDirectivity() 58 | # arrays.append(paa) 59 | # 60 | # 61 | # ## Plotting 62 | # plr = True 63 | # ax = plt.subplot(111,polar=plr) 64 | # 65 | # for array in arrays: 66 | # array.plotDirectivityCut("phi", [0],ax=ax,polar=plr,labels=["Uniform, "+str(array.Nelements_x) + "x" + str(array.Nelements_y)]) 67 | # #paa.plotPattern3D(logarithmic=False,log_range=-40) 68 | # arrays[0].plotDirectivity3D() 69 | plt.show() 70 | 71 | 72 | -------------------------------------------------------------------------------- /antenna_design_s_parameter_plots.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import math 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | plt.style.use('classic') 13 | plt.rc('text', usetex=True) 14 | plt.rc('font', family='serif') 15 | plt.rcParams.update({'font.size': 16}) 16 | plt.rc('grid', linestyle="--") 17 | 18 | import matplotlib.gridspec as gridspec 19 | from scipy.interpolate import interp2d 20 | import scipy.constants as const 21 | from util import * 22 | 23 | 24 | if __name__=="__main__": 25 | 26 | directory = "../sim_data/element_tuning_5x5/" 27 | files = [directory + "s11_parameters_5x5_patch_untuned.txt", 28 | directory + "s_parameter_2x2_array_tuned.txt", 29 | directory + "s_parameter_3x3_array_tuned.txt", 30 | directory + "s_parameter_4x4_array_tuned.txt", 31 | directory + "s_parameter_5x5_array_tuned.txt"] 32 | 33 | number_of_elements = [25, 4, 9, 16, 25] 34 | 35 | for i,file in enumerate(files): 36 | plot_name = file.split("/")[3] 37 | plot_name = plot_name[:-4] 38 | with open(file, newline='') as f: 39 | s_param = [] 40 | start = 0 41 | for line in f: 42 | if "----------------------------------------------------------------------" in line: 43 | start = 1 44 | elif line in ['\n', '\r\n']: 45 | start = 0 46 | elif start: 47 | s_param.append(list(map(float, line.split()))) 48 | s_param= np.asarray(s_param) 49 | s_param = np.split(s_param, number_of_elements[i]) 50 | freq = s_param[0][:,0] 51 | 52 | # Plot 53 | fig = plt.figure() 54 | fig.canvas.set_window_title(plot_name) 55 | ax0 = plt.subplot() 56 | for i,s in enumerate(s_param): 57 | ax0.plot(freq,s[:,1], "-",linewidth=0.5) 58 | 59 | ax0.set_xlim([6,10]) 60 | ax0.set_ylim([-35,0]) 61 | ax0.set_yticks(np.arange(-30,5,5)) 62 | ax0.set_xlabel("Frequency [GHz]") 63 | ax0.set_ylabel("S11 [dB]") 64 | 65 | plt.grid(True) 66 | plt.savefig(directory + plot_name + ".png") 67 | 68 | plt.show() 69 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | import sys 9 | import math 10 | import numpy as np 11 | 12 | from scipy.misc import factorial 13 | 14 | """ Conversion """ 15 | def deg2rad(angle): 16 | return angle*math.pi/180 17 | def rad2deg(angle): 18 | return angle*180/math.pi 19 | 20 | def lin2db(value_lin): 21 | return 20*np.log10(value_lin) 22 | def db2lin(value_db): 23 | return 10**(value_db/20) 24 | def pow2db(value_pow): 25 | return 10*np.log10(value_pow) 26 | def db2pow(value_db): 27 | return 10**(value_db/10) 28 | 29 | def cart2sph(x,y,z): 30 | r = np.sqrt(x**2 + y**2 + z**2) 31 | phi = np.arctan2(y,x) 32 | theta = np.arccos(z/r) 33 | return phi, theta, r 34 | 35 | def sph2cart(phi,theta,r): 36 | x = r * np.sin(theta) * np.cos(phi) 37 | y = r * np.sin(theta) * np.sin(phi) 38 | z = r * np.cos(theta) 39 | return x, y, z 40 | 41 | """ Data Treatment """ 42 | def normalize(x): 43 | return (x - x.min()) / (np.ptp(x)) 44 | 45 | 46 | """ Polynomials """ 47 | def binomial(x, y): 48 | """ Calculate Binomial Coefficient """ 49 | try: 50 | binom = math.factorial(x) // math.factorial(y) // math.factorial(x - y) 51 | except ValueError: 52 | binom = 0 53 | return binom 54 | 55 | def dolphTschebychev(n_elements,R): 56 | """ Calculate Weights based on Tschebychev Polynomial 57 | 58 | Args: 59 | n_elements: Number of elements in array 60 | R: Sidelobe-level (voltage ratio) 61 | 62 | Return: 63 | w: polynomial weights 64 | """ 65 | if R <= 0: 66 | sys.exit('Sidelobe Level has to be positive!') 67 | 68 | N = math.floor(n_elements/2) 69 | 70 | beta = (np.cosh(np.arccosh(R)/(n_elements-1)))**2 71 | alpha = 1 - 1/beta 72 | 73 | # Even Case 74 | if n_elements == 2*N: 75 | nend = N - 1 76 | w = np.zeros(N) 77 | else: 78 | nend = N 79 | w = np.zeros(N+1) 80 | 81 | w[0] = 1 82 | for n in range(1,nend+1): 83 | p = 1 84 | for m in range(1,n): 85 | f_m = m * (n_elements-1-2*n + m) / ((n-m) * (n+1-m)) 86 | p = p * alpha * f_m + 1 87 | 88 | w[n] = (n_elements-1)*alpha * p 89 | 90 | if n_elements == 2*N: 91 | w = np.concatenate((w,np.flipud(w))) 92 | else: 93 | w = np.concatenate((w[:-1],np.flipud(w))) 94 | 95 | 96 | return w 97 | 98 | -------------------------------------------------------------------------------- /antenna_toolbox/elementPhasePlot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import matplotlib.pyplot as plt 10 | from matplotlib.patches import Rectangle 11 | from matplotlib.collections import PatchCollection 12 | 13 | plt.style.use('seaborn-whitegrid') 14 | plt.rc('text', usetex=True) 15 | plt.rc('font', family='serif') 16 | plt.rcParams.update({'font.size': 14}) 17 | 18 | from antenna_toolbox.RectangularArray import RectangularArray 19 | from util import * 20 | 21 | """ 22 | Plot the phase shift for each array element given a certain steering angle 23 | """ 24 | 25 | if __name__=="__main__": 26 | 27 | theta_angles = np.arange(0,90,1) 28 | phi_angles = np.arange(0,360,1) 29 | theta_angles = np.asarray([10,20,30]) 30 | phi_angles = np.asarray([45]) 31 | 32 | 33 | 34 | 35 | array = RectangularArray(10, 10, 0.5, 0.5, 8e9) 36 | for theta in theta_angles: 37 | for phi in phi_angles: 38 | array.getArrayFactor(0,90,10,0,360,20,theta,phi) 39 | phase = rad2deg(array.element_phase_shift) 40 | phase = np.mod(phase, 360) 41 | 42 | 43 | 44 | patches = [] 45 | color_list = phase.flatten() 46 | 47 | # Add rectangles 48 | width = 0.01*1000 49 | height = 0.01*1000 50 | for a_x, a_y in zip(array.elements_x.flatten()*1000,array.elements_y.flatten()*1000): 51 | patch = Rectangle(xy=(a_x - width / 2, a_y - height / 2), width=width, height=height) 52 | patches.append(patch) 53 | 54 | 55 | collection = PatchCollection(patches,cmap=plt.cm.hot) 56 | collection.set_array(color_list) 57 | 58 | # Figure 59 | fig = plt.figure() 60 | fig.canvas.set_window_title("Element Phase Shift" + " Theta: " + str(theta) + "°, Phi: " + str(phi) + "°") 61 | ax = plt.subplot() 62 | 63 | ax.add_collection(collection) 64 | 65 | 66 | ax.set_title("Element Phase Shift\n" + "Theta: " + str(theta) + "°, Phi: " + str(phi) + "°") 67 | ax.set_xlim(array.elements_x.min()*1000*1.2,array.elements_x.max()*1000*1.2) 68 | ax.set_ylim(array.elements_y.min()*1000*1.2,array.elements_y.max()*1000*1.2) 69 | ax.set_xticks(np.unique(array.elements_x)*1000) 70 | ax.set_yticks(np.unique(array.elements_y)*1000) 71 | ax.set_facecolor('grey') 72 | plt.xticks(rotation=45) 73 | plt.colorbar(collection) 74 | 75 | plt.show() 76 | 77 | -------------------------------------------------------------------------------- /antenna_toolbox/rect_patch_comparison.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import matplotlib.pyplot as plt 10 | import matplotlib.gridspec as gridspec 11 | plt.style.use('seaborn-whitegrid') 12 | # plt.rc('text', usetex=True) 13 | plt.rc('font', family='serif') 14 | plt.rcParams.update({'font.size': 14}) 15 | 16 | # Own Modules 17 | from antenna_toolbox.Antenna import Antenna 18 | from antenna_toolbox.RectangularPatch import RectangularPatch 19 | from util import * 20 | import pandas 21 | 22 | if __name__ == "__main__": 23 | 24 | f = 8e9 25 | e_r = 3.48 26 | h = 1.55e-3 27 | 28 | ### Analytical Patch ### 29 | patch = RectangularPatch(e_r, h, f, rolloff=0.1) 30 | patch.getField(0, 90, 1, 0, 360, 1) 31 | patch.calcDirectivity() 32 | 33 | 34 | gs = gridspec.GridSpec(2, 2) 35 | #ax1 = plt.subplot(gs[0],polar=True) 36 | #ax2 = plt.subplot(gs[1],projection='3d') 37 | #ax3 = plt.subplot(gs[2],polar=True) 38 | #ax4 = plt.subplot(gs[3],projection='3d') 39 | 40 | fig = plt.figure() 41 | ax1 = fig.add_subplot(111, polar=True) 42 | fig.suptitle('Directivity, Phi= 0°') 43 | 44 | 45 | fig = plt.figure() 46 | ax2 = fig.add_subplot(111, projection='3d') 47 | fig.suptitle('Normalized Pattern, Analytical') 48 | 49 | fig = plt.figure() 50 | ax3 = fig.add_subplot(111, polar=True) 51 | fig.suptitle('Directivity, Phi= 90°') 52 | 53 | fig = plt.figure() 54 | ax4 = fig.add_subplot(111, projection='3d') 55 | fig.suptitle('Normalized Pattern, Simulated') 56 | 57 | #patch.plotPatternCut("phi", [0,90],ax=ax1,labels=["0Deg","90Deg"]) 58 | patch.plotPattern3D(ax=ax2,logarithmic=True) 59 | 60 | 61 | ### Simulated Patch ### 62 | 63 | patch_sim = Antenna() 64 | patch_sim.loadPatternFromFile("../sim/patch_8Ghz_efield_ro5880_0.254.txt") 65 | patch_sim.calcDirectivity() 66 | 67 | 68 | #patch_sim.plotPatternCut("phi", [0,90],ax=ax3,labels=["0Deg","90Deg"]) 69 | patch_sim.plotPattern3D(ax=ax4,logarithmic=True) 70 | 71 | _,dir_phi0,theta_phi0 = patch.plotPatternCut("phi", [0],ax=ax1,labels=["Analytical"]) 72 | _, dir_sim_phi0, theta_sim_phi0 = patch_sim.plotPatternCut("phi", [0],ax=ax1,labels=["Simulated"]) 73 | _, dir_phi90, theta_phi90 = patch.plotPatternCut("phi", [90],ax=ax3,labels=["Analytical"]) 74 | _, dir_sim_phi90, theta_phi90 = patch_sim.plotPatternCut("phi", [90],ax=ax3,labels=["Simulated"]) 75 | 76 | 77 | dic = {"Theta": rad2deg(theta_phi0), 78 | "Dir Phi0": lin2db(dir_phi0).clip(min=-40), 79 | "Dir Phi90": lin2db(dir_phi90).clip(min=-40)} 80 | dic_sim = {"Theta":rad2deg(theta_sim_phi0), 81 | "Dir Sim Phi0": lin2db(dir_sim_phi0).clip(min=-40), 82 | "Dir Sim Phi90": lin2db(dir_sim_phi90).clip(min=-40)} 83 | 84 | df = pandas.DataFrame(data=dic) 85 | df.to_csv("../sim/directivity_patch_analytical.txt", index=None, sep=",", float_format='%.2f') 86 | df_sim = pandas.DataFrame(data=dic_sim) 87 | df_sim.to_csv("../sim/directivity_patch_sim.txt", index=None, sep=",", float_format='%.2f') 88 | 89 | #plt.show() 90 | 91 | -------------------------------------------------------------------------------- /antenna_toolbox/sweep_steering_angle.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import os 10 | import matplotlib.pyplot as plt 11 | 12 | from scipy.interpolate import interp2d 13 | 14 | # Own Modules 15 | from antenna_toolbox.Antenna import Antenna 16 | from antenna_toolbox.RectangularArray import RectangularArray 17 | from util import * 18 | 19 | if __name__ == "__main__": 20 | # Load Unit Cell 21 | f = 8e9 22 | patch = Antenna() 23 | patch.loadPatternFromFile("sim/patch_8Ghz_efield_ro5880_0.254.txt") 24 | 25 | # Array Dimensions 26 | dims = [2,3,4,5] 27 | #dims = [3,4] 28 | 29 | # Steering angles 30 | theta_angles = np.linspace(0,90,19) 31 | phi_angles = np.linspace(0,360,73) 32 | #theta_angles = np.linspace(0,90,4) 33 | #phi_angles = np.linspace(0,90,4) 34 | theta_mesh,phi_mesh = np.meshgrid(theta_angles,phi_angles) 35 | 36 | dir_heatmap = np.zeros((theta_angles.size,phi_angles.size)) 37 | arrays = [] 38 | dir_heatmaps = [] 39 | 40 | 41 | for dim in dims: 42 | result_path = "results/steering_sweep/uniform/array"+ str(dim) + "x" + str(dim) + "/" 43 | if not os.path.exists(result_path): 44 | os.makedirs(result_path) 45 | 46 | max_dir_filename = "max_directivity_" + str(dim) + "x" + str(dim) + "_array.txt" 47 | 48 | if not os.path.isfile(result_path + max_dir_filename): 49 | print("Calculating " + str(dim) + "x" + str(dim) + "-Array") 50 | # Create Array 51 | paa = RectangularArray(dim,dim,0.5,0.5,f,arraytype="uniform") 52 | paa.setElement(patch) 53 | 54 | 55 | # Loop through steering angles 56 | for t,theta in enumerate(theta_angles): 57 | for p,phi in enumerate(phi_angles): 58 | paa.getField(theta,phi) 59 | paa.calcDirectivity() 60 | 61 | dir_filename = "directivity_" + str(dim) + "x" + str(dim) + "_array_" + str(theta) + "-" + str(phi) +".txt" 62 | # Store directivity pattern 63 | np.savetxt(result_path + dir_filename,np.array((paa.theta_mesh.flatten(),paa.phi_mesh.flatten(),paa.directivity.flatten())).T) 64 | 65 | dir_heatmap[t,p]= paa.max_directivity 66 | print("Theta= " +str(theta)+", Phi= " + str(phi) + ": Max. Dir= " + str(pow2db(paa.max_directivity))) 67 | 68 | #Store max directivity map 69 | np.savetxt(result_path + max_dir_filename,np.array((theta_mesh.flatten(),phi_mesh.flatten(),dir_heatmap.flatten())).T) 70 | arrays.append(paa) 71 | 72 | else: 73 | print("Loading stored results...") 74 | theta_mesh, phi_mesh, dir_heatmap = np.loadtxt(result_path + max_dir_filename, unpack=True) 75 | ntheta = np.unique(theta_mesh).size 76 | nphi = np.unique(phi_mesh).size 77 | theta_mesh = np.reshape(theta_mesh,(nphi,ntheta)) 78 | phi_mesh = np.reshape(phi_mesh,(nphi,ntheta)) 79 | dir_heatmap = np.reshape(dir_heatmap,(nphi,ntheta)) 80 | 81 | 82 | dir_heatmaps.append(dir_heatmap) 83 | 84 | 85 | x = np.arange(0, 90, 0.5) 86 | y = np.arange(0, 90, 0.5) 87 | X, Y = np.meshgrid(x, y) 88 | 89 | for heatmap in dir_heatmaps: 90 | 91 | h = pow2db(heatmap) 92 | f = interp2d(theta_mesh, phi_mesh, h, kind='cubic') 93 | 94 | fig,ax = plt.subplots() 95 | c = ax.pcolormesh(theta_mesh,phi_mesh,h, cmap='jet',vmin=h.min(),vmax=h.max()) 96 | ax.set_title("Maximum Directivity") 97 | fig.colorbar(c, ax=ax) 98 | 99 | plt.show() 100 | -------------------------------------------------------------------------------- /sim/directivity_patch_analytical.txt: -------------------------------------------------------------------------------- 1 | Theta,Dir Phi0,Dir Phi90 2 | -90.00,-40.00,-2.98 3 | -89.00,-36.82,-2.98 4 | -88.00,-30.80,-2.98 5 | -87.00,-27.28,-2.97 6 | -86.00,-24.78,-2.97 7 | -85.00,-22.84,-2.96 8 | -84.00,-21.26,-2.95 9 | -83.00,-19.92,-2.93 10 | -82.00,-18.75,-2.92 11 | -81.00,-17.73,-2.90 12 | -80.00,-16.81,-2.88 13 | -79.00,-15.99,-2.86 14 | -78.00,-15.23,-2.84 15 | -77.00,-14.53,-2.82 16 | -76.00,-13.89,-2.79 17 | -75.00,-13.29,-2.76 18 | -74.00,-12.73,-2.73 19 | -73.00,-12.20,-2.70 20 | -72.00,-11.70,-2.67 21 | -71.00,-11.23,-2.64 22 | -70.00,-10.79,-2.60 23 | -69.00,-10.36,-2.57 24 | -68.00,-9.96,-2.53 25 | -67.00,-9.57,-2.49 26 | -66.00,-9.20,-2.45 27 | -65.00,-8.85,-2.41 28 | -64.00,-8.51,-2.37 29 | -63.00,-8.18,-2.32 30 | -62.00,-7.87,-2.28 31 | -61.00,-7.56,-2.23 32 | -60.00,-7.27,-2.19 33 | -59.00,-6.99,-2.14 34 | -58.00,-6.72,-2.09 35 | -57.00,-6.46,-2.05 36 | -56.00,-6.20,-2.00 37 | -55.00,-5.95,-1.95 38 | -54.00,-5.71,-1.90 39 | -53.00,-5.48,-1.85 40 | -52.00,-5.26,-1.80 41 | -51.00,-5.04,-1.75 42 | -50.00,-4.83,-1.70 43 | -49.00,-4.63,-1.65 44 | -48.00,-4.43,-1.60 45 | -47.00,-4.23,-1.55 46 | -46.00,-4.05,-1.49 47 | -45.00,-3.87,-1.44 48 | -44.00,-3.69,-1.39 49 | -43.00,-3.52,-1.34 50 | -42.00,-3.35,-1.29 51 | -41.00,-3.19,-1.25 52 | -40.00,-3.03,-1.20 53 | -39.00,-2.88,-1.15 54 | -38.00,-2.74,-1.10 55 | -37.00,-2.59,-1.05 56 | -36.00,-2.45,-1.01 57 | -35.00,-2.32,-0.96 58 | -34.00,-2.19,-0.92 59 | -33.00,-2.07,-0.87 60 | -32.00,-1.95,-0.83 61 | -31.00,-1.83,-0.79 62 | -30.00,-1.72,-0.75 63 | -29.00,-1.61,-0.71 64 | -28.00,-1.50,-0.67 65 | -27.00,-1.40,-0.63 66 | -26.00,-1.30,-0.59 67 | -25.00,-1.21,-0.55 68 | -24.00,-1.12,-0.52 69 | -23.00,-1.04,-0.48 70 | -22.00,-0.95,-0.45 71 | -21.00,-0.88,-0.42 72 | -20.00,-0.80,-0.39 73 | -19.00,-0.73,-0.36 74 | -18.00,-0.67,-0.33 75 | -17.00,-0.60,-0.31 76 | -16.00,-0.54,-0.28 77 | -15.00,-0.49,-0.26 78 | -14.00,-0.44,-0.24 79 | -13.00,-0.39,-0.22 80 | -12.00,-0.34,-0.20 81 | -11.00,-0.30,-0.18 82 | -10.00,-0.26,-0.16 83 | -9.00,-0.23,-0.15 84 | -8.00,-0.20,-0.14 85 | -7.00,-0.17,-0.12 86 | -6.00,-0.15,-0.11 87 | -5.00,-0.13,-0.10 88 | -4.00,-0.11,-0.10 89 | -3.00,-0.10,-0.09 90 | -2.00,-0.09,-0.09 91 | -1.00,-0.09,-0.09 92 | -0.00,-0.09,-0.09 93 | 0.00,-0.09,-0.09 94 | 1.00,-0.09,-0.09 95 | 2.00,-0.09,-0.09 96 | 3.00,-0.10,-0.09 97 | 4.00,-0.11,-0.10 98 | 5.00,-0.13,-0.10 99 | 6.00,-0.15,-0.11 100 | 7.00,-0.17,-0.12 101 | 8.00,-0.20,-0.14 102 | 9.00,-0.23,-0.15 103 | 10.00,-0.26,-0.16 104 | 11.00,-0.30,-0.18 105 | 12.00,-0.34,-0.20 106 | 13.00,-0.39,-0.22 107 | 14.00,-0.44,-0.24 108 | 15.00,-0.49,-0.26 109 | 16.00,-0.54,-0.28 110 | 17.00,-0.60,-0.31 111 | 18.00,-0.67,-0.33 112 | 19.00,-0.73,-0.36 113 | 20.00,-0.80,-0.39 114 | 21.00,-0.88,-0.42 115 | 22.00,-0.95,-0.45 116 | 23.00,-1.04,-0.48 117 | 24.00,-1.12,-0.52 118 | 25.00,-1.21,-0.55 119 | 26.00,-1.30,-0.59 120 | 27.00,-1.40,-0.63 121 | 28.00,-1.50,-0.67 122 | 29.00,-1.61,-0.71 123 | 30.00,-1.72,-0.75 124 | 31.00,-1.83,-0.79 125 | 32.00,-1.95,-0.83 126 | 33.00,-2.07,-0.87 127 | 34.00,-2.19,-0.92 128 | 35.00,-2.32,-0.96 129 | 36.00,-2.45,-1.01 130 | 37.00,-2.59,-1.05 131 | 38.00,-2.74,-1.10 132 | 39.00,-2.88,-1.15 133 | 40.00,-3.03,-1.20 134 | 41.00,-3.19,-1.25 135 | 42.00,-3.35,-1.29 136 | 43.00,-3.52,-1.34 137 | 44.00,-3.69,-1.39 138 | 45.00,-3.87,-1.44 139 | 46.00,-4.05,-1.49 140 | 47.00,-4.23,-1.55 141 | 48.00,-4.43,-1.60 142 | 49.00,-4.63,-1.65 143 | 50.00,-4.83,-1.70 144 | 51.00,-5.04,-1.75 145 | 52.00,-5.26,-1.80 146 | 53.00,-5.48,-1.85 147 | 54.00,-5.71,-1.90 148 | 55.00,-5.95,-1.95 149 | 56.00,-6.20,-2.00 150 | 57.00,-6.46,-2.05 151 | 58.00,-6.72,-2.09 152 | 59.00,-6.99,-2.14 153 | 60.00,-7.27,-2.19 154 | 61.00,-7.56,-2.23 155 | 62.00,-7.87,-2.28 156 | 63.00,-8.18,-2.32 157 | 64.00,-8.51,-2.37 158 | 65.00,-8.85,-2.41 159 | 66.00,-9.20,-2.45 160 | 67.00,-9.57,-2.49 161 | 68.00,-9.96,-2.53 162 | 69.00,-10.36,-2.57 163 | 70.00,-10.79,-2.60 164 | 71.00,-11.23,-2.64 165 | 72.00,-11.70,-2.67 166 | 73.00,-12.20,-2.70 167 | 74.00,-12.73,-2.73 168 | 75.00,-13.29,-2.76 169 | 76.00,-13.89,-2.79 170 | 77.00,-14.53,-2.82 171 | 78.00,-15.23,-2.84 172 | 79.00,-15.99,-2.86 173 | 80.00,-16.81,-2.88 174 | 81.00,-17.73,-2.90 175 | 82.00,-18.75,-2.92 176 | 83.00,-19.92,-2.93 177 | 84.00,-21.26,-2.95 178 | 85.00,-22.84,-2.96 179 | 86.00,-24.78,-2.97 180 | 87.00,-27.28,-2.97 181 | 88.00,-30.80,-2.98 182 | 89.00,-36.82,-2.98 183 | 90.00,-40.00,-2.98 184 | -------------------------------------------------------------------------------- /stk_scripts/stk_linkbudget_access_avg.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import os 11 | import matplotlib.pyplot as plt 12 | import matplotlib.ticker as ticker 13 | import matplotlib.dates as mdates 14 | #plt.style.use('seaborn') 15 | #plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | #plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | import pandas 20 | import datetime 21 | 22 | def dateparse (time_in_secs): 23 | return datetime.datetime.fromtimestamp(float(time_in_secs)) 24 | 25 | 26 | """ 27 | Plot the average link budget over all accesses 28 | """ 29 | 30 | if __name__ == "__main__": 31 | 32 | # Load Link Budget Comparison 33 | directory= "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2gnd/linkbudgetcomparison_10Mbps/" 34 | filenames = ["Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 35 | "Satellite-DelphiniX-Transmitter-PAA3x3-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 36 | "Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 37 | "Satellite-DelphiniX-Transmitter-PAA5x5-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 38 | "Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv"] 39 | 40 | labels = ["2x2","3x3","4x4","5x5","Patch"] 41 | 42 | 43 | 44 | data_output_directory = directory + "access_avg/" 45 | 46 | os.makedirs(data_output_directory, exist_ok=True) 47 | 48 | data = [] 49 | 50 | for filename in filenames: 51 | d = pandas.read_csv(directory + filename, 52 | delimiter=',', 53 | parse_dates=["Time (UTCG)"], 54 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in dates]) 55 | data.append(d) 56 | 57 | gs = gridspec.GridSpec(2, 1) 58 | plt.figure() 59 | ax0 = plt.subplot(gs[0]) 60 | ax1 = plt.subplot(gs[1], sharex=ax0) 61 | 62 | for i,d in enumerate(data): 63 | accesses = np.unique(d["Access Number"].values) 64 | access_avg = d.groupby(["Access Number"]).mean() 65 | access_max = d.groupby(["Access Number"]).max() 66 | access_min = d.groupby(["Access Number"]).min() 67 | access_std = d.groupby(["Access Number"]).std() 68 | 69 | 70 | ax0.plot(accesses[0:20],access_avg["EIRP (dBW)"].values[0:20],".--",label=labels[i]) 71 | ax0.fill_between(accesses[0:20], 72 | access_avg["EIRP (dBW)"].values[0:20] - access_std["EIRP (dBW)"].values[0:20], 73 | access_avg["EIRP (dBW)"].values[0:20] + access_std["EIRP (dBW)"].values[0:20], 74 | alpha=0.3) 75 | ax0.set_ylabel("EIRP", ) 76 | ax0.grid(True) 77 | 78 | dic = {"Access Number": accesses[0:20], 79 | "EIRP Avg": access_avg["EIRP (dBW)"].values[0:20], 80 | "EIRP Std": access_std["EIRP (dBW)"].values[0:20]} 81 | 82 | df = pandas.DataFrame(data=dic) 83 | df.to_csv(data_output_directory + "eirp_access" + labels[i] + ".txt", index=None, sep=",",float_format='%.2f') 84 | 85 | 86 | ax1.plot(accesses[0:20],access_avg["C/N (dB)"].values[0:20],".--",label=labels[i]) 87 | ax1.fill_between(accesses[0:20], 88 | access_avg["C/N (dB)"].values[0:20] - access_std["C/N (dB)"].values[0:20], 89 | access_avg["C/N (dB)"].values[0:20] + access_std["C/N (dB)"].values[0:20], 90 | alpha=0.3) 91 | ax1.set_ylabel("C/N", ) 92 | ax1.grid(True) 93 | 94 | dic = {"Access Number": accesses[0:20], 95 | "SNR Avg": access_avg["C/N (dB)"].values[0:20], 96 | "SNR Std": access_std["C/N (dB)"].values[0:20]} 97 | 98 | df = pandas.DataFrame(data=dic) 99 | df.to_csv(data_output_directory + "snr_access" + labels[i] + ".txt", index=None, sep=",",float_format='%.2f') 100 | 101 | plt.legend(loc='lower left', bbox_to_anchor=(1, 0.5)) 102 | plt.show() 103 | -------------------------------------------------------------------------------- /stk_scripts/stk_linkbudget_plots.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import os 11 | import matplotlib.pyplot as plt 12 | import matplotlib.ticker as ticker 13 | import matplotlib.dates as mdates 14 | #plt.style.use('classic') 15 | #plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | #plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | import pandas 20 | import datetime 21 | 22 | def dateparse (time_in_secs): 23 | return datetime.datetime.fromtimestamp(float(time_in_secs)) 24 | 25 | 26 | 27 | """ 28 | Plot several link budget simulation results 29 | """ 30 | 31 | 32 | if __name__ == "__main__": 33 | 34 | # Load Data 35 | directory= "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2gnd/linkbudgetcomparison_10Mbps/" 36 | filenames = ["Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 37 | "Satellite-DelphiniX-Transmitter-PAA3x3-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 38 | "Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 39 | "Satellite-DelphiniX-Transmitter-PAA5x5-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv", 40 | "Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison.csv"] 41 | 42 | labels = ["2x2","3x3","4x4","5x5","Patch"] 43 | 44 | 45 | data = [] 46 | 47 | for filename in filenames: 48 | d = pandas.read_csv(directory + filename, 49 | delimiter=',', 50 | parse_dates=["Time (UTCG)"], 51 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in dates]) 52 | data.append(d) 53 | 54 | 55 | 56 | # Select which access to plot 57 | 58 | access=18 # Good Pass 59 | data_output_directory = directory + "goodpass/" 60 | #access=16 #Bad Pass 61 | #data_output_directory = directory + "badpass/" 62 | 63 | os.makedirs(data_output_directory, exist_ok=True) 64 | 65 | if not access == []: 66 | a = data[0].loc[d["Access Number"] == access] 67 | else: 68 | a = d 69 | 70 | 71 | r = a["Range (km)"] 72 | el = a["Elevation (deg)"] 73 | t = a["Time (UTCG)"] 74 | 75 | cn = [] 76 | eirp = [] 77 | for d in data: 78 | if not access == []: 79 | a = d.loc[d["Access Number"] == access] 80 | else: 81 | a = d 82 | cn.append(a["C/N (dB)"]) 83 | eirp.append(a["EIRP (dBW)"]) 84 | 85 | 86 | 87 | # Plot 88 | gs = gridspec.GridSpec(4, 1) 89 | plt.figure() 90 | ax0 = plt.subplot(gs[0]) 91 | ax1 = plt.subplot(gs[1],sharex=ax0) 92 | ax2 = plt.subplot(gs[2],sharex=ax0) 93 | ax3 = plt.subplot(gs[3],sharex=ax0) 94 | 95 | ax0.plot(t, r, "r") 96 | ax0.set_ylabel("Range [km]", ) 97 | ax0.grid(True) 98 | df = pandas.concat([t, r], axis=1) 99 | df.to_csv(data_output_directory + "range.txt", index=None, sep=",", float_format='%.2f') 100 | 101 | ax1.plot(t, el, "r") 102 | ax1.set_ylabel("Elevation [deg]", ) 103 | ax1.grid(True) 104 | df = pandas.concat([t, el], axis=1) 105 | df.to_csv(data_output_directory + "elevation.txt", index=None, sep=",", float_format='%.2f') 106 | 107 | 108 | for i,x in enumerate(eirp): 109 | ax2.plot(t,x,label=labels[i]) 110 | df = pandas.concat([t, x],axis=1) 111 | df.to_csv(data_output_directory + "eirp" + labels[i] + ".txt", index=None, sep="," ,float_format='%.2f') 112 | 113 | ax2.set_ylabel("EIRP [dBW]") 114 | ax2.set_xlabel("Time") 115 | #ax1.set_ylim([0,20]) 116 | ax2.grid(True) 117 | 118 | 119 | for i,x in enumerate(cn): 120 | ax3.plot(t,x,label=labels[i]) 121 | df = pandas.concat([t, x],axis=1) 122 | df.to_csv(data_output_directory + "snr" + labels[i] + ".txt", index=None, sep="," ,float_format='%.2f') 123 | 124 | ax3.set_ylabel("C/N") 125 | ax3.set_xlabel("Time") 126 | #ax2.set_ylim([0,60]) 127 | ax3.grid(True) 128 | 129 | 130 | ax3.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 131 | ax3.xaxis.set_major_locator(mdates.MinuteLocator()) 132 | plt.gcf().autofmt_xdate() 133 | 134 | plt.legend(loc='lower left', bbox_to_anchor=(1, 0.5)) 135 | 136 | 137 | 138 | plt.show() 139 | 140 | -------------------------------------------------------------------------------- /antenna_toolbox/RectangularPatch.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | from math import cos, sin, sqrt 10 | 11 | from util import * 12 | from antenna_toolbox.Antenna import Antenna 13 | 14 | class RectangularPatch(Antenna): 15 | """ Rectangular Patch Antenna """ 16 | 17 | def __init__(self,epsilon_r,substrate_height,frequency,rolloff=1): 18 | self.epsilon_r = epsilon_r 19 | self.substrate_height = substrate_height 20 | self.frequency = frequency 21 | 22 | self.getDimensions() 23 | 24 | 25 | 26 | self.rolloff_factor = rolloff 27 | 28 | def getDimensions(self): 29 | """ Calculate Standard Rectangular Patch Dimensions (Balanis,4th Ed.,14.2""" 30 | 31 | e_r = self.epsilon_r 32 | h = self.substrate_height 33 | f = self.frequency 34 | 35 | # Permittivity of free space 36 | epsilon0 = 8.854185e-12 37 | 38 | wavelength = 3e8 / f 39 | wavelength_adapt = wavelength / sqrt(e_r) 40 | 41 | # Patch Witdh 42 | W = (3e8 / (2 * f)) * sqrt(2 / (e_r + 1)) 43 | 44 | e_r_eff = ((e_r + 1) / 2) + ((e_r - 1) / 2) * (1 + 12 * (h / W)) ** -0.5 45 | 46 | F1 = (e_r_eff + 0.3) * (W / h + 0.264) 47 | F2 = (e_r_eff - 0.258) * (W / h + 0.8) 48 | dL = h * 0.412 * (F1 / F2) 49 | 50 | wavelength_adapt = wavelength / sqrt(e_r_eff) 51 | self.L = (wavelength_adapt/ 2) - 2 * dL 52 | self.substrate_height_eff = h * sqrt(e_r) 53 | self.W = W 54 | self.epsilon_r_eff = e_r_eff 55 | 56 | return self.W,self.L,self.epsilon_r_eff,self.substrate_height_eff 57 | 58 | 59 | def calcFieldValue(self,theta_in,phi_in): 60 | """ Calculate far-field normalized pattern value """ 61 | 62 | # Wavenumber 63 | wavelength = 3e8 / self.frequency 64 | k = 2 * math.pi / wavelength 65 | 66 | # Calculate Far-Field Points and rotate coordinate system 90 degrees 67 | xff, yff, zff = sph2cart(phi_in, theta_in, 999) 68 | # #xffd = zff 69 | # #yffd = yff 70 | # #zffd = xff 71 | # xffd = xff 72 | # yffd = yff 73 | # zffd = zff 74 | # phi, theta, r = cart2sph(xffd, yffd, zffd) 75 | # 76 | # if theta == 0: 77 | # theta = 1e-9 78 | # if phi == 0: 79 | # phi = 1e-9 80 | # 81 | # # Calculate Normalized Far-Field Pattern 82 | # X = k*self.W/2 *sin(theta)*sin(phi) 83 | # Fphi = -sin(X)/X*cos(k*self.L*sin(theta)*cos(phi))*cos(theta)*sin(phi) 84 | # Ftheta = sin(X)/X*cos(k*self.L*sin(theta)*cos(phi))*cos(phi) 85 | # 86 | # 87 | # # Smooth pattern using roll-off 88 | # theta_deg = theta_in * 180 / math.pi 89 | # F1 = 1 / (((self.rolloff_factor * (abs(theta_deg) - 90)) ** 2) + 0.001) 90 | # PatEdgeSF = 1 / (F1 + 1) 91 | # 92 | # UNF = 1.0006 93 | 94 | 95 | # Balanis Method 96 | xffd = zff 97 | yffd = yff 98 | zffd = xff 99 | phi, theta, r = cart2sph(xffd, yffd, zffd) 100 | 101 | if theta == 0: 102 | theta = 1e-9 103 | if phi == 0: 104 | phi = 1e-9 105 | 106 | 107 | 108 | X = k * self.substrate_height_eff / 2 * sin(theta)*cos(phi) 109 | Z = k * self.W / 2 * cos(theta) 110 | 111 | if theta_in <= math.pi / 2: 112 | Ftotal = sin(theta)*sin(X)/X * sin(Z)/Z * cos(k * self.L / 2 * sin(theta) *sin(phi)) 113 | else: 114 | Ftotal = 1e-9 115 | 116 | 117 | 118 | return Ftotal 119 | 120 | 121 | def getField(self,theta_start,theta_stop,theta_step,phi_start,phi_stop,phi_step): 122 | """ Calculate Normalized Pattern for angular range """ 123 | self.Ntheta = (theta_stop-theta_start)/theta_step + 1 124 | self.Nphi = (phi_stop-phi_start)/phi_step + 1 125 | self.theta_step = theta_step 126 | self.phi_step = phi_step 127 | self.theta_start = theta_start 128 | self.theta_stop = theta_stop 129 | self.phi_start = phi_start 130 | self.phi_stop = phi_stop 131 | 132 | theta_range = deg2rad(np.linspace(theta_start,theta_stop,self.Ntheta)) 133 | phi_range = deg2rad(np.linspace(phi_start,phi_stop,self.Nphi)) 134 | self.pattern = np.ones((theta_range.size,phi_range.size)) 135 | self.theta_mesh = np.ones((theta_range.size,phi_range.size)) 136 | self.phi_mesh = np.ones((theta_range.size,phi_range.size)) 137 | 138 | 139 | 140 | for t,theta in enumerate(theta_range): 141 | for p,phi in enumerate(phi_range): 142 | self.pattern[t,p] = self.calcFieldValue(theta,phi) 143 | self.theta_mesh[t,p] = theta 144 | self.phi_mesh[t,p] = phi 145 | 146 | return self.pattern 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /stk_scripts/stk_sat2sat_comparison_fixed_paa.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import sys 11 | import os 12 | import matplotlib.pyplot as plt 13 | import matplotlib.ticker as ticker 14 | import matplotlib.dates as mdates 15 | #plt.style.use('classic') 16 | #plt.rc('text', usetex=True) 17 | plt.rc('font', family='serif') 18 | #plt.rcParams.update({'font.size': 14}) 19 | import matplotlib.gridspec as gridspec 20 | cmap = plt.get_cmap('jet') 21 | import pandas 22 | import datetime 23 | 24 | 25 | 26 | """ 27 | Plots for Sat2Sat scenario 28 | """ 29 | 30 | 31 | # Load Data 32 | directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat/" 33 | filename = "Satellite-LeadingSat-Transmitter-Fixed2x2-To-Satellite-TrailingSat-Receiver-Fixed2x2 AER.csv" 34 | 35 | d = pandas.read_csv(directory + filename, 36 | delimiter=',', 37 | parse_dates=["Time (UTCG)"], 38 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in dates]) 39 | r = d["Range (km)"] 40 | if __name__ == "__main__": 41 | 42 | directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat/comparison_fixed_paa/" 43 | 44 | 45 | filenames_fixed = ['Satellite-LeadingSat-Transmitter-Fixed2x2-To-Satellite-TrailingSat-Receiver-Fixed2x2 Link Budget - Detailed.csv', 46 | 'Satellite-LeadingSat-Transmitter-Fixed3x3-To-Satellite-TrailingSat-Receiver-Fixed3x3 Link Budget - Detailed.csv', 47 | 'Satellite-LeadingSat-Transmitter-Fixed4x4-To-Satellite-TrailingSat-Receiver-Fixed4x4 Link Budget - Detailed.csv', 48 | 'Satellite-LeadingSat-Transmitter-Fixed5x5-To-Satellite-TrailingSat-Receiver-Fixed5x5 Link Budget - Detailed.csv'] 49 | filenames_paa = ['Satellite-LeadingSat-Transmitter-PAA2x2-To-Satellite-TrailingSat-Receiver-PAA2x2 Link Budget - Detailed.csv', 50 | 'Satellite-LeadingSat-Transmitter-PAA3x3-To-Satellite-TrailingSat-Receiver-PAA3x3 Link Budget - Detailed.csv', 51 | 'Satellite-LeadingSat-Transmitter-PAA4x4-To-Satellite-TrailingSat-Receiver-PAA4x4 Link Budget - Detailed.csv', 52 | 'Satellite-LeadingSat-Transmitter-PAA5x5-To-Satellite-TrailingSat-Receiver-PAA5x5 Link Budget - Detailed.csv'] 53 | 54 | labels = ["2x2","3x3","4x4","5x5"] 55 | 56 | 57 | gs = gridspec.GridSpec(2, 1) 58 | plt.figure() 59 | ax0 = plt.subplot(gs[0]) 60 | ax1 = plt.subplot(gs[1],sharex=ax0) 61 | 62 | 63 | t=[] 64 | snr_fixed = [] 65 | eirp_fixed = [] 66 | snr_paa = [] 67 | eirp_paa = [] 68 | for i,label in enumerate(labels): 69 | color = cmap(float(i) / 4) 70 | 71 | d_fixed = pandas.read_csv(directory + filenames_fixed[i], 72 | delimiter=',', 73 | parse_dates=["Time (UTCG)"], 74 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in 75 | dates]) 76 | 77 | t = d_fixed["Time (UTCG)"] 78 | snr_fixed.append(d_fixed["C/N (dB)"]) 79 | eirp_fixed.append(d_fixed["EIRP (dBW)"]) 80 | 81 | ax0.plot(r,eirp_fixed[i],label=label,c=color) 82 | ax1.plot(r,snr_fixed[i],label=label,c=color) 83 | 84 | 85 | 86 | d_paa = pandas.read_csv(directory + filenames_paa[i], 87 | delimiter=',', 88 | parse_dates=["Time (UTCG)"], 89 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d 90 | in 91 | dates]) 92 | 93 | t = d_paa["Time (UTCG)"] 94 | snr_paa.append(d_paa["C/N (dB)"]) 95 | eirp_paa.append(d_paa["EIRP (dBW)"]) 96 | 97 | ax0.plot(r,eirp_paa[i],"--",label=label,c=color) 98 | ax1.plot(r,snr_paa[i],"--",label=label,c=color) 99 | 100 | 101 | 102 | ax1.set_ylabel("SNR [dB]") 103 | ax0.set_ylabel("EIRP [dBW]") 104 | 105 | ax0.grid(True) 106 | ax1.grid(True) 107 | plt.legend(loc='upper right') 108 | 109 | 110 | dic = {"Time": t, 111 | "Range": r, 112 | "SNR Fixed2x2": snr_fixed[0], 113 | "SNR PAA2x2": snr_paa[0], 114 | "EIRP Fixed2x2": eirp_fixed[0], 115 | "EIRP PAA2x2": eirp_paa[0], 116 | "SNR Fixed3x3": snr_fixed[1], 117 | "SNR PAA3x3": snr_paa[1], 118 | "EIRP Fixed3x3": eirp_fixed[1], 119 | "EIRP PAA3x3": eirp_paa[1], 120 | "SNR Fixed4x4": snr_fixed[2], 121 | "SNR PAA4x4": snr_paa[2], 122 | "EIRP Fixed4x4": eirp_fixed[2], 123 | "EIRP PAA4x4": eirp_paa[2], 124 | "SNR Fixed5x5": snr_fixed[3], 125 | "SNR PAA5x5": snr_paa[3], 126 | "EIRP Fixed5x5": eirp_fixed[3], 127 | "EIRP PAA5x5": eirp_paa[3]} 128 | 129 | df = pandas.DataFrame(data=dic) 130 | df.to_csv(directory + "comparison_fixed_paa.txt", index=None, sep=",", float_format='%.2f') 131 | 132 | plt.show() 133 | -------------------------------------------------------------------------------- /plotCSTHeatmap.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | import matplotlib.ticker as ticker 14 | plt.style.use('seaborn-whitegrid') 15 | plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | 20 | 21 | """ 22 | Plot Heatmaps (Theta/Phi) of the different antenna performance indicators 23 | """ 24 | 25 | 26 | if __name__=="__main__": 27 | 28 | plot_directory = "../plots/steering_sweep_results/" 29 | 30 | directories = ["../sim_data/5x5/chebychev/", 31 | "../sim_data/4x4/chebychev/", 32 | "../sim_data/3x3/chebychev/", 33 | "../sim_data/2x2/chebychev/", 34 | "../sim_data/5x5/uniform/", 35 | "../sim_data/4x4/uniform/", 36 | "../sim_data/3x3/uniform/", 37 | "../sim_data/2x2/uniform/", 38 | "../sim_data/5x5/binomial/", 39 | "../sim_data/4x4/binomial/", 40 | "../sim_data/3x3/binomial/", 41 | "../sim_data/2x2/binomial/"] 42 | 43 | directories = ["../sim_data/2x2/uniform/", 44 | "../sim_data/3x3/uniform/", 45 | "../sim_data/4x4/uniform/", 46 | "../sim_data/5x5/uniform/", 47 | "../sim_data/2x2/binomial/", 48 | "../sim_data/3x3/binomial/", 49 | "../sim_data/4x4/binomial/", 50 | "../sim_data/5x5/binomial/", 51 | "../sim_data/2x2/chebychev/", 52 | "../sim_data/3x3/chebychev/", 53 | "../sim_data/4x4/chebychev/", 54 | "../sim_data/5x5/chebychev/"] 55 | 56 | min_sll = [] 57 | max_sll = [] 58 | rms_sll = [] 59 | 60 | min_hpbw =[] 61 | max_hpbw =[] 62 | 63 | max_direction_offset =[] 64 | 65 | 66 | for directory in directories: 67 | 68 | 69 | 70 | # Max Directivity 71 | #filename = "Directivity,Theta=PAA_FA_SCANTHETA,Phi=PAA_FA_SCANPHI.txt" 72 | #filename = "Directivity,Phi=PAA_FA_SCANPHI,Max. Value.txt" 73 | #phi_scan, theta_scan, max_dir = np.loadtxt(directory + filename, skiprows=2, unpack=True) 74 | #phi_mesh = np.reshape(phi_scan,(-1,np.unique(phi_scan).size)) 75 | #theta_mesh = np.reshape(theta_scan,(-1,np.unique(phi_scan).size)) 76 | #max_dir_mesh = np.reshape(max_dir,(-1,np.unique(phi_scan).size)) 77 | 78 | 79 | 80 | filename = "result_navigator.csv" 81 | run_id, phi_scan, theta_scan = np.loadtxt('../sim_data/' + filename,delimiter=',', skiprows=1, unpack=True, ) 82 | phi_mesh = np.reshape(phi_scan, (-1, np.unique(phi_scan).size)) 83 | theta_mesh = np.reshape(theta_scan, (-1, np.unique(phi_scan).size)) 84 | 85 | 86 | 87 | # Side Lobe Level 88 | filename = "Directivity,Phi=PAA_FA_SCANPHI,Side Lobe Level.txt" 89 | 90 | run_id, sll_phi = np.loadtxt(directory + filename, skiprows=2, unpack=True) 91 | sll_phi_mesh = np.reshape(sll_phi, (-1, np.unique(phi_scan).size)) 92 | 93 | # HPBW 94 | filename = "Directivity,Phi=PAA_FA_SCANPHI,HPBW.txt" 95 | run_id, hpbw = np.loadtxt(directory + filename, skiprows=2, unpack=True) 96 | hpbw_mesh = np.reshape(hpbw, (-1, np.unique(phi_scan).size)) 97 | 98 | # Direction Offset 99 | filename = "Directivity,Phi=PAA_FA_SCANPHI,Main Lobe Direction.txt" 100 | run_id, main_lobe_direction = np.loadtxt(directory + filename, skiprows=2, unpack=True) 101 | main_lobe_direction = np.absolute(main_lobe_direction) 102 | direction_offset = np.reshape(main_lobe_direction, (-1, np.unique(phi_scan).size)) - theta_mesh 103 | 104 | # 105 | # fig2 = plt.figure() 106 | # fig2.canvas.set_window_title(directory + "- Maximum Side Lobe Level") 107 | # ax2 = plt.subplot() 108 | # fig3 = plt.figure() 109 | # fig3.canvas.set_window_title(directory + "- HPBW") 110 | # ax3 = plt.subplot() 111 | # fig4 = plt.figure() 112 | # fig4.canvas.set_window_title(directory + "- Steering Direction Offset") 113 | # ax4 = plt.subplot() 114 | # 115 | # 116 | # 117 | # c = ax2.pcolormesh(phi_mesh, theta_mesh, sll_phi_mesh, cmap='jet', vmin=sll_phi.min(), vmax=sll_phi.max()) 118 | # ax2.set_title("Maximum Side Lobe Level") 119 | # ax2.set_xlabel("Phi Scan [°]") 120 | # ax2.set_ylabel("Theta Scan [°]") 121 | # ax2.set_xticks(np.arange(0,200,20)) 122 | # ax2.set_yticks(np.arange(0,100,10)) 123 | # cbar = fig2.colorbar(c, ax=ax2) 124 | # cbar.set_label('dB', rotation=270,labelpad=20) 125 | # fig2.savefig(plot_directory + directory.split('/')[2] + "-" + directory.split('/')[3] + "- Maximum Side Lobe Level.png") 126 | # 127 | # c = ax3.pcolormesh(phi_mesh, theta_mesh, hpbw_mesh, cmap='jet', vmin=hpbw.min(), vmax=hpbw.max()) 128 | # ax3.set_title("HPBW") 129 | # ax3.set_xlabel("Phi Scan [°]") 130 | # ax3.set_ylabel("Theta Scan [°]") 131 | # ax3.set_xticks(np.arange(0,200,20)) 132 | # ax3.set_yticks(np.arange(0,100,10)) 133 | # cbar = fig3.colorbar(c, ax=ax3) 134 | # cbar.set_label('Degrees', rotation=270,labelpad=20) 135 | # fig3.savefig(plot_directory + directory.split('/')[2] + "-" + directory.split('/')[3] + "- HPBW.png") 136 | # 137 | # 138 | # c = ax4.pcolormesh(phi_mesh, theta_mesh, direction_offset, cmap='jet', vmin=direction_offset.min(), vmax=direction_offset.max()) 139 | # ax4.set_title("Steering Direction Offset") 140 | # ax4.set_xlabel("Phi Scan [°]") 141 | # ax4.set_ylabel("Theta Scan [°]") 142 | # ax4.set_xticks(np.arange(0,200,20)) 143 | # ax4.set_yticks(np.arange(0,100,10)) 144 | # cbar = fig4.colorbar(c, ax=ax4) 145 | # cbar.set_label('Degrees', rotation=270,labelpad=20) 146 | # fig4.savefig(plot_directory + directory.split('/')[2] + "-" + directory.split('/')[3] + "- Steering Direction Offset.png") 147 | 148 | 149 | min_sll.append(sll_phi.min()) 150 | max_sll.append(sll_phi.max()) 151 | rms_sll.append(np.sqrt(np.mean(sll_phi**2))) 152 | #rms_sll.append(np.mean(sll_phi)) 153 | 154 | min_hpbw.append(hpbw.min()) 155 | max_hpbw.append(hpbw.max()) 156 | max_direction_offset.append(direction_offset.min()) 157 | 158 | for i, n in enumerate(rms_sll): 159 | print("(" + str(i % 4) + "," + str(rms_sll[i]) + ")") 160 | #for i,n in enumerate(min_sll): 161 | # print("(" + str(i%4)+"," + str(min_sll[i]) + ")") 162 | #for i,n in enumerate(max_sll): 163 | # print("(" + str(i%4)+"," + str(max_sll[i]) + ")") 164 | #for i,n in enumerate(min_hpbw): 165 | # print("(" + str(i%4)+"," + str(min_hpbw[i]) + ")") 166 | #for i,n in enumerate(max_hpbw): 167 | # print("(" + str(i%4)+"," + str(max_hpbw[i]) + ")") 168 | #for i,n in enumerate(max_direction_offset): 169 | # print("(" + str(i%4)+"," + str(max_direction_offset[i]) + ")") 170 | 171 | #plt.show() 172 | 173 | 174 | -------------------------------------------------------------------------------- /stk_scripts/stk_attitude_noise.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | 11 | 12 | 13 | import numpy as np 14 | import os 15 | import matplotlib.pyplot as plt 16 | import matplotlib.ticker as ticker 17 | import matplotlib.dates as mdates 18 | #plt.style.use('classic') 19 | #plt.rc('text', usetex=True) 20 | plt.rc('font', family='serif') 21 | #plt.rcParams.update({'font.size': 14}) 22 | import matplotlib.gridspec as gridspec 23 | import pandas 24 | import datetime 25 | 26 | from util import * 27 | 28 | 29 | """ 30 | Apply Attitude noise to a STK Attitude File for a given scenario 31 | """ 32 | 33 | 34 | def euler_to_quaternion(yaw, pitch, roll): 35 | qx = np.sin(roll / 2) * np.cos(pitch / 2) * np.cos(yaw / 2) - np.cos(roll / 2) * np.sin(pitch / 2) * np.sin(yaw / 2) 36 | qy = np.cos(roll / 2) * np.sin(pitch / 2) * np.cos(yaw / 2) + np.sin(roll / 2) * np.cos(pitch / 2) * np.sin(yaw / 2) 37 | qz = np.cos(roll / 2) * np.cos(pitch / 2) * np.sin(yaw / 2) - np.sin(roll / 2) * np.sin(pitch / 2) * np.cos(yaw / 2) 38 | qw = np.cos(roll / 2) * np.cos(pitch / 2) * np.cos(yaw / 2) + np.sin(roll / 2) * np.sin(pitch / 2) * np.sin(yaw / 2) 39 | 40 | return np.asarray([qx, qy, qz, qw]).T 41 | 42 | def quaternion_to_euler(q): 43 | x = q[:,0] 44 | y = q[:,1] 45 | z = q[:,2] 46 | w = q[:,3] 47 | 48 | t0 = +2.0 * (w * x + y * z) 49 | t1 = +1.0 - 2.0 * (x * x + y * y) 50 | X = np.arctan2(t0, t1) 51 | 52 | t2 = +2.0 * (w * y - z * x) 53 | #t2[np.where(t2 > 1.0)] = 1.0 54 | #t2[np.where(t2 < -1.0)] = -1.0 55 | Y = np.arcsin(t2) 56 | 57 | t3 = +2.0 * (w * z + x * y) 58 | t4 = +1.0 - 2.0 * (y * y + z * z) 59 | Z = np.arctan2(t3, t4) 60 | 61 | return np.asarray([X, Y, Z]).T 62 | 63 | def quaternion_multiply(q1,q2): 64 | x1 = q1[:,0] 65 | y1 = q1[:,1] 66 | z1 = q1[:,2] 67 | w1 = q1[:,3] 68 | 69 | x2 = q2[:,0] 70 | y2 = q2[:,1] 71 | z2 = q2[:,2] 72 | w2 = q2[:,3] 73 | 74 | w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 75 | x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 76 | y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 77 | z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 78 | 79 | return np.asarray([x, y, z, w]).T 80 | 81 | def quaternion_normalize(q): 82 | x = q[:,0] 83 | y = q[:,1] 84 | z = q[:,2] 85 | w = q[:,3] 86 | 87 | norm = np.sqrt(np.power(x,2) + np.power(y,2) + np.power(z,2) + np.power(w,2)).reshape(-1,1) 88 | norm = np.tile(norm,(1,4)) 89 | return q/norm 90 | 91 | def fftnoise(f): 92 | f = np.array(f, dtype='complex') 93 | Np = (len(f) - 1) // 2 94 | phases = np.random.rand(Np) * 2 * np.pi 95 | phases = np.cos(phases) + 1j * np.sin(phases) 96 | f[1:Np+1] *= phases 97 | f[-1:-1-Np:-1] = np.conj(f[1:Np+1]) 98 | return np.fft.ifft(f).real 99 | 100 | def band_limited_noise(min_freq, max_freq, samples=1024, samplerate=1): 101 | freqs = np.abs(np.fft.fftfreq(samples, 1/samplerate)) 102 | f = np.zeros(samples) 103 | idx = np.where(np.logical_and(freqs>=min_freq, freqs<=max_freq))[0] 104 | f[idx] = 1 105 | return fftnoise(f) 106 | 107 | if __name__ == "__main__": 108 | output_directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/" 109 | 110 | std = [1,2,3,4,5,6,7,8,9,10] 111 | std = [30] 112 | 113 | for target_steering_noise_std in std: 114 | 115 | filenames = ["../../stk/Scenario_SAT_SAT_attitude_noise/LeadingSat.a", 116 | "../../stk/Scenario_SAT_SAT_attitude_noise/TrailingSat.a"] 117 | 118 | target_filenames = ["../../stk/Scenario_SAT_SAT_attitude_noise/LeadingSatNoiseSigma" 119 | +str(target_steering_noise_std) + ".a", 120 | "../../stk/Scenario_SAT_SAT_attitude_noise/TrailingSatNoiseSigma" 121 | +str(target_steering_noise_std) + ".a"] 122 | 123 | for i,filename in enumerate(filenames): 124 | target_filename = target_filenames[i] 125 | 126 | data = np.genfromtxt(filename, skip_header=25,skip_footer=2) 127 | timestamp = data[:, 0] 128 | quaternions = data[:, 1:] 129 | 130 | dt = np.diff(timestamp).mean() 131 | #print(dt) 132 | upper_frequency = 0.1 133 | noise_x = band_limited_noise(0,upper_frequency,samples=timestamp.size,samplerate=1/dt) 134 | noise_x = target_steering_noise_std*noise_x/noise_x.std() 135 | noise_y = band_limited_noise(0,upper_frequency,samples=timestamp.size,samplerate=1/dt) 136 | noise_y = target_steering_noise_std*noise_y/noise_y.std() 137 | 138 | #plt.plot(timestamp,noise_x) 139 | #plt.plot(timestamp,noise_y) 140 | 141 | noise_z = np.zeros(timestamp.shape) 142 | 143 | 144 | noise = euler_to_quaternion(deg2rad(noise_x),deg2rad(noise_y),deg2rad(noise_z)) 145 | noise = noise.reshape((-1,4)) 146 | 147 | quaternions = quaternion_normalize(quaternions) 148 | noise = quaternion_normalize(noise) 149 | 150 | quaternions_noisy = quaternion_multiply(noise,quaternions) 151 | quaternions_noisy = quaternion_normalize(quaternions_noisy) 152 | 153 | euler = quaternion_to_euler(quaternions) 154 | euler_noise = quaternion_to_euler(quaternions_noisy) 155 | 156 | # Plot 157 | gs = gridspec.GridSpec(3, 1) 158 | fig = plt.figure() 159 | fig.canvas.set_window_title("Sigma=" +str(target_steering_noise_std)) 160 | ax0 = plt.subplot(gs[0]) 161 | ax1 = plt.subplot(gs[1], sharex=ax0) 162 | ax2 = plt.subplot(gs[2], sharex=ax0) 163 | end = 1000 164 | 165 | ax0.plot(timestamp[:end], rad2deg(euler_noise[:end,0]),label="Noise") 166 | ax0.plot(timestamp[:end], rad2deg(euler[:end,0]), label="No Noise") 167 | 168 | ax0.set_ylabel("Roll [deg]") 169 | ax0.grid(True) 170 | 171 | ax1.plot(timestamp[:end], rad2deg(euler_noise[:end,1]),label="Noise") 172 | ax1.plot(timestamp[:end], rad2deg(euler[:end,1]), label="No Noise") 173 | 174 | ax1.set_ylabel("Pitch [deg]") 175 | ax1.grid(True) 176 | 177 | ax2.plot(timestamp[:end], rad2deg(euler_noise[:end,2]),label="Noise") 178 | ax2.plot(timestamp[:end], rad2deg(euler[:end,2]), label="No Noise") 179 | 180 | ax2.set_ylabel("Yaw [deg]") 181 | ax2.set_xlabel("Time [s]") 182 | ax2.grid(True) 183 | 184 | dic = {"Time": timestamp[:end], 185 | "RollNoise": rad2deg(euler_noise[:end,0]), 186 | "RollNoNoise": rad2deg(euler[:end,0]), 187 | "PitchNoise": rad2deg(euler_noise[:end,1]), 188 | "PitchNoNoise": rad2deg(euler[:end,1]), 189 | "YawNoise": rad2deg(euler_noise[:end, 2]), 190 | "YawNoNoise": rad2deg(euler[:end, 2])} 191 | 192 | df = pandas.DataFrame(data=dic) 193 | df.to_csv(output_directory + "attitude_noise_sigma="+str(target_steering_noise_std) + ".txt", 194 | index=None, sep=",", float_format='%.2f') 195 | 196 | #ax1.xaxis.set_major_formatter(mdates.DateFormatter('%d %b %Y ')) 197 | #ax1.xaxis.set_major_locator(mdates.DayLocator()) 198 | #plt.gcf().autofmt_xdate() 199 | plt.legend(loc="upper right") 200 | 201 | data_out = np.vstack([timestamp,quaternions_noisy.T]).T 202 | 203 | with open(filename) as sourcefile: 204 | header = [next(sourcefile) for x in range(25)] 205 | 206 | with open(target_filename,'w') as targetfile: 207 | targetfile.write(''.join(header)) 208 | with open(target_filename,'ab') as targetfile: 209 | np.savetxt(targetfile,data_out) 210 | targetfile.write("\nEND Attitude".encode()) 211 | 212 | plt.show() 213 | 214 | 215 | -------------------------------------------------------------------------------- /quantization_error_phase_plot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import os 11 | import matplotlib.pyplot as plt 12 | import matplotlib.ticker as ticker 13 | import matplotlib.dates as mdates 14 | #plt.style.use('classic') 15 | #plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | #plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | import pandas 20 | import datetime 21 | 22 | from util import * 23 | 24 | directories = ["C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/sim_data/quantization_analysis/5x5", 25 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/sim_data/quantization_analysis/4x4", 26 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/sim_data/quantization_analysis/3x3", 27 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/sim_data/quantization_analysis/2x2"] 28 | output_directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/sim_data/quantization_analysis/" 29 | 30 | cuts_file = 'Directivity,Phi=0.txt' 31 | hpbw_file = 'Realized Gain,Phi=PAA_FA_SCANPHI, HPBW.txt' 32 | mainlobe_file = 'Realized Gain,Phi=PAA_FA_SCANPHI, MainLobeDirection.txt' 33 | maxvalue_file = 'Realized Gain,Phi=PAA_FA_SCANPHI, MaxValue.txt' 34 | sll_file = 'Realized Gain,Phi=PAA_FA_SCANPHI, SLL.txt' 35 | value_file = 'Realized Gain,Theta=PAA_FA_SCANTHETA,Phi=PAA_FA_SCANPHI.txt' 36 | resnav_file = "result_navigator.csv" 37 | 38 | arraysize = ["5x5","4x4","3x3","2x2"] 39 | 40 | for k,directory in enumerate(directories): 41 | ### Cuts ### 42 | with open(directory + "_analog/" + cuts_file, newline='') as f: 43 | res = [] 44 | start = 0 45 | for line in f: 46 | 47 | if "----------------------------------------------------------------------" in line: 48 | 49 | start = 1 50 | elif line in ['\n', '\r\n']: 51 | start = 0 52 | elif start: 53 | res.append(list(map(float, line.split()))) 54 | results_analog = np.asarray(res) 55 | results_analog = np.split(results_analog, 11) 56 | 57 | 58 | 59 | with open(directory + "_digital/" + cuts_file, newline='') as f: 60 | res = [] 61 | start = 0 62 | for line in f: 63 | 64 | if "----------------------------------------------------------------------" in line: 65 | 66 | start = 1 67 | elif line in ['\n', '\r\n']: 68 | start = 0 69 | elif start: 70 | res.append(list(map(float, line.split()))) 71 | results = np.asarray(res) 72 | results = np.split(results, 66) 73 | 74 | 75 | result_navigator = np.genfromtxt(directory + "_digital/" + resnav_file,skip_header=1,delimiter=",") 76 | 77 | 78 | fig = plt.figure() 79 | ax = plt.subplot(111) 80 | ax.plot(results_analog[0][:, 0], results_analog[0][:, 1],label="Theta=45" + ",Analog") 81 | 82 | indices = np.arange(0,6,1) 83 | for idx in indices: 84 | ax.plot(results[idx][:,0],results[idx][:,1],label="Theta=" + str(result_navigator[idx,1])+ ",Bits=" + str(result_navigator[idx,2])) 85 | 86 | plt.legend() 87 | 88 | fig = plt.figure() 89 | ax = plt.subplot(111) 90 | ax.plot(results_analog[10][:, 0], results_analog[10][:, 1],label="Theta=90" + ",Analog") 91 | 92 | indices = np.arange(15,66,10) 93 | for idx in indices: 94 | ax.plot(results[idx][:,0],results[idx][:,1],label="Theta=" + str(result_navigator[idx,1])+ ",Bits=" + str(result_navigator[idx,2])) 95 | 96 | 97 | plt.legend() 98 | 99 | fig = plt.figure() 100 | ax = plt.subplot(111) 101 | ax.plot(results_analog[3][:, 0], results_analog[3][:, 1],label="Theta=20" + ",Analog") 102 | 103 | indices = np.arange(8,66,10) 104 | for idx in indices: 105 | ax.plot(results[idx][:,0],results[idx][:,1],label="Theta=" + str(result_navigator[idx,1])+ ",Bits=" + str(result_navigator[idx,2])) 106 | 107 | 108 | plt.legend() 109 | #plt.show() 110 | 111 | dic = {"Theta": results[8][:,0], 112 | "Analog": results_analog[3][:, 1], 113 | "Bit1": results[8][:,1], 114 | "Bit2": results[18][:, 1], 115 | "Bit3": results[28][:, 1], 116 | "Bit4": results[38][:, 1], 117 | "Bit5": results[48][:, 1], 118 | "Bit6": results[58][:, 1]} 119 | 120 | df = pandas.DataFrame(data=dic) 121 | df.to_csv(output_directory + "quantization_cuts_theta=20," + arraysize[k] + ".txt", 122 | index=None, sep=",", float_format='%.2f') 123 | 124 | dic = {"Theta": results[11][:,0], 125 | "Analog": results_analog[6][:, 1], 126 | "Bit1": results[11][:,1], 127 | "Bit2": results[21][:, 1], 128 | "Bit3": results[31][:, 1], 129 | "Bit4": results[41][:, 1], 130 | "Bit5": results[51][:, 1], 131 | "Bit6": results[61][:, 1]} 132 | 133 | df = pandas.DataFrame(data=dic) 134 | df.to_csv(output_directory + "quantization_cuts_theta=50," + arraysize[k] + ".txt", 135 | index=None, sep=",", float_format='%.2f') 136 | 137 | dic = {"Theta": results[15][:,0], 138 | "Analog": results_analog[10][:, 1], 139 | "Bit1": results[15][:,1], 140 | "Bit2": results[25][:, 1], 141 | "Bit3": results[35][:, 1], 142 | "Bit4": results[45][:, 1], 143 | "Bit5": results[55][:, 1], 144 | "Bit6": results[65][:, 1]} 145 | 146 | df = pandas.DataFrame(data=dic) 147 | df.to_csv(output_directory + "quantization_cuts_theta=90," + arraysize[k] + ".txt", 148 | index=None, sep=",", float_format='%.2f') 149 | 150 | ### Single Value Plots ### 151 | file = directory + "_digital/" + value_file 152 | value = np.genfromtxt(file,skip_header=2) 153 | value_bits = np.split(value[6:,1],6) 154 | 155 | file_analog = directory + "_analog/" + value_file 156 | value_analog = np.genfromtxt(file_analog,skip_header=2) 157 | mask = np.ones(value_analog.shape[0],dtype=bool) 158 | mask[5] = 0 159 | value_analog = value_analog[mask] 160 | 161 | 162 | fig = plt.figure() 163 | ax = plt.subplot(111) 164 | ax.plot(value_analog[:,1],label="Analog") 165 | for i in range(0,6): 166 | ax.plot(value_bits[i],label="Bits=" + str(i+1)) 167 | plt.legend() 168 | 169 | 170 | file = directory + "_digital/" + mainlobe_file 171 | mainlobe_direction = np.genfromtxt(file,skip_header=2) 172 | mainlobe_direction_bits = np.split(mainlobe_direction[6:,1],6) 173 | 174 | file_analog = directory + "_analog/" + mainlobe_file 175 | mainlobe_analog = np.genfromtxt(file_analog,skip_header=2) 176 | mask = np.ones(mainlobe_analog.shape[0],dtype=bool) 177 | mask[5] = 0 178 | mainlobe_analog = mainlobe_analog[mask] 179 | 180 | 181 | 182 | fig = plt.figure() 183 | ax = plt.subplot(111) 184 | ax.plot(mainlobe_analog[:,1] - np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90]), label="Analog") 185 | 186 | for i in range(0,6): 187 | ax.plot(mainlobe_direction_bits[i] - np.array([0, 10 ,20 ,30 ,40 ,50 ,60 ,70 ,80,90]),label="Bits=" + str(i+1)) 188 | plt.legend() 189 | 190 | dic = {"Theta": np.arange(0,100,10), 191 | "Analog": value_analog[:, 1], 192 | "Bit1": value_bits[0], 193 | "Bit2": value_bits[1], 194 | "Bit3": value_bits[2], 195 | "Bit4": value_bits[3], 196 | "Bit5": value_bits[4], 197 | "Bit6": value_bits[5]} 198 | 199 | df = pandas.DataFrame(data=dic) 200 | df.to_csv(output_directory + "realized_gain_in_steering_direction," + arraysize[k] + ".txt", 201 | index=None, sep=",", float_format='%.2f') 202 | 203 | 204 | theta = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) 205 | dic = {"Theta": theta, 206 | "Analog": mainlobe_analog[:,1] - theta, 207 | "Bit1": mainlobe_direction_bits[0] - theta, 208 | "Bit2": mainlobe_direction_bits[1] - theta, 209 | "Bit3": mainlobe_direction_bits[2] - theta, 210 | "Bit4": mainlobe_direction_bits[3] - theta, 211 | "Bit5": mainlobe_direction_bits[4] - theta, 212 | "Bit6": mainlobe_direction_bits[5] - theta} 213 | 214 | df = pandas.DataFrame(data=dic) 215 | df.to_csv(output_directory + "steering_offset," + arraysize[k] + ".txt", 216 | index=None, sep=",", float_format='%.2f') 217 | 218 | 219 | 220 | 221 | plt.show() 222 | 223 | -------------------------------------------------------------------------------- /sim/directivity_patch_sim.txt: -------------------------------------------------------------------------------- 1 | Theta,Dir Sim Phi0,Dir Sim Phi90 2 | -180.00,-22.15,-22.15 3 | -179.00,-22.16,-22.16 4 | -178.00,-22.20,-22.19 5 | -177.00,-22.23,-22.21 6 | -176.00,-22.28,-22.25 7 | -175.00,-22.35,-22.30 8 | -174.00,-22.44,-22.35 9 | -173.00,-22.53,-22.43 10 | -172.00,-22.65,-22.51 11 | -171.00,-22.78,-22.60 12 | -170.00,-22.93,-22.71 13 | -169.00,-23.10,-22.82 14 | -168.00,-23.28,-22.94 15 | -167.00,-23.49,-23.08 16 | -166.00,-23.70,-23.21 17 | -165.00,-23.94,-23.37 18 | -164.00,-24.20,-23.53 19 | -163.00,-24.48,-23.69 20 | -162.00,-24.78,-23.87 21 | -161.00,-25.11,-24.06 22 | -160.00,-25.46,-24.24 23 | -159.00,-25.83,-24.42 24 | -158.00,-26.24,-24.62 25 | -157.00,-26.67,-24.81 26 | -156.00,-27.13,-25.01 27 | -155.00,-27.63,-25.20 28 | -154.00,-28.16,-25.38 29 | -153.00,-28.73,-25.56 30 | -152.00,-29.34,-25.72 31 | -151.00,-30.00,-25.88 32 | -150.00,-30.69,-26.02 33 | -149.00,-31.44,-26.14 34 | -148.00,-32.22,-26.23 35 | -147.00,-33.06,-26.31 36 | -146.00,-33.94,-26.35 37 | -145.00,-34.85,-26.37 38 | -144.00,-35.79,-26.36 39 | -143.00,-36.72,-26.32 40 | -142.00,-37.63,-26.24 41 | -141.00,-38.45,-26.14 42 | -140.00,-39.14,-26.01 43 | -139.00,-39.62,-25.85 44 | -138.00,-39.86,-25.66 45 | -137.00,-39.80,-25.46 46 | -136.00,-39.46,-25.23 47 | -135.00,-38.87,-24.98 48 | -134.00,-38.07,-24.71 49 | -133.00,-37.15,-24.43 50 | -132.00,-36.14,-24.15 51 | -131.00,-35.11,-23.85 52 | -130.00,-34.07,-23.55 53 | -129.00,-33.06,-23.24 54 | -128.00,-32.09,-22.93 55 | -127.00,-31.16,-22.62 56 | -126.00,-30.27,-22.30 57 | -125.00,-29.43,-21.99 58 | -124.00,-28.63,-21.67 59 | -123.00,-27.87,-21.37 60 | -122.00,-27.15,-21.06 61 | -121.00,-26.47,-20.76 62 | -120.00,-25.82,-20.45 63 | -119.00,-25.20,-20.16 64 | -118.00,-24.61,-19.86 65 | -117.00,-24.04,-19.58 66 | -116.00,-23.51,-19.28 67 | -115.00,-22.98,-19.00 68 | -114.00,-22.49,-18.72 69 | -113.00,-22.00,-18.44 70 | -112.00,-21.54,-18.17 71 | -111.00,-21.09,-17.90 72 | -110.00,-20.66,-17.64 73 | -109.00,-20.24,-17.37 74 | -108.00,-19.83,-17.11 75 | -107.00,-19.43,-16.85 76 | -106.00,-19.04,-16.60 77 | -105.00,-18.67,-16.34 78 | -104.00,-18.30,-16.10 79 | -103.00,-17.94,-15.85 80 | -102.00,-17.58,-15.60 81 | -101.00,-17.24,-15.35 82 | -100.00,-16.90,-15.11 83 | -99.00,-16.56,-14.87 84 | -98.00,-16.24,-14.63 85 | -97.00,-15.92,-14.39 86 | -96.00,-15.60,-14.16 87 | -95.00,-15.29,-13.92 88 | -94.00,-14.98,-13.69 89 | -93.00,-14.68,-13.46 90 | -92.00,-14.38,-13.23 91 | -91.00,-14.09,-12.99 92 | -90.00,-13.80,-12.76 93 | -89.00,-13.51,-12.54 94 | -88.00,-13.23,-12.31 95 | -87.00,-12.95,-12.08 96 | -86.00,-12.68,-11.86 97 | -85.00,-12.40,-11.63 98 | -84.00,-12.13,-11.41 99 | -83.00,-11.87,-11.19 100 | -82.00,-11.60,-10.96 101 | -81.00,-11.34,-10.74 102 | -80.00,-11.08,-10.52 103 | -79.00,-10.83,-10.30 104 | -78.00,-10.58,-10.08 105 | -77.00,-10.33,-9.87 106 | -76.00,-10.08,-9.65 107 | -75.00,-9.83,-9.44 108 | -74.00,-9.59,-9.22 109 | -73.00,-9.35,-9.01 110 | -72.00,-9.12,-8.80 111 | -71.00,-8.88,-8.59 112 | -70.00,-8.65,-8.38 113 | -69.00,-8.42,-8.17 114 | -68.00,-8.20,-7.97 115 | -67.00,-7.97,-7.76 116 | -66.00,-7.75,-7.56 117 | -65.00,-7.53,-7.36 118 | -64.00,-7.32,-7.16 119 | -63.00,-7.10,-6.96 120 | -62.00,-6.89,-6.76 121 | -61.00,-6.69,-6.57 122 | -60.00,-6.48,-6.38 123 | -59.00,-6.28,-6.19 124 | -58.00,-6.08,-6.00 125 | -57.00,-5.88,-5.81 126 | -56.00,-5.68,-5.62 127 | -55.00,-5.49,-5.44 128 | -54.00,-5.30,-5.26 129 | -53.00,-5.12,-5.08 130 | -52.00,-4.94,-4.91 131 | -51.00,-4.76,-4.73 132 | -50.00,-4.58,-4.56 133 | -49.00,-4.40,-4.39 134 | -48.00,-4.23,-4.23 135 | -47.00,-4.06,-4.06 136 | -46.00,-3.90,-3.90 137 | -45.00,-3.74,-3.74 138 | -44.00,-3.58,-3.59 139 | -43.00,-3.42,-3.43 140 | -42.00,-3.27,-3.28 141 | -41.00,-3.12,-3.14 142 | -40.00,-2.97,-2.99 143 | -39.00,-2.83,-2.85 144 | -38.00,-2.69,-2.71 145 | -37.00,-2.55,-2.58 146 | -36.00,-2.42,-2.44 147 | -35.00,-2.29,-2.31 148 | -34.00,-2.16,-2.19 149 | -33.00,-2.04,-2.07 150 | -32.00,-1.92,-1.95 151 | -31.00,-1.80,-1.83 152 | -30.00,-1.69,-1.72 153 | -29.00,-1.58,-1.61 154 | -28.00,-1.47,-1.50 155 | -27.00,-1.37,-1.40 156 | -26.00,-1.27,-1.30 157 | -25.00,-1.17,-1.20 158 | -24.00,-1.08,-1.11 159 | -23.00,-0.99,-1.02 160 | -22.00,-0.90,-0.93 161 | -21.00,-0.82,-0.85 162 | -20.00,-0.75,-0.77 163 | -19.00,-0.68,-0.70 164 | -18.00,-0.61,-0.62 165 | -17.00,-0.54,-0.56 166 | -16.00,-0.48,-0.50 167 | -15.00,-0.42,-0.44 168 | -14.00,-0.36,-0.38 169 | -13.00,-0.31,-0.33 170 | -12.00,-0.26,-0.28 171 | -11.00,-0.22,-0.24 172 | -10.00,-0.18,-0.19 173 | -9.00,-0.15,-0.15 174 | -8.00,-0.11,-0.12 175 | -7.00,-0.09,-0.10 176 | -6.00,-0.06,-0.07 177 | -5.00,-0.04,-0.05 178 | -4.00,-0.02,-0.03 179 | -3.00,-0.02,-0.02 180 | -2.00,-0.01,-0.01 181 | -1.00,0.00,0.00 182 | -0.00,0.00,0.00 183 | 0.00,0.00,0.00 184 | 1.00,0.00,0.00 185 | 2.00,-0.01,-0.01 186 | 3.00,-0.02,-0.02 187 | 4.00,-0.03,-0.03 188 | 5.00,-0.06,-0.05 189 | 6.00,-0.08,-0.07 190 | 7.00,-0.10,-0.10 191 | 8.00,-0.14,-0.12 192 | 9.00,-0.17,-0.15 193 | 10.00,-0.21,-0.19 194 | 11.00,-0.25,-0.24 195 | 12.00,-0.29,-0.28 196 | 13.00,-0.34,-0.33 197 | 14.00,-0.40,-0.38 198 | 15.00,-0.45,-0.44 199 | 16.00,-0.52,-0.50 200 | 17.00,-0.58,-0.56 201 | 18.00,-0.65,-0.62 202 | 19.00,-0.73,-0.70 203 | 20.00,-0.80,-0.77 204 | 21.00,-0.88,-0.85 205 | 22.00,-0.97,-0.93 206 | 23.00,-1.05,-1.02 207 | 24.00,-1.14,-1.11 208 | 25.00,-1.24,-1.20 209 | 26.00,-1.34,-1.30 210 | 27.00,-1.44,-1.40 211 | 28.00,-1.55,-1.50 212 | 29.00,-1.66,-1.61 213 | 30.00,-1.77,-1.72 214 | 31.00,-1.89,-1.83 215 | 32.00,-2.01,-1.95 216 | 33.00,-2.13,-2.07 217 | 34.00,-2.26,-2.19 218 | 35.00,-2.39,-2.31 219 | 36.00,-2.52,-2.44 220 | 37.00,-2.66,-2.58 221 | 38.00,-2.80,-2.71 222 | 39.00,-2.94,-2.85 223 | 40.00,-3.09,-2.99 224 | 41.00,-3.24,-3.14 225 | 42.00,-3.40,-3.28 226 | 43.00,-3.55,-3.43 227 | 44.00,-3.71,-3.59 228 | 45.00,-3.88,-3.74 229 | 46.00,-4.04,-3.90 230 | 47.00,-4.22,-4.06 231 | 48.00,-4.39,-4.23 232 | 49.00,-4.56,-4.39 233 | 50.00,-4.74,-4.56 234 | 51.00,-4.93,-4.73 235 | 52.00,-5.11,-4.91 236 | 53.00,-5.30,-5.08 237 | 54.00,-5.49,-5.26 238 | 55.00,-5.69,-5.44 239 | 56.00,-5.89,-5.62 240 | 57.00,-6.09,-5.81 241 | 58.00,-6.29,-6.00 242 | 59.00,-6.50,-6.19 243 | 60.00,-6.71,-6.38 244 | 61.00,-6.92,-6.57 245 | 62.00,-7.14,-6.76 246 | 63.00,-7.36,-6.96 247 | 64.00,-7.58,-7.16 248 | 65.00,-7.80,-7.36 249 | 66.00,-8.03,-7.56 250 | 67.00,-8.26,-7.76 251 | 68.00,-8.49,-7.97 252 | 69.00,-8.73,-8.17 253 | 70.00,-8.97,-8.38 254 | 71.00,-9.21,-8.59 255 | 72.00,-9.46,-8.80 256 | 73.00,-9.71,-9.01 257 | 74.00,-9.95,-9.22 258 | 75.00,-10.21,-9.44 259 | 76.00,-10.47,-9.65 260 | 77.00,-10.73,-9.87 261 | 78.00,-10.99,-10.08 262 | 79.00,-11.26,-10.30 263 | 80.00,-11.53,-10.52 264 | 81.00,-11.80,-10.74 265 | 82.00,-12.08,-10.96 266 | 83.00,-12.36,-11.19 267 | 84.00,-12.64,-11.41 268 | 85.00,-12.93,-11.63 269 | 86.00,-13.23,-11.86 270 | 87.00,-13.52,-12.08 271 | 88.00,-13.82,-12.31 272 | 89.00,-14.13,-12.54 273 | 90.00,-14.44,-12.76 274 | 91.00,-14.75,-12.99 275 | 92.00,-15.07,-13.23 276 | 93.00,-15.39,-13.46 277 | 94.00,-15.72,-13.69 278 | 95.00,-16.06,-13.92 279 | 96.00,-16.40,-14.16 280 | 97.00,-16.75,-14.39 281 | 98.00,-17.10,-14.63 282 | 99.00,-17.47,-14.87 283 | 100.00,-17.84,-15.11 284 | 101.00,-18.22,-15.35 285 | 102.00,-18.61,-15.60 286 | 103.00,-19.01,-15.85 287 | 104.00,-19.42,-16.10 288 | 105.00,-19.84,-16.34 289 | 106.00,-20.27,-16.60 290 | 107.00,-20.72,-16.85 291 | 108.00,-21.18,-17.11 292 | 109.00,-21.65,-17.37 293 | 110.00,-22.15,-17.64 294 | 111.00,-22.67,-17.90 295 | 112.00,-23.21,-18.17 296 | 113.00,-23.78,-18.44 297 | 114.00,-24.37,-18.72 298 | 115.00,-24.99,-19.00 299 | 116.00,-25.65,-19.28 300 | 117.00,-26.34,-19.58 301 | 118.00,-27.08,-19.86 302 | 119.00,-27.87,-20.16 303 | 120.00,-28.72,-20.45 304 | 121.00,-29.64,-20.76 305 | 122.00,-30.63,-21.06 306 | 123.00,-31.72,-21.37 307 | 124.00,-32.91,-21.67 308 | 125.00,-34.24,-21.99 309 | 126.00,-35.73,-22.30 310 | 127.00,-37.42,-22.62 311 | 128.00,-39.36,-22.93 312 | 129.00,-40.00,-23.24 313 | 130.00,-40.00,-23.55 314 | 131.00,-40.00,-23.85 315 | 132.00,-40.00,-24.15 316 | 133.00,-40.00,-24.43 317 | 134.00,-40.00,-24.71 318 | 135.00,-40.00,-24.98 319 | 136.00,-40.00,-25.23 320 | 137.00,-40.00,-25.46 321 | 138.00,-40.00,-25.66 322 | 139.00,-40.00,-25.85 323 | 140.00,-40.00,-26.01 324 | 141.00,-40.00,-26.14 325 | 142.00,-40.00,-26.24 326 | 143.00,-40.00,-26.32 327 | 144.00,-38.31,-26.36 328 | 145.00,-36.72,-26.37 329 | 146.00,-35.32,-26.35 330 | 147.00,-34.09,-26.31 331 | 148.00,-33.00,-26.23 332 | 149.00,-32.01,-26.14 333 | 150.00,-31.12,-26.02 334 | 151.00,-30.31,-25.88 335 | 152.00,-29.57,-25.72 336 | 153.00,-28.90,-25.56 337 | 154.00,-28.28,-25.38 338 | 155.00,-27.71,-25.20 339 | 156.00,-27.18,-25.01 340 | 157.00,-26.70,-24.81 341 | 158.00,-26.25,-24.62 342 | 159.00,-25.83,-24.42 343 | 160.00,-25.44,-24.24 344 | 161.00,-25.09,-24.06 345 | 162.00,-24.76,-23.87 346 | 163.00,-24.45,-23.69 347 | 164.00,-24.17,-23.53 348 | 165.00,-23.90,-23.37 349 | 166.00,-23.67,-23.21 350 | 167.00,-23.45,-23.08 351 | 168.00,-23.25,-22.94 352 | 169.00,-23.06,-22.82 353 | 170.00,-22.91,-22.71 354 | 171.00,-22.76,-22.60 355 | 172.00,-22.63,-22.51 356 | 173.00,-22.52,-22.43 357 | 174.00,-22.42,-22.35 358 | 175.00,-22.34,-22.30 359 | 176.00,-22.27,-22.25 360 | 177.00,-22.22,-22.21 361 | 178.00,-22.19,-22.19 362 | 179.00,-22.16,-22.16 363 | 180.00,-22.15,-22.15 364 | -------------------------------------------------------------------------------- /stk_scripts/stk_datarate_sweep_plots.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import os 11 | import matplotlib.pyplot as plt 12 | import matplotlib.ticker as ticker 13 | import matplotlib.dates as mdates 14 | #plt.style.use('classic') 15 | #plt.rc('text', usetex=True) 16 | plt.rc('font', family='serif') 17 | #plt.rcParams.update({'font.size': 14}) 18 | import matplotlib.gridspec as gridspec 19 | import pandas 20 | import datetime 21 | 22 | def dateparse (time_in_secs): 23 | return datetime.datetime.fromtimestamp(float(time_in_secs)) 24 | 25 | 26 | """ 27 | Plot the results of the STK datarate sweep 28 | """ 29 | 30 | 31 | if __name__ == "__main__": 32 | 33 | directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2gnd/varying_datarate/" 34 | 35 | 36 | filenames = ['Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_0.5Mbps.csv', 37 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_1Mbps.csv', 38 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_2Mbps.csv', 39 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_5Mbps.csv', 40 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_10Mbps.csv', 41 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_20Mbps.csv', 42 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_50Mbps.csv', 43 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_100Mbps.csv', 44 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_150Mbps.csv', 45 | 'Satellite-DelphiniX-Transmitter-PAA2x2-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_200Mbps.csv'] 46 | 47 | 48 | 49 | data = [] 50 | 51 | for filename in filenames: 52 | d = pandas.read_csv(directory + filename, 53 | delimiter=',', 54 | parse_dates=["Time (UTCG)"], 55 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in 56 | dates]) 57 | data.append(d) 58 | 59 | SNR_avg = [] 60 | SNR_std = [] 61 | for i, d in enumerate(data): 62 | accesses = np.unique(d["Access Number"].values) 63 | access_avg = d.groupby(["Access Number"]).mean() 64 | access_max = d.groupby(["Access Number"]).max() 65 | access_min = d.groupby(["Access Number"]).min() 66 | access_std = d.groupby(["Access Number"]).std() 67 | SNR_avg.append(access_avg["C/N (dB)"].values[0:20].mean()) 68 | SNR_std.append(access_std["C/N (dB)"].values[0:20].mean()) 69 | 70 | SNR_avg_paa2 = np.asarray(SNR_avg) 71 | SNR_std_paa2 = np.asarray(SNR_std) 72 | 73 | 74 | 75 | filenames = ['Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_0.5Mbps.csv', 76 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_1Mbps.csv', 77 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_2Mbps.csv', 78 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_5Mbps.csv', 79 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_10Mbps.csv', 80 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_20Mbps.csv', 81 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_50Mbps.csv', 82 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_100Mbps.csv', 83 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_150Mbps.csv', 84 | 'Satellite-DelphiniX-Transmitter-PAA4x4-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_200Mbps.csv'] 85 | 86 | data = [] 87 | 88 | for filename in filenames: 89 | d = pandas.read_csv(directory + filename, 90 | delimiter=',', 91 | parse_dates=["Time (UTCG)"], 92 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in 93 | dates]) 94 | data.append(d) 95 | 96 | SNR_avg = [] 97 | SNR_std = [] 98 | for i, d in enumerate(data): 99 | accesses = np.unique(d["Access Number"].values) 100 | access_avg = d.groupby(["Access Number"]).mean() 101 | access_max = d.groupby(["Access Number"]).max() 102 | access_min = d.groupby(["Access Number"]).min() 103 | access_std = d.groupby(["Access Number"]).std() 104 | SNR_avg.append(access_avg["C/N (dB)"].values[0:20].mean()) 105 | SNR_std.append(access_std["C/N (dB)"].values[0:20].mean()) 106 | 107 | SNR_avg_paa4 = np.asarray(SNR_avg) 108 | SNR_std_paa4 = np.asarray(SNR_std) 109 | 110 | #### Patch Antenna ##### 111 | 112 | 113 | filenames = ['Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_0.5Mbps.csv', 114 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_1Mbps.csv', 115 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_2Mbps.csv', 116 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_5Mbps.csv', 117 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_10Mbps.csv', 118 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_20Mbps.csv', 119 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_50Mbps.csv', 120 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_100Mbps.csv', 121 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_150Mbps.csv', 122 | 'Satellite-DelphiniX-Transmitter-PatchTransmitter-To-Place-Aarhus-Receiver-Receiver2 LinkBudgetCustomComparison_200Mbps.csv'] 123 | 124 | data = [] 125 | 126 | for filename in filenames: 127 | d = pandas.read_csv(directory + filename, 128 | delimiter=',', 129 | parse_dates=["Time (UTCG)"], 130 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') for d in dates]) 131 | data.append(d) 132 | 133 | 134 | SNR_avg = [] 135 | SNR_std = [] 136 | for i,d in enumerate(data): 137 | accesses = np.unique(d["Access Number"].values) 138 | access_avg = d.groupby(["Access Number"]).mean() 139 | access_max = d.groupby(["Access Number"]).max() 140 | access_min = d.groupby(["Access Number"]).min() 141 | access_std = d.groupby(["Access Number"]).std() 142 | SNR_avg.append(access_avg["C/N (dB)"].values[0:20].mean()) 143 | SNR_std.append(access_std["C/N (dB)"].values[0:20].mean()) 144 | 145 | SNR_avg_patch = np.asarray(SNR_avg) 146 | SNR_std_patch = np.asarray(SNR_std) 147 | 148 | 149 | 150 | datarates = [0.5,1,2,5,10,20,50,100,150,200] 151 | 152 | gs = gridspec.GridSpec(1, 1) 153 | plt.figure() 154 | ax0 = plt.subplot(gs[0]) 155 | 156 | ax0.plot(datarates,SNR_avg_patch,".--",label="Patch") 157 | ax0.fill_between(datarates, 158 | SNR_avg_patch - SNR_std_patch, 159 | SNR_avg_patch + SNR_std_patch, 160 | alpha=0.3) 161 | ax0.plot(datarates,SNR_avg_paa2,".--",label="PAA2x2") 162 | ax0.fill_between(datarates, 163 | SNR_avg_paa2 - SNR_std_paa2, 164 | SNR_avg_paa2 + SNR_std_paa2, 165 | alpha=0.3) 166 | ax0.plot(datarates,SNR_avg_paa4,".--",label="PAA4x4") 167 | ax0.fill_between(datarates, 168 | SNR_avg_paa4 - SNR_std_paa4, 169 | SNR_avg_paa4 + SNR_std_paa4, 170 | alpha=0.3) 171 | ax0.set_ylabel("SNR [dB]") 172 | ax0.set_xlabel("Datarate [Mbps]") 173 | ax0.grid(True) 174 | plt.legend(loc='upper right') 175 | ax0.set_ylim([0,30]) 176 | 177 | dic = {"Data Rate":datarates, 178 | "SNR Patch": SNR_avg_patch, 179 | "SNR Patch Std": SNR_std_patch, 180 | "SNR PAA2x2": SNR_avg_paa2, 181 | "SNR PAA2x2 Std": SNR_std_paa2, 182 | "SNR PAA4x4": SNR_avg_paa4, 183 | "SNR PAA4x4 Std": SNR_std_paa4} 184 | 185 | df = pandas.DataFrame(data=dic) 186 | df.to_csv(directory + "snr_datarate_sweep.txt", index=None, sep=",", float_format='%.2f') 187 | 188 | plt.show() 189 | -------------------------------------------------------------------------------- /antenna_toolbox/RectangularArray.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | import matplotlib.pyplot as plt 9 | 10 | from matplotlib import cm 11 | 12 | from util import * 13 | from antenna_toolbox.Antenna import Antenna 14 | 15 | 16 | class RectangularArray(Antenna): 17 | """ Rectangular Phased Array""" 18 | 19 | def __init__(self,Nelements_x,Nelements_y,d_x,d_y,freq,arraytype="uniform",sidelobe_level=20): 20 | 21 | self.Nelements_x = Nelements_x 22 | self.Nelements_y = Nelements_y 23 | self.element_distance_x = d_x 24 | self.element_distance_y = d_y 25 | self.frequency = freq 26 | 27 | 28 | wavelength = 3e8/freq 29 | 30 | x = d_x * wavelength * np.linspace(-Nelements_x/2,Nelements_x/2,Nelements_x) 31 | y = d_y * wavelength * np.linspace(-Nelements_y/2,Nelements_y/2,Nelements_y) 32 | 33 | self.elements_y, self.elements_x = np.meshgrid(y,x) 34 | 35 | 36 | self.arraytype = arraytype 37 | 38 | if arraytype == "uniform": 39 | self.uniformWeights() 40 | elif arraytype == "binomial": 41 | self.binomialWeights() 42 | elif arraytype == "dt": 43 | self.dtWeights(sidelobe_level) 44 | 45 | def setElement(self,element): 46 | self.element = element 47 | 48 | def uniformWeights(self): 49 | """ Uniform Amplitude Weights """ 50 | self.weights = np.ones((self.Nelements_x,self.Nelements_y)) 51 | return self.weights 52 | 53 | def binomialWeights(self): 54 | n = self.Nelements_x-1 55 | m = self.Nelements_y-1 56 | 57 | w_x = [binomial(n,i) for i in range(n+1)] 58 | w_y = [binomial(m,i) for i in range(m+1)] 59 | 60 | w_y_grid,w_x_grid = np.meshgrid(w_y,w_x) 61 | self.weights = w_y_grid*w_x_grid 62 | return self.weights 63 | 64 | 65 | def dtWeights(self,sidelobe_level): 66 | """ Calculate Dolph-Tchebychev weights 67 | 68 | See Balanis, Antenna Theory,4ed, Chapter 6.8.4, p338 69 | 70 | """ 71 | R = db2lin(sidelobe_level) 72 | w_x = dolphTschebychev(self.Nelements_x,R) 73 | w_y = dolphTschebychev(self.Nelements_y,R) 74 | 75 | w_y_grid,w_x_grid = np.meshgrid(w_y,w_x) 76 | self.weights = w_y_grid*w_x_grid 77 | return self.weights 78 | 79 | 80 | def getField(self,theta_steering=0,phi_steering=0): 81 | """ Calculate Resulting Field (Element*AF) 82 | 83 | Uses the same step size and range for far-field calculation as the element. 84 | 85 | Args: 86 | theta_steering: Theta angle of beam steering direction 87 | phi_steering: Phi angle of beam steering direction 88 | 89 | Return: 90 | pattern: Combined Field Pattern 91 | """ 92 | e = self.element 93 | self.getArrayFactor(e.theta_start,e.theta_stop,e.theta_step,e.phi_start,e.phi_stop,e.phi_step,theta_steering,phi_steering) 94 | Ntheta = (e.theta_stop-e.theta_start)/e.theta_step + 1 95 | Nphi = (e.phi_stop-e.phi_start)/e.phi_step + 1 96 | 97 | theta_range = deg2rad(np.linspace(e.theta_start,e.theta_stop,Ntheta)) 98 | phi_range = deg2rad(np.linspace(e.phi_start,e.phi_stop,Nphi)) 99 | self.pattern = np.ones((theta_range.size,phi_range.size)) 100 | self.theta_mesh_pattern = np.ones((theta_range.size,phi_range.size)) 101 | self.phi_mesh_pattern = np.ones((theta_range.size,phi_range.size)) 102 | 103 | # Pattern Multiplication 104 | self.pattern = e.pattern*self.array_factor 105 | 106 | self.pattern=normalize(self.pattern) 107 | return self.pattern 108 | 109 | 110 | def getArrayFactor(self,theta_start,theta_stop,theta_step,phi_start,phi_stop,phi_step,theta_steering=0,phi_steering=0,pre_comp_phase=False): 111 | """ Calculate array factor 112 | 113 | Args: 114 | theta_step: Step size in Theta dimension 115 | phi_step: Step size in Phi dimension 116 | theta_steering: Direction of steered beam 117 | phi_steering: Direction of steered beam 118 | 119 | Return: 120 | array_factor: Array factor as ndarray in theta/phi-domain 121 | 122 | """ 123 | 124 | # Wavenumber 125 | k = 2*math.pi*self.frequency/3e8 126 | 127 | # Vectors to each element 128 | rx = self.elements_x.flatten() 129 | ry = self.elements_y.flatten() 130 | rz = np.zeros(self.elements_x.size) 131 | r = np.vstack((rx,ry,rz)) 132 | 133 | if pre_comp_phase == False: 134 | # Get element phase shifts from steering direction 135 | X0, Y0,Z0 = sph2cart(deg2rad(phi_steering),deg2rad(theta_steering),999) 136 | R0 = np.array((X0,Y0,Z0)) 137 | self.element_phase_shift = -k * R0 @ r 138 | self.element_phase_shift = np.reshape(self.element_phase_shift,self.elements_x.shape) 139 | 140 | self.Ntheta = (theta_stop-theta_start)/theta_step + 1 141 | self.Nphi = (phi_stop-phi_start)/phi_step + 1 142 | self.theta_step = theta_step 143 | self.phi_step = phi_step 144 | 145 | theta_range = deg2rad(np.linspace(theta_start,theta_stop,self.Ntheta)) 146 | phi_range = deg2rad(np.linspace(phi_start,phi_stop,self.Nphi)) 147 | self.array_factor = np.ones((theta_range.size,phi_range.size)) 148 | self.theta_mesh = np.ones((theta_range.size,phi_range.size)) 149 | self.phi_mesh = np.ones((theta_range.size,phi_range.size)) 150 | 151 | 152 | for t,theta in enumerate(theta_range): 153 | for p,phi in enumerate(phi_range): 154 | 155 | # Far field direction unit vector 156 | X,Y,Z= sph2cart(phi,theta,1) 157 | R = np.array((X,Y,Z)) 158 | 159 | psi = k * R @ r + self.element_phase_shift.flatten() 160 | self.array_factor[t,p] = np.real(np.dot(self.weights.flatten(),np.exp(1j*psi))) 161 | self.theta_mesh[t,p] = theta 162 | self.phi_mesh[t,p] = phi 163 | 164 | 165 | self.array_factor = normalize(self.array_factor) 166 | 167 | return self.array_factor 168 | 169 | 170 | def getArrayFactorCut(self,dim,angle): 171 | """ Calculate Cutting plane in Array Factor 172 | 173 | Args: 174 | dim: Cutting Dimension (theta/phi) 175 | angle: Angle of Cutting Plane 176 | 177 | Return: 178 | array_factor_cut: Array of AF values in cutting plane 179 | cut_angles: Angles in cutting plane 180 | 181 | """ 182 | if dim =="theta": 183 | t_idx=round(angle/self.theta_step) 184 | 185 | return self.array_factor[t_idx,:], self.phi_mesh[t_idx,:] 186 | 187 | 188 | elif dim =="phi": 189 | p_idx= round(angle/self.phi_step) 190 | offset = round(180/self.phi_step) 191 | array_factor_cut = np.concatenate([np.flip(self.array_factor[:,p_idx + offset]),self.array_factor[:,p_idx]]) 192 | cut_angles = np.concatenate([-np.flip(self.theta_mesh[:,p_idx + offset]),self.theta_mesh[:,p_idx]]) 193 | 194 | return array_factor_cut,cut_angles 195 | 196 | 197 | def plotArrayFactorCut(self,dim,angles,ax=None,logarithmic=True,polar=True,label="Cut"): 198 | """ Plot AF Cuts in given dimension 199 | 200 | Args: 201 | dim: Dimension of cut (Theta/Phi). 202 | angles: List of cutting angles. 203 | ax: Axis object to plot into 204 | logarithmic: Plot in dB 205 | polar: Polar plot 206 | 207 | Return: 208 | ax: Axes object 209 | """ 210 | 211 | if polar == True: 212 | if ax==None: 213 | fig = plt.figure() 214 | ax = fig.add_subplot(111,polar=True) 215 | 216 | ax.set_theta_zero_location('N') 217 | ax.grid(True) 218 | 219 | 220 | for angle in angles: 221 | 222 | array_factor_cut,cut_angles = self.getArrayFactorCut(dim,angle) 223 | 224 | if dim =="phi": 225 | ax.set_xticks(np.pi/180. * np.linspace(180, -180, 8, endpoint=False)) 226 | ax.set_thetalim(-np.pi, np.pi) 227 | 228 | if logarithmic == True: 229 | ax.plot(cut_angles,lin2db(array_factor_cut),label=label) 230 | ax.set_yticks(range(-60,0 , 10)) 231 | ax.set_ylim(-60, 0) 232 | else: 233 | ax.plot(cut_angles,array_factor_cut,label=label) 234 | ax.set_yticks(range(0,1 , 0.2)) 235 | ax.set_ylim(0, 1) 236 | 237 | elif polar == False: 238 | 239 | if ax==None: 240 | fig = plt.figure() 241 | ax = fig.add_subplot(111,polar=False) 242 | ax.grid(True) 243 | 244 | for angle in angles: 245 | 246 | array_factor_cut,cut_angles = self.getArrayFactorCut(dim,angle) 247 | 248 | if logarithmic == True: 249 | ax.plot(cut_angles,lin2db(array_factor_cut),label=label) 250 | ax.set_yticks(range(-60,0 , 10)) 251 | ax.set_ylim(-60, 0) 252 | else: 253 | ax.plot(cut_angles,array_factor_cut,label=label) 254 | ax.set_yticks(range(0,1 , 0.2)) 255 | ax.set_ylim(0, 1) 256 | 257 | plt.legend() 258 | 259 | return ax 260 | 261 | 262 | 263 | def plotArrayFactor3D(self,ax=None, logarithmic=True,log_range=-40): 264 | """ Plot 3D Array Factor 265 | 266 | Args: 267 | ax: Axis object to plot into 268 | logarithmic: Plot in dB 269 | 270 | Return: 271 | ax: Axes object 272 | """ 273 | 274 | 275 | if ax==None: 276 | fig = plt.figure() 277 | ax = fig.add_subplot(111, projection='3d') 278 | 279 | 280 | if logarithmic ==True: 281 | array_factor_db = lin2db(self.array_factor) 282 | array_factor_min = np.min(array_factor_db) 283 | array_factor_max = np.max(array_factor_db) 284 | e = array_factor_db - (log_range + array_factor_max) 285 | e[e<0.0] = 0.0 286 | else: 287 | e = self.array_factor 288 | array_factor_min = np.min(self.array_factor) 289 | array_factor_max = np.max(self.array_factor) 290 | 291 | x,y,z = sph2cart(self.phi_mesh,self.theta_mesh,e) 292 | 293 | ax.plot_surface(x, y, z,facecolors=cm.jet(self.array_factor)) 294 | plt.xlabel('X') 295 | plt.ylabel('Y') 296 | lim_left,lim_right = ax.get_zlim() 297 | ax.set_xlim(lim_left,lim_right) 298 | ax.set_ylim(lim_left,lim_right) 299 | 300 | m = cm.ScalarMappable(cmap=plt.cm.jet) 301 | if logarithmic==True: 302 | m.set_array(e+(log_range+array_factor_max)) 303 | else: 304 | m.set_array(e) 305 | plt.colorbar(m) 306 | 307 | return ax 308 | -------------------------------------------------------------------------------- /stk_scripts/stk_attitude_noise_sweep.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | import numpy as np 10 | import sys 11 | import os 12 | import matplotlib.pyplot as plt 13 | import matplotlib.ticker as ticker 14 | import matplotlib.dates as mdates 15 | #plt.style.use('classic') 16 | #plt.rc('text', usetex=True) 17 | plt.rc('font', family='serif') 18 | #plt.rcParams.update({'font.size': 14}) 19 | import matplotlib.gridspec as gridspec 20 | cmap = plt.get_cmap('jet') 21 | import pandas 22 | from util import * 23 | 24 | 25 | """ 26 | Parse and plot STK results, Attitude Noise Sweep 27 | """ 28 | 29 | if __name__ == "__main__": 30 | 31 | output_directory = "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/" 32 | directories = ["C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma1/", 33 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma2/", 34 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma3/", 35 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma4/", 36 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma5/", 37 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma6/", 38 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma7/", 39 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma8/", 40 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma9/", 41 | "C:/Users/hanne/Nextcloud/studies/au/courses/master_thesis/stk_analysis_results/sat2sat_noise/sigma10/"] 42 | 43 | 44 | 45 | filenames_fixed = ['Satellite-LeadingSat-Transmitter-Fixed2x2-To-Satellite-TrailingSat-Receiver-Fixed2x2 NoisyLinkBudgetAnalysis.csv', 46 | 'Satellite-LeadingSat-Transmitter-Fixed3x3-To-Satellite-TrailingSat-Receiver-Fixed3x3 NoisyLinkBudgetAnalysis.csv', 47 | 'Satellite-LeadingSat-Transmitter-Fixed4x4-To-Satellite-TrailingSat-Receiver-Fixed4x4 NoisyLinkBudgetAnalysis.csv', 48 | 'Satellite-LeadingSat-Transmitter-Fixed5x5-To-Satellite-TrailingSat-Receiver-Fixed5x5 NoisyLinkBudgetAnalysis.csv'] 49 | 50 | filenames_paa=[ 51 | 'Satellite-LeadingSat-Transmitter-PAA2x2-To-Satellite-TrailingSat-Receiver-PAA2x2 NoisyLinkBudgetAnalysis.csv', 52 | 'Satellite-LeadingSat-Transmitter-PAA3x3-To-Satellite-TrailingSat-Receiver-PAA3x3 NoisyLinkBudgetAnalysis.csv', 53 | 'Satellite-LeadingSat-Transmitter-PAA4x4-To-Satellite-TrailingSat-Receiver-PAA4x4 NoisyLinkBudgetAnalysis.csv', 54 | 'Satellite-LeadingSat-Transmitter-PAA5x5-To-Satellite-TrailingSat-Receiver-PAA5x5 NoisyLinkBudgetAnalysis.csv'] 55 | 56 | sigma = [1,2,3,4,5,6,7,8,9,10] 57 | labels = ["2x2","3x3","4x4","5x5"] 58 | quantity = "Xmtr Gain (dB)" 59 | #quantity = "Rcvr Gain (dB)" 60 | #quantity = "C/N (dB)" 61 | #quantity = "log(BER)" 62 | 63 | d22_fixed = [] 64 | d22_paa = [] 65 | d33_fixed = [] 66 | d33_paa = [] 67 | d44_fixed = [] 68 | d44_paa = [] 69 | d55_fixed = [] 70 | d55_paa = [] 71 | 72 | for directory in directories: 73 | 74 | d22_fixed.append(pandas.read_csv(directory + filenames_fixed[0], 75 | delimiter=',', 76 | parse_dates=["Time (UTCG)"], 77 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 78 | for d in dates])) 79 | 80 | d22_paa.append(pandas.read_csv(directory + filenames_paa[0], 81 | delimiter=',', 82 | parse_dates=["Time (UTCG)"], 83 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 84 | for d in dates])) 85 | d33_fixed.append(pandas.read_csv(directory + filenames_fixed[1], 86 | delimiter=',', 87 | parse_dates=["Time (UTCG)"], 88 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 89 | for d in dates])) 90 | 91 | d33_paa.append(pandas.read_csv(directory + filenames_paa[1], 92 | delimiter=',', 93 | parse_dates=["Time (UTCG)"], 94 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 95 | for d in dates])) 96 | d44_fixed.append(pandas.read_csv(directory + filenames_fixed[2], 97 | delimiter=',', 98 | parse_dates=["Time (UTCG)"], 99 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 100 | for d in dates])) 101 | 102 | d44_paa.append(pandas.read_csv(directory + filenames_paa[2], 103 | delimiter=',', 104 | parse_dates=["Time (UTCG)"], 105 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 106 | for d in dates])) 107 | d55_fixed.append(pandas.read_csv(directory + filenames_fixed[3], 108 | delimiter=',', 109 | parse_dates=["Time (UTCG)"], 110 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 111 | for d in dates])) 112 | 113 | d55_paa.append(pandas.read_csv(directory + filenames_paa[3], 114 | delimiter=',', 115 | parse_dates=["Time (UTCG)"], 116 | date_parser=lambda dates: [pandas.datetime.strptime(d, '%d %b %Y %H:%M:%S.%f') 117 | for d in dates])) 118 | 119 | ## Plot Dedicated Array Over Time 120 | for idx,s in enumerate(sigma): 121 | fig = plt.figure() 122 | fig.suptitle("Sigma=" + str(s)) 123 | ax0 = plt.subplot() 124 | 125 | 126 | end = 400 127 | ax0.plot(d55_fixed[idx]["Time (UTCG)"][:end],d55_fixed[idx][quantity][:end],label=labels[3]) 128 | ax0.plot(d44_fixed[idx]["Time (UTCG)"][:end],d44_fixed[idx][quantity][:end],label=labels[2]) 129 | ax0.plot(d33_fixed[idx]["Time (UTCG)"][:end],d33_fixed[idx][quantity][:end],label=labels[1]) 130 | ax0.plot(d22_fixed[idx]["Time (UTCG)"][:end],d22_fixed[idx][quantity][:end],label=labels[0]) 131 | 132 | 133 | ax0.set_ylim([0,20]) 134 | ax0.set_ylabel(quantity) 135 | ax0.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 136 | ax0.xaxis.set_major_locator(mdates.AutoDateLocator()) 137 | plt.gcf().autofmt_xdate() 138 | 139 | dic = {"Time": d55_fixed[idx]["Time (UTCG)"][:end], 140 | "Xmtr Gain 5x5 fixed": d55_fixed[idx][quantity][:end], 141 | "Xmtr Gain 4x4 fixed": d44_fixed[idx][quantity][:end], 142 | "Xmtr Gain 3x3 fixed": d33_fixed[idx][quantity][:end], 143 | "Xmtr Gain 2x2 fixed": d22_fixed[idx][quantity][:end], 144 | "Xmtr Gain 5x5 PAA": d55_paa[idx][quantity][:end], 145 | "Xmtr Gain 4x4 PAA": d44_paa[idx][quantity][:end], 146 | "Xmtr Gain 3x3 PAA": d33_paa[idx][quantity][:end], 147 | "Xmtr Gain 2x2 PAA": d22_paa[idx][quantity][:end]} 148 | 149 | df = pandas.DataFrame(data=dic) 150 | df.to_csv(output_directory + "xmtr_gain_sigma=" + str(s) + ".txt", index=None, sep=",", float_format='%.2f') 151 | 152 | 153 | 154 | ## Compare Array for fixed Sigma 155 | end = 400 156 | idx = 3 157 | 158 | fig = plt.figure() 159 | fig.suptitle("Comparison Fixed Array vs PAA 5x5") 160 | ax0 = plt.subplot() 161 | ax0.plot(d55_fixed[idx]["Time (UTCG)"][:end],d55_fixed[idx][quantity][:end],label=labels[3]) 162 | ax0.plot(d55_paa[idx]["Time (UTCG)"][:end],d55_paa[idx][quantity][:end],label=labels[3]) 163 | ax0.set_ylim([0,20]) 164 | ax0.set_ylabel(quantity) 165 | ax0.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 166 | ax0.xaxis.set_major_locator(mdates.AutoDateLocator()) 167 | plt.gcf().autofmt_xdate() 168 | 169 | fig = plt.figure() 170 | fig.suptitle("Comparison Fixed Array vs PAA 4x4") 171 | ax0 = plt.subplot() 172 | ax0.plot(d44_fixed[idx]["Time (UTCG)"][:end],d44_fixed[idx][quantity][:end],label=labels[2]) 173 | ax0.plot(d44_paa[idx]["Time (UTCG)"][:end],d44_paa[idx][quantity][:end],label=labels[2]) 174 | ax0.set_ylim([0,20]) 175 | ax0.set_ylabel(quantity) 176 | ax0.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 177 | ax0.xaxis.set_major_locator(mdates.AutoDateLocator()) 178 | plt.gcf().autofmt_xdate() 179 | 180 | fig = plt.figure() 181 | fig.suptitle("Comparison Fixed Array vs PAA 3x3") 182 | ax0 = plt.subplot() 183 | ax0.plot(d33_fixed[idx]["Time (UTCG)"][:end],d33_fixed[idx][quantity][:end],label=labels[1]) 184 | ax0.plot(d33_paa[idx]["Time (UTCG)"][:end],d33_paa[idx][quantity][:end],label=labels[1]) 185 | ax0.set_ylim([0,20]) 186 | ax0.set_ylabel(quantity) 187 | ax0.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 188 | ax0.xaxis.set_major_locator(mdates.AutoDateLocator()) 189 | plt.gcf().autofmt_xdate() 190 | 191 | fig = plt.figure() 192 | fig.suptitle("Comparison Fixed Array vs PAA 2x2") 193 | ax0 = plt.subplot() 194 | ax0.plot(d22_fixed[idx]["Time (UTCG)"][:end],d22_fixed[idx][quantity][:end],label=labels[0]) 195 | ax0.plot(d22_paa[idx]["Time (UTCG)"][:end],d22_paa[idx][quantity][:end],label=labels[0]) 196 | ax0.set_ylim([0,20]) 197 | ax0.set_ylabel(quantity) 198 | ax0.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) 199 | ax0.xaxis.set_major_locator(mdates.AutoDateLocator()) 200 | plt.gcf().autofmt_xdate() 201 | 202 | 203 | 204 | ### Plot Averages 205 | d22_fixed_avg = [] 206 | d22_paa_avg = [] 207 | d33_fixed_avg = [] 208 | d33_paa_avg = [] 209 | d44_fixed_avg = [] 210 | d44_paa_avg = [] 211 | d55_fixed_avg = [] 212 | d55_paa_avg = [] 213 | for i,s in enumerate(sigma): 214 | d22_fixed_avg.append(pow2db(db2pow(d22_fixed[i][quantity]).mean())) 215 | d22_paa_avg.append(pow2db(db2pow(d22_paa[i][quantity]).mean())) 216 | 217 | d33_fixed_avg.append(pow2db(db2pow(d33_fixed[i][quantity]).mean())) 218 | d33_paa_avg.append(pow2db(db2pow(d33_paa[i][quantity]).mean())) 219 | 220 | d44_fixed_avg.append(pow2db(db2pow(d44_fixed[i][quantity]).mean())) 221 | d44_paa_avg.append(pow2db(db2pow(d44_paa[i][quantity]).mean())) 222 | 223 | d55_fixed_avg.append(pow2db(db2pow(d55_fixed[i][quantity]).mean())) 224 | d55_paa_avg.append(pow2db(db2pow(d55_paa[i][quantity]).mean())) 225 | 226 | fig = plt.figure() 227 | fig.suptitle("Average " + quantity) 228 | ax0 = plt.subplot() 229 | ax0.plot(sigma,d22_fixed_avg,label=labels[0] + ",fixed") 230 | ax0.plot(sigma,d22_paa_avg,label=labels[0] + ",PAA") 231 | ax0.plot(sigma,d33_fixed_avg,label=labels[1] + ",fixed") 232 | ax0.plot(sigma,d33_paa_avg,label=labels[1] + ",PAA") 233 | ax0.plot(sigma,d44_fixed_avg,label=labels[2] + ",fixed") 234 | ax0.plot(sigma,d44_paa_avg,label=labels[2] + ",PAA") 235 | ax0.plot(sigma,d55_fixed_avg,label=labels[3] + ",fixed") 236 | ax0.plot(sigma,d55_paa_avg,label=labels[3] + ",PAA") 237 | ax0.set_ylabel(quantity) 238 | ax0.grid(True) 239 | 240 | dic = {"Std Deviation": sigma, 241 | "Avg Xmtr Gain 5x5 fixed": d55_fixed_avg, 242 | "Avg Xmtr Gain 5x5 PAA": d55_paa_avg, 243 | "Avg Xmtr Gain 4x4 fixed": d44_fixed_avg, 244 | "Avg Xmtr Gain 4x4 PAA": d44_paa_avg, 245 | "Avg Xmtr Gain 3x3 fixed": d33_fixed_avg, 246 | "Avg Xmtr Gain 3x3 PAA": d33_paa_avg, 247 | "Avg Xmtr Gain 2x2 fixed": d22_fixed_avg, 248 | "Avg Xmtr Gain 2x2 PAA": d22_paa_avg, 249 | } 250 | 251 | df = pandas.DataFrame(data=dic) 252 | df.to_csv(output_directory + "avg_xmtr_gain.txt", index=None, sep=",", float_format='%.2f') 253 | 254 | plt.legend() 255 | 256 | plt.show() 257 | -------------------------------------------------------------------------------- /antenna_toolbox/Antenna.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | import sys 9 | import math 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from mpl_toolkits.axes_grid1 import make_axes_locatable 13 | 14 | from matplotlib import cm 15 | from math import cos, sin, sqrt 16 | from mpl_toolkits.mplot3d import Axes3D 17 | 18 | from util import * 19 | 20 | class Antenna: 21 | """ Antenna Parent Class 22 | 23 | Implements general functions needed for all types of antennas, e.g. plotting. 24 | """ 25 | 26 | def loadPatternFromFile(self,patternfile): 27 | """ Loads a .txt file containing the simulated far-field pattern 28 | 29 | Format has to be CST Studio FarField Export. 30 | 31 | Args: 32 | patternfile: Path to file containing the pattern 33 | 34 | Return: 35 | pattern: Antenna Pattern 36 | 37 | """ 38 | 39 | # Load txt file 40 | theta,phi,e_abs,e_theta,phase_theta,e_phi,phase_phi,ax_ratio= np.loadtxt(patternfile,skiprows=2,unpack=True) 41 | 42 | 43 | self.Ntheta = np.unique(theta).size 44 | self.Nphi = np.unique(phi).size 45 | self.theta_step = theta[1]-theta[0] 46 | self.phi_step = phi[1+self.Nphi] - phi[0] 47 | 48 | 49 | # Reshape into nd Array and copy value at phi=0/360 degree 50 | self.theta_mesh = deg2rad(np.reshape(theta,(self.Nphi,self.Ntheta))).T 51 | self.theta_mesh = np.concatenate([self.theta_mesh,self.theta_mesh[:,0].reshape((self.Ntheta,1))],axis=1) 52 | 53 | self.phi_mesh = deg2rad(np.reshape(phi,(self.Nphi,self.Ntheta))).T 54 | self.phi_mesh = np.concatenate([self.phi_mesh,self.phi_mesh[:,0].reshape((self.Ntheta,1))+2*math.pi],axis=1) 55 | 56 | pattern = np.reshape(e_abs,(self.Nphi,self.Ntheta)).T 57 | pattern = np.concatenate([pattern,pattern[:,0].reshape((self.Ntheta,1))],axis=1) 58 | 59 | self.pattern = normalize(pattern) 60 | 61 | # Add one sample at phi=360 degree 62 | self.Nphi=self.Nphi+1 63 | self.theta_start = rad2deg(self.theta_mesh.min()) 64 | self.theta_stop = rad2deg(self.theta_mesh.max()) 65 | self.phi_start = rad2deg(self.phi_mesh.min()) 66 | self.phi_stop = rad2deg(self.phi_mesh.max()) 67 | 68 | def calcDirectivity(self): 69 | """ Calculate Maximum directiviy of antenna 70 | 71 | Returns: 72 | directivity: Directivity in dB 73 | max_directivity: Maximum Directivity in specific direction 74 | theta_max: Theta angle of direction with maximum directivity 75 | phi_max: Phi angle of direction with maximum directivity 76 | 77 | """ 78 | pattern = normalize(self.pattern) 79 | 80 | rad_intensity = pattern*pattern 81 | 82 | rad_intensity_iso = 0 83 | dtheta = deg2rad(self.theta_step) 84 | dphi = deg2rad(self.phi_step) 85 | 86 | #for t,theta in enumerate(self.theta_mesh): 87 | # for p,phi in enumerate(self.phi_mesh): 88 | # rad_intensity_iso = rad_intensity_iso + rad_intensity[t,p]*np.sin(theta)*dtheta*dphi 89 | 90 | rad_intensity_iso = np.sum(rad_intensity*np.sin(self.theta_mesh)*dtheta*dphi) 91 | 92 | rad_intensity_iso = 1/(4 * math.pi) * rad_intensity_iso 93 | 94 | self.directivity = rad_intensity/rad_intensity_iso 95 | 96 | self.max_directivity = self.directivity.max() 97 | 98 | return self.directivity,self.max_directivity 99 | 100 | 101 | def calcHPBW(self): 102 | """ Calculate Half-Power Beamwidth of Antenna 103 | 104 | Returns: 105 | hpbw: Half-Power Beamwidth in Degrees 106 | """ 107 | pass 108 | 109 | 110 | 111 | def getPatternCut(self,dim,angle): 112 | if dim =="theta": 113 | t_idx=round(angle/self.theta_step) 114 | 115 | return self.pattern[t_idx,:], self.phi_mesh[t_idx,:] 116 | 117 | 118 | elif dim =="phi": 119 | p_idx= int(round(angle/self.phi_step)) 120 | offset = int(round(180/self.phi_step)) 121 | pattern_cut = np.concatenate([np.flip(self.pattern[:,p_idx + offset]),self.pattern[:,p_idx]]) 122 | cut_angles = np.concatenate([-np.flip(self.theta_mesh[:,p_idx + offset]),self.theta_mesh[:,p_idx]]) 123 | 124 | return pattern_cut,cut_angles 125 | 126 | def plotPatternCut(self,dim,angles,ax=None,logarithmic=True,polar=True,labels=["Cut"]): 127 | """ Plot Pattern Cuts in given dimension 128 | 129 | Args: 130 | dim: Dimension of cut (Theta/Phi). 131 | angles: List of cutting angles. 132 | ax: Axis object to plot into 133 | logarithmic: Plot in dB 134 | polar: Polar plot 135 | 136 | Return: 137 | ax: Axes object 138 | """ 139 | 140 | if polar == True: 141 | if ax==None: 142 | fig = plt.figure() 143 | ax = fig.add_subplot(111,polar=True) 144 | 145 | ax.set_theta_zero_location('N') 146 | ax.grid(True) 147 | 148 | 149 | for i,angle in enumerate(angles): 150 | 151 | pattern_cut,cut_angles = self.getPatternCut(dim,angle) 152 | 153 | if dim =="phi": 154 | ax.set_xticks(np.pi/180. * np.linspace(180, -180, 8, endpoint=False)) 155 | ax.set_thetalim(-np.pi, np.pi) 156 | 157 | if logarithmic == True: 158 | ax.plot(cut_angles,lin2db(pattern_cut),label=labels[i]) 159 | ax.set_yticks(range(-50,0 , 10)) 160 | ax.set_ylim(-50, 0) 161 | else: 162 | ax.plot(cut_angles,pattern_cut,label=labels[i]) 163 | ax.set_yticks(range(0,1 , 0.2)) 164 | ax.set_ylim(0, 1) 165 | 166 | elif polar == False: 167 | 168 | if ax==None: 169 | fig = plt.figure() 170 | ax = fig.add_subplot(111,polar=False) 171 | ax.grid(True) 172 | 173 | for i,angle in enumerate(angles): 174 | 175 | pattern_cut,cut_angles = self.getPatternCut(dim,angle) 176 | 177 | if logarithmic == True: 178 | ax.plot(cut_angles,lin2db(pattern_cut),label=labels[i]) 179 | ax.set_yticks(range(-50,0 , 10)) 180 | ax.set_ylim(-50, 0) 181 | else: 182 | ax.plot(cut_angles,pattern_cut,label=labels[i]) 183 | ax.set_yticks(range(0,1 , 0.2)) 184 | ax.set_ylim(0, 1) 185 | 186 | ax.legend(loc="lower right",bbox_to_anchor=(1.4, 0)) 187 | 188 | return ax,pattern_cut,cut_angles 189 | 190 | 191 | 192 | def plotPattern3D(self,ax=None, logarithmic=True,log_range=-20): 193 | """ Plot 3D Pattern 194 | 195 | Args: 196 | ax: Axis object to plot into 197 | logarithmic: Plot in dB 198 | log_range: Minimum Value of plotting range in dB 199 | 200 | Return: 201 | ax: Axes object 202 | """ 203 | 204 | if ax==None: 205 | fig = plt.figure() 206 | ax = fig.add_subplot(111, projection='3d') 207 | 208 | 209 | if logarithmic ==True: 210 | pattern_db = lin2db(self.pattern) 211 | pattern_min = np.min(pattern_db) 212 | pattern_max = np.max(pattern_db) 213 | e = pattern_db - (log_range + pattern_max) 214 | e[e<0.0] = 0.0 215 | else: 216 | e = self.pattern 217 | pattern_min = np.min(self.pattern) 218 | pattern_max = np.max(self.pattern) 219 | 220 | x,y,z = sph2cart(self.phi_mesh,self.theta_mesh,e) 221 | 222 | ax.plot_surface(x, y, z,facecolors=cm.jet(self.pattern)) 223 | plt.xlabel('X') 224 | plt.ylabel('Y') 225 | lim_left,lim_right = ax.get_zlim() 226 | ax.set_xlim(lim_left-lim_right/2,lim_right/2) 227 | ax.set_ylim(lim_left-lim_right/2,lim_right/2) 228 | 229 | m = cm.ScalarMappable(cmap=plt.cm.jet) 230 | if logarithmic==True: 231 | m.set_array((e+(log_range+pattern_max))) 232 | else: 233 | m.set_array(e) 234 | cbar = plt.colorbar(m,ax=ax) 235 | cbar.set_label('dB', rotation=270,labelpad=12) 236 | 237 | return ax 238 | 239 | 240 | def getDirectivityCut(self,dim,angle): 241 | if dim =="theta": 242 | t_idx=round(angle/self.theta_step) 243 | 244 | return self.directivity[t_idx,:], self.phi_mesh[t_idx,:] 245 | 246 | 247 | elif dim =="phi": 248 | p_idx= int(round(angle/self.phi_step)) 249 | offset = int(round(180/self.phi_step)) 250 | directivity_cut = np.concatenate([np.flip(self.directivity[:,p_idx + offset]),self.directivity[:,p_idx]]) 251 | cut_angles = np.concatenate([-np.flip(self.theta_mesh[:,p_idx + offset]),self.theta_mesh[:,p_idx]]) 252 | 253 | return directivity_cut,cut_angles 254 | 255 | def plotDirectivityCut(self,dim,angles,ax=None,logarithmic=True,polar=True,labels=["Cut"]): 256 | """ Plot Directivity Cuts in given dimension 257 | 258 | Args: 259 | dim: Dimension of cut (Theta/Phi). 260 | angles: List of cutting angles. 261 | ax: Axis object to plot into 262 | logarithmic: Plot in dB 263 | polar: Polar plot 264 | 265 | Return: 266 | ax: Axes object 267 | """ 268 | 269 | if polar == True: 270 | if ax==None: 271 | fig = plt.figure() 272 | ax = fig.add_subplot(111,polar=True) 273 | 274 | ax.set_theta_zero_location('N') 275 | ax.grid(True) 276 | 277 | 278 | for i,angle in enumerate(angles): 279 | 280 | directivity_cut,cut_angles = self.getDirectivityCut(dim,angle) 281 | if dim =="phi": 282 | ax.set_xticks(np.pi/180. * np.linspace(180, -180, 8, endpoint=False)) 283 | ax.set_thetalim(-np.pi, np.pi) 284 | 285 | if logarithmic == True: 286 | ax.plot(cut_angles,pow2db(directivity_cut),label=labels[i]) 287 | ax.set_yticks(range(-50,int(directivity_cut.max()) , 10)) 288 | ax.set_ylim(-50, int(directivity_cut.max())) 289 | else: 290 | ax.plot(cut_angles,directivity_cut,label=labels[i]) 291 | #ax.set_yticks(range(0,1 , 0.2)) 292 | #ax.set_ylim(0, 1) 293 | 294 | elif polar == False: 295 | 296 | if ax==None: 297 | fig = plt.figure() 298 | ax = fig.add_subplot(111,polar=False) 299 | ax.grid(True) 300 | 301 | for i,angle in enumerate(angles): 302 | 303 | directivity_cut,cut_angles = self.getDirectivityCut(dim,angle) 304 | 305 | if logarithmic == True: 306 | ax.plot(cut_angles,pow2db(directivity_cut),label=labels[i]) 307 | ax.set_yticks(range(-50,int(directivity_cut.max()) , 10)) 308 | ax.set_ylim(-50, int(directivity_cut.max())) 309 | else: 310 | ax.plot(cut_angles,directivity_cut,label=labels[i]) 311 | #ax.set_yticks(range(0,1 , 0.2)) 312 | #ax.set_ylim(0, 1) 313 | 314 | ax.legend() 315 | 316 | return ax 317 | 318 | def plotDirectivity3D(self,ax=None, logarithmic=True,log_range=-20): 319 | """ Plot 3D Directivity 320 | 321 | Args: 322 | ax: Axis object to plot into 323 | logarithmic: Plot in dB 324 | log_range: Minimum Value of plotting range in dB 325 | 326 | Return: 327 | ax: Axes object 328 | """ 329 | 330 | if ax==None: 331 | fig = plt.figure() 332 | ax = fig.add_subplot(111, projection='3d') 333 | 334 | 335 | if logarithmic ==True: 336 | directivity_db = pow2db(self.directivity) 337 | directivity_min = np.min(directivity_db) 338 | directivity_max = np.max(directivity_db) 339 | e = directivity_db - (log_range + directivity_max) 340 | e[e<0.0] = 0.0 341 | else: 342 | e = self.directivity 343 | directivity_min = np.min(self.directivity) 344 | directivitye_max = np.max(self.directivity) 345 | 346 | x,y,z = sph2cart(self.phi_mesh,self.theta_mesh,e) 347 | 348 | ax.plot_surface(x, y, z,facecolors=cm.jet(normalize(self.directivity))) 349 | plt.xlabel('X') 350 | plt.ylabel('Y') 351 | lim_left,lim_right = ax.get_zlim() 352 | ax.set_xlim(lim_left-lim_right/2,lim_right/2) 353 | ax.set_ylim(lim_left-lim_right/2,lim_right/2) 354 | 355 | m = cm.ScalarMappable(cmap=plt.cm.jet) 356 | if logarithmic==True: 357 | m.set_array((e+(log_range+directivity_max))) 358 | else: 359 | m.set_array(e) 360 | plt.colorbar(m) 361 | 362 | return ax 363 | -------------------------------------------------------------------------------- /CSTResultLoader.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2019 Hannes Bartle 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | import sys 11 | import math 12 | import csv 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | import matplotlib.ticker as ticker 16 | plt.style.use('seaborn-whitegrid') 17 | plt.rc('text', usetex=True) 18 | plt.rc('font', family='serif') 19 | plt.rcParams.update({'font.size': 14}) 20 | from scipy import interpolate 21 | from scipy.signal import argrelextrema 22 | 23 | from util import * 24 | 25 | 26 | class CSTResult: 27 | def __init__(self,run_id,theta_scan,phi_scan,field_cut): 28 | self.run_id = run_id 29 | self.theta_scan = theta_scan 30 | self.phi_scan = phi_scan 31 | self.field_cut = field_cut 32 | 33 | def calcMaxDir(self): 34 | """ Calculate maximum directivity and direction thereof """ 35 | ind = np.unravel_index(np.argmax(self.field_cut[:,1]), self.field_cut[:,1].shape) 36 | max_dir = self.field_cut[ind,1] 37 | max_dir_theta = self.field_cut[ind,0] 38 | 39 | return max_dir[0],max_dir_theta[0] 40 | 41 | def calcHPBW(self): 42 | """ Calculate Half-Power Beamwidth """ 43 | max_dir,max_dir_theta = self.calcMaxDir() 44 | half_power_level = max_dir - 3 45 | index = (np.abs(self.field_cut[:,1] - half_power_level)).argmin() 46 | hpbw = 2*np.abs(max_dir_theta-self.field_cut[index,0]) 47 | 48 | return hpbw 49 | 50 | 51 | def calcSLL(self): 52 | """ Calculate Side-Lobe Level """ 53 | max_dir,max_dir_theta = self.calcMaxDir() 54 | ind = argrelextrema(self.field_cut[:, 1], np.greater) 55 | maxima = self.field_cut[ind, 1].flatten() 56 | maxima_sorted = maxima[maxima.argsort()] 57 | max_sidelobe = maxima_sorted[-2:-1] 58 | sll = -(max_dir -max_sidelobe) 59 | return sll[0],max_sidelobe[0] 60 | 61 | def calcSteeringOffset(self): 62 | """ Calculate the difference between steering angle and actual main beam direction """ 63 | ind = np.unravel_index(np.argmax(self.field_cut[:,1]), self.field_cut[:,1].shape) 64 | max_dir_theta = self.field_cut[ind,0] 65 | 66 | steering_offset = np.abs(max_dir_theta-self.theta_scan) 67 | return steering_offset[0] 68 | 69 | class CSTSweepResult: 70 | """ Class for results obtain by a CST scan sweep""" 71 | 72 | def __init__(self,theta=[],phi=[],directivity=[],gain_ieee=[],gain_realized=[],array_size=[],array_type=[]): 73 | self.theta_scan = theta 74 | self.phi_scan = phi 75 | self.directivity = directivity 76 | self.gain_ieee = gain_ieee 77 | self.gain_realized = gain_realized 78 | self.array_size=array_size 79 | self.array_type = array_type 80 | 81 | 82 | def constructSTKAntennaFile(self,output_file_dir): 83 | """ Generate the External Antenna File for STK simulations """ 84 | print("Construct STK Antenna file from scanning sweep data...") 85 | print("File: " + output_file_dir) 86 | filename = "stk_antenna_file_" + self.array_size + "_" + self.array_type + "_gain_ieee.txt" 87 | with open(output_file_dir + filename, "w") as file: 88 | file.write("stk.v.6.0\n" + 89 | "ThetaPhiPattern\n" + 90 | "AngleUnits Degrees\n" + 91 | "NumberOfPoints " + str(self.theta_scan.size * 2) + "\n" + 92 | "PatternData\n") 93 | np.savetxt(file, np.array([self.theta_scan.astype(np.float), 94 | self.phi_scan.astype(np.float), 95 | self.gain_ieee]).T, fmt="%.2f") 96 | np.savetxt(file, np.array([self.theta_scan.astype(np.float), 97 | self.phi_scan.astype(np.float)+180, 98 | self.gain_ieee]).T, fmt="%.2f") 99 | 100 | filename = "stk_antenna_file_" + self.array_size + "_" + self.array_type + "_gain_realized.txt" 101 | with open(output_file_dir + filename, "w") as file: 102 | file.write("stk.v.6.0\n" + 103 | "ThetaPhiPattern\n" + 104 | "AngleUnits Degrees\n" + 105 | "NumberOfPoints " + str(self.theta_scan.size * 2) + "\n" + 106 | "PatternData\n") 107 | 108 | np.savetxt(file, np.array([self.theta_scan.astype(np.float), 109 | self.phi_scan.astype(np.float), 110 | self.gain_realized]).T, fmt="%.2f") 111 | np.savetxt(file, np.array([self.theta_scan.astype(np.float), 112 | self.phi_scan.astype(np.float)+180, 113 | self.gain_realized]).T, fmt="%.2f") 114 | print("Done.") 115 | class CSTResultLoader: 116 | 117 | def loadSweepResultFile(self, results_file, run_id_file): 118 | """ 119 | :param results_file: Path to CST results file 120 | :param run_id_file: Path to Result navigator file 121 | :return: Result object containing Directivity, Gain (IEEE/Realized) of scanning sweep 122 | """ 123 | with open(results_file,newline='') as f: 124 | res = [] 125 | start=0 126 | for line in f: 127 | if "----------------------------------------------------------------------" in line: 128 | start=1 129 | elif line in ['\n', '\r\n']: 130 | start=0 131 | elif start: 132 | res.append(list(map(float, line.split()))) 133 | results = np.asarray(res) 134 | sim_results = np.split(results,3) 135 | directivity = sim_results[0] 136 | gain_ieee = sim_results[1] 137 | gain_realized = sim_results[2] 138 | 139 | theta = [] 140 | phi = [] 141 | with open(run_id_file,'r') as f: 142 | reader = csv.reader(f, delimiter=",") 143 | next(reader) 144 | for line in reader: 145 | if not line[0] == '0': 146 | phi.append(line[1]) 147 | theta.append(line[2]) 148 | theta = np.asarray(theta) 149 | phi = np.asarray(phi) 150 | 151 | 152 | 153 | 154 | array_type = directory.split("/") 155 | result_object = CSTSweepResult(theta=theta, phi=phi, 156 | directivity=directivity[:,1], 157 | gain_ieee=gain_ieee[:,1], 158 | gain_realized=gain_realized[:,1], 159 | array_size=array_type[2], 160 | array_type=array_type[3]) 161 | 162 | return result_object 163 | 164 | def loadCutsResultFile(self, results_file, run_id_file): 165 | with open(results_file,newline='') as f: 166 | res = [] 167 | start=0 168 | for line in f: 169 | 170 | if "----------------------------------------------------------------------" in line: 171 | 172 | 173 | start=1 174 | elif line in ['\n', '\r\n']: 175 | start=0 176 | elif start: 177 | res.append(list(map(float, line.split()))) 178 | results = np.asarray(res) 179 | results = np.split(results,361) 180 | result_objects = [] 181 | with open(run_id_file,newline='') as f: 182 | csvReader = csv.reader(f) 183 | next(csvReader,None) 184 | for i,row in enumerate(csvReader): 185 | if not row[0] == '0': 186 | row = list(map(int, row)) 187 | obj = CSTResult(row[0],row[1],row[2],results[i]) 188 | result_objects.append(obj) 189 | return result_objects 190 | 191 | def convertASCII2STKAntennaFile(self, directory, results_file): 192 | print("Converting far-field ASCII file to STK antenna file...") 193 | print("File: "+ directory + results_file) 194 | with open(directory + results_file,'r') as f: 195 | data = np.genfromtxt(f,skip_header=2) 196 | theta = data[:,0] 197 | phi = data[:,1] 198 | gain = data[:,2] 199 | 200 | filename = "stk_antenna_file_" + results_file 201 | with open(directory + filename, "w") as file: 202 | file.write("stk.v.6.0\n" + 203 | "ThetaPhiPattern\n" + 204 | "AngleUnits Degrees\n" + 205 | "NumberOfPoints " + str(gain.size) + "\n" + 206 | "PatternData\n") 207 | 208 | np.savetxt(file, np.array([theta,phi,gain]).T, fmt="%.2f") 209 | 210 | print("Done.") 211 | 212 | class CSTResultAnalyzer: 213 | """ Analyze CSTResult Objects """ 214 | 215 | def __init__(self,result_objects): 216 | self.result_objects = result_objects 217 | 218 | 219 | 220 | def analyzeCut(self,theta_scan, phi_scan): 221 | """ Analyze a single cut """ 222 | fig = plt.figure() 223 | ax = fig.add_subplot(111, polar=True) 224 | for obj in self.result_objects: 225 | if obj.phi_scan == phi_scan and obj.theta_scan == theta_scan: 226 | 227 | # Plot Cut 228 | ax.plot(obj.field_cut[:, 0] * math.pi / 180, obj.field_cut[:, 1], 229 | label="SCANTHETA=" + str(obj.theta_scan) + ",SCANPHI=" + str(obj.phi_scan)) 230 | 231 | plot_min = obj.field_cut[:,1].min() 232 | plot_max = obj.field_cut[:,1].max()+obj.field_cut[:,1].max()/3 233 | plt.ylim(plot_min, plot_max) 234 | 235 | # Plot Max Directivity 236 | max_dir,max_dir_direction = obj.calcMaxDir() 237 | ax.plot((0, max_dir_direction*math.pi/180),(plot_min,max_dir),"g--",linewidth=0.8) 238 | 239 | # Plot HPBW 240 | hpbw = obj.calcHPBW() 241 | ax.plot((0,(max_dir_direction+hpbw/2)*math.pi/180),(plot_min,plot_max), "r--",linewidth=0.8) 242 | ax.plot((0,(max_dir_direction-hpbw/2)*math.pi/180),(plot_min,plot_max), "r--",linewidth=0.8) 243 | 244 | # Plot SLL 245 | sll, max_sidelobe = obj.calcSLL() 246 | ax.plot(np.linspace(0, 2 * np.pi, 100), np.ones(100) * max_sidelobe, "m--",linewidth=0.8) 247 | 248 | # Steering Offset 249 | steering_offset = obj.calcSteeringOffset() 250 | ax.plot((0,(obj.theta_scan)*math.pi/180),(plot_min,plot_max), "k--",linewidth=0.8) 251 | 252 | 253 | 254 | ax.legend() 255 | print(max_dir) 256 | ax.annotate('Max. Directivity: ' + str(np.round(max_dir,2)) + "dBi\n" + 257 | "HPBW: " + str(np.round(hpbw)) +"°\n" + 258 | "Sidelobe-Level: " + str(np.round(sll,2)) + "dB\n" + 259 | "Steering Offset: " + str(steering_offset) + "°", 260 | xy=(0.1, 0.9), 261 | xycoords=('figure fraction', 'figure fraction'), 262 | size=10, ha='left', va='center') 263 | 264 | ax.set_theta_zero_location("N") 265 | plt.grid(True) 266 | plt.show() 267 | 268 | def plotHeatmap(self,title = "",value="gain_realized",interp=False): 269 | res = self.result_objects 270 | if value == "gain_realized": 271 | v = res.gain_realized 272 | unit = "dBi" 273 | elif value == "gain_ieee": 274 | v = res.gain_ieee 275 | unit = "dBi" 276 | elif value == "directivity": 277 | v = res.directivity 278 | unit = "dBi" 279 | elif value == "full_efficiency": 280 | v = 100*db2pow(res.gain_realized - res.directivity) 281 | unit = "Percent" 282 | elif value == "radiation_efficiency": 283 | v = 100*db2pow(res.gain_ieee - res.directivity) 284 | unit = "Percent" 285 | else: 286 | print("Specify a correct quantity: directivity,gain_ieee,gain_realized") 287 | 288 | phi_mesh = np.reshape(res.phi_scan, (-1, np.unique(res.phi_scan).size)) 289 | theta_mesh = np.reshape(res.theta_scan, (-1, np.unique(res.phi_scan).size)) 290 | v_mesh = np.reshape(v,(-1, np.unique(res.phi_scan).size)) 291 | 292 | if interp == True: 293 | theta_interp = np.arange(0, 90, 1) 294 | phi_interp = np.arange(0,180, 1) 295 | f = interpolate.interp2d(theta_mesh.astype(np.float), phi_mesh.astype(np.float), v_mesh, kind='linear') 296 | theta_mesh, phi_mesh = np.meshgrid(theta_interp,phi_interp) 297 | v_mesh = f(theta_interp,phi_interp) 298 | 299 | 300 | 301 | fig = plt.figure() 302 | fig.canvas.set_window_title(title) 303 | ax = plt.subplot() 304 | 305 | c = ax.pcolormesh(phi_mesh, theta_mesh, v_mesh, 306 | cmap='jet', 307 | vmin=v_mesh.min(), vmax=v_mesh.max()) 308 | ax.set_title(title.split("-")[-1]) 309 | ax.set_xlabel("Phi Scan [°]") 310 | ax.set_ylabel("Theta Scan [°]") 311 | ax.xaxis.set_major_locator(ticker.MultipleLocator(2)) 312 | ax.yaxis.set_major_locator(ticker.MultipleLocator(2)) 313 | colorbar_ticks = np.linspace(v_mesh.min(),v_mesh.max(),8) 314 | cbar = fig.colorbar(c, ax=ax,ticks=colorbar_ticks) 315 | #cbar = fig.colorbar(c, ax=ax) 316 | cbar.set_label(unit, rotation=270, labelpad=20) 317 | 318 | return fig 319 | 320 | # Find and plot line of constant value 321 | #idx = np.where(v>=(v.max()-3))[0] 322 | #const = v[idx] 323 | #print(const) 324 | 325 | def calcSteeringBandwidth(self): 326 | gain_realized = self.result_objects.gain_realized 327 | max_gain_realized = gain_realized.max() 328 | 329 | bandwidth_gain = max_gain_realized - gain_realized 330 | index = np.where(bandwidth_gain>=3) 331 | theta = np.asarray(list(map(int,self.result_objects.theta_scan[index]))) 332 | return theta.min() 333 | 334 | 335 | if __name__ == "__main__": 336 | result_loader = CSTResultLoader() 337 | 338 | plot_directory = "../plots/steering_sweep_results/" 339 | 340 | directories = ["../sim_data/5x5/uniform/", 341 | "../sim_data/5x5/binomial/", 342 | "../sim_data/5x5/chebychev/", 343 | "../sim_data/4x4/uniform/", 344 | "../sim_data/4x4/binomial/", 345 | "../sim_data/4x4/chebychev/", 346 | "../sim_data/3x3/uniform/", 347 | "../sim_data/3x3/binomial/", 348 | "../sim_data/3x3/chebychev/", 349 | "../sim_data/2x2/uniform/", 350 | "../sim_data/2x2/binomial/", 351 | "../sim_data/2x2/chebychev/"] 352 | 353 | directories = ["../sim_data/2x2/uniform/", 354 | "../sim_data/3x3/uniform/", 355 | "../sim_data/4x4/uniform/", 356 | "../sim_data/5x5/uniform/", 357 | "../sim_data/2x2/binomial/", 358 | "../sim_data/3x3/binomial/", 359 | "../sim_data/4x4/binomial/", 360 | "../sim_data/5x5/binomial/", 361 | "../sim_data/2x2/chebychev/", 362 | "../sim_data/3x3/chebychev/", 363 | "../sim_data/4x4/chebychev/", 364 | "../sim_data/5x5/chebychev/"] 365 | 366 | max_gain_realized = [] 367 | min_sll = [] 368 | min_hpbw =[] 369 | max_steering_offset = [] 370 | steering_bandwidth = [] 371 | 372 | 373 | for directory in directories: 374 | result_objects_cut = result_loader.loadCutsResultFile(directory + "Directivity,Phi=0.txt", 375 | "../sim_data/result_navigator.csv") 376 | res_analyzer = CSTResultAnalyzer(result_objects_cut) 377 | #res_analyzer.analyzeCut(0,0) 378 | 379 | # Load Scanning Sweep Result and plot Heatmaps 380 | result_object_sweep = result_loader.loadSweepResultFile(directory + "DGG,Value,Theta=PAA_FA_SCANTHETA,Phi=PAA_FA_SCANPHI.txt", 381 | "../sim_data/result_navigator.csv") 382 | 383 | analyzer = CSTResultAnalyzer(result_object_sweep) 384 | # Realized Gain 385 | title = directory.split('/')[2] + '-' + directory.split('/')[3] + " - Scanning Direction, Realized Gain" 386 | #fig = analyzer.plotHeatmap(title=title, value="gain_realized") 387 | #fig.savefig(plot_directory + title + ".png") 388 | 389 | # Gain IEEE 390 | title = directory.split('/')[2] + '-' + directory.split('/')[3] +" - Scanning Direction, Gain IEEE" 391 | #fig = analyzer.plotHeatmap(title=title, value="gain_ieee") 392 | #fig.savefig(plot_directory + title + ".png") 393 | 394 | # Save Max Values for Comparison plots 395 | max_gain_realized.append(result_object_sweep.gain_realized.max()) 396 | steering_bandwidth.append(analyzer.calcSteeringBandwidth()) 397 | 398 | 399 | result_object_sweep = result_loader.loadSweepResultFile(directory + "DGG,Max. Value,Phi=PAA_FA_SCANPHI.txt", 400 | "../sim_data/result_navigator.csv") 401 | analyzer = CSTResultAnalyzer(result_object_sweep) 402 | 403 | # Realized Gain Max Value 404 | title = directory.split('/')[2] + '-' + directory.split('/')[3] + " - Max. Value, Realized Gain" 405 | #fig = analyzer.plotHeatmap(title=title,value="gain_realized") 406 | #fig.savefig(plot_directory + title + ".png") 407 | 408 | # Gain IEEE Max Value 409 | title = directory.split('/')[2] + '-' + directory.split('/')[3] + " - Max. Value, Gain IEEE" 410 | #fig = analyzer.plotHeatmap(title=title,value="gain_ieee") 411 | #fig.savefig(plot_directory + title + ".png") 412 | 413 | # Efficiency 414 | title = directory.split('/')[2] + '-' + directory.split('/')[3] + " - Full Efficiency" 415 | #fig = analyzer.plotHeatmap(title=title, value="full_efficiency") 416 | #fig.savefig(plot_directory + title + ".png") 417 | 418 | title = directory.split('/')[2] + '-' + directory.split('/')[3] + " - Radiation Efficiency" 419 | #fig = analyzer.plotHeatmap(title=title, value="radiation_efficiency") 420 | #fig.savefig(plot_directory + title + ".png") 421 | 422 | #result_object_sweep.constructSTKAntennaFile(directory) 423 | 424 | 425 | 426 | # Create STK antenna file for reference antennas 427 | # result_loader.convertASCII2STKAntennaFile("../sim_data/reference_antennas/","8ghz_patch_realized_gain.txt") 428 | # result_loader.convertASCII2STKAntennaFile("../sim_data/reference_antennas/", 429 | # "4x4_chebychev_broadside_array_realized_gain.txt") 430 | 431 | #plt.show() 432 | for i,n in enumerate(max_gain_realized): 433 | print("(" + str(i%4)+"," + str(max_gain_realized[i]) + ")") 434 | --------------------------------------------------------------------------------