Changed axis definitions and working gain for fix theta

Changed the definitions of theta and phi to follow antenna-theory.net
First working plots
This commit is contained in:
Vincent Bareiss 2024-04-03 11:41:23 +02:00
parent bf58484dfa
commit 83bdce162a
3 changed files with 65394 additions and 203 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -9,23 +9,27 @@ import numpy as np
import pandas as pd
import math
# from spatz.sensors import Sensor
# from spatz.transforms import Transform
# from spatz.dataset import Dataset
# from spatz.logger import Logger
from spatz.sensors import Sensor
from spatz.simulation import Simulation
from spatz.transforms import Transform
from spatz.dataset import Dataset
from spatz.logger import Logger
import time
GAIN_NAME = "Abs(Gain)"
'''
Sensor to simulate TX antenna gain in direction of ground station
Returns the gain in dBi per timestep.
gain_pattern: matrix, groundstation_offset_vector
Class representing a CST gain pattern
This (and the sensor below) follow the convetions laid out by https://www.antenna-theory.com/basics/radpattern.php.
I.e, theta represents the elevation angle and goes from 0 to 180 deg, Phi represents the azimuth angle.
The data is interpolated, you will have to specify the step size for this to work correctly.
'''
class GainPattern():
def __init__(self, filepath: str):
def __init__(self, filepath: str, step_size: int):
self._stepsize = step_size
# This is a cursed parser. If it breaks, though luck.
with open(filepath,"r") as file:
# Read Header
@ -37,32 +41,62 @@ class GainPattern():
# Parse to DF
lines = file.readlines()
clean_csv = header
for line in lines:
cleaned = re.sub(r'\s+',',',line).removeprefix(',').removesuffix(',').strip()
clean_csv = clean_csv + cleaned + '\n'
clean_csv = [header]
start_time = time.time()
num_lines = len(lines)
for i,line in enumerate(lines):
if(i % step_size == 0 or i == num_lines-1):
cleaned = re.sub(r'\s+',',',line).removeprefix(',').removesuffix(',').strip()
clean_csv.append(cleaned + '\n')
clean_csv = ''.join(clean_csv)
filelike = StringIO(clean_csv)
self._df = pd.read_csv(filelike)
print(self._df.head())
print(f"Processed {num_lines} lines in {(time.time()-start_time):.1f}s.")
print(f"Used {num_lines // step_size} lines due to step size")
self._df.to_csv("gainpattern.csv")
def get_phi_cut(self, phi:float) -> ArrayLike: #Return farfield cut with phi = const (Looking from the side)
assert 0 <= phi < 180
sub_df = self._df.loc[self._df["Phi"] == phi]
angles = sub_df["Theta"]
gain = sub_df[GAIN_NAME]
return angles,gain
def get_theta_cut(self, theta:float) -> ArrayLike: #Return farfield cut with theta = const (looking from the top)
assert 0<= theta < 360
sub_df_left = self._df.loc[self._df["Theta"] == theta]
angles = sub_df_left["Phi"]
gain = sub_df_left[GAIN_NAME]
sub_df_right = self._df.loc[self._df["Theta"] == ((theta + 180) % 360)]
angles = sub_df_right["Phi"]
gain = sub_df_right[GAIN_NAME]
return angles,gain
def __get_gain_internal(self,phi5:float,theta5:float):
assert phi5%5 ==0
assert theta5%5==0
def __get_gain_internal(self,phi_step:float,theta_step:float):
assert phi_step%self._stepsize ==0
assert theta_step%self._stepsize==0
row = self._df.loc[(self._df["Theta"] == theta5) & (self._df["Phi"] == phi5)].iloc[0]
return row["Abs(Dir.)"]
row = self._df.loc[(self._df["Theta"] == theta_step) & (self._df["Phi"] == phi_step)].iloc[0]
return row[GAIN_NAME]
def get_gain(self, phi, theta) -> float:
assert 0 <= phi <= 180
assert 0 <= theta <= 360
assert 0 <= phi < 360
assert 0 <= theta < 180
#Interpolate using binlinear interpolation https://en.wikipedia.org/wiki/Bilinear_interpolation
phi_lower = math.floor(phi/5)*5
phi_upper = phi_lower + 5
theta_lower = math.floor(theta/5)*5
theta_upper = theta_lower + 5
phi_lower = math.floor(phi/self._stepsize)*self._stepsize
phi_upper = phi_lower + self._stepsize
theta_lower = math.floor(theta/self._stepsize)*self._stepsize
theta_upper = theta_lower + self._stepsize
G11 = self.__get_gain_internal(phi_lower,theta_lower)
G12 = self.__get_gain_internal(phi_lower,theta_upper)
@ -73,37 +107,49 @@ class GainPattern():
v2 = np.array([[theta_upper-theta],[theta-theta_lower]])
A = np.array([[G11,G12],[G21,G22]])
interpolated = 1/25 * v1 @ A @ v2
interpolated = 1/(self._stepsize*self._stepsize) * v1 @ A @ v2
return interpolated[0]
'''
Sensor to simulate TX antenna gain in direction of ground station
# class AntennaTxGain(Sensor):
Returns the gain in dBi per timestep.
# def __init__(self, dataset: Dataset, logger: Logger, transforms: List[Transform] = []):
# super().__init__(dataset, logger, transforms)
'''
class AntennaTxGain(Sensor):
# def _get_data(self) -> ArrayLike | float:
# # Get current position of rocket
# [x,y,z] = self._dataset.fetch_values(['x', 'y', 'z'])
def __init__(self, dataset: Dataset, logger: Logger, transforms: List[Transform] = [], gain_pattern_path = "data/gain_pattern/farfield_2_45_GHz.txt"):
super().__init__(dataset, logger, transforms)
self._pattern = GainPattern(gain_pattern_path,1)
def _get_data(self) -> ArrayLike | float:
# Get current position of rocket in FL Frame (Launcher Frame).
pos_fl = self._dataset.fetch_values(['x', 'y', 'z']) #X,Y,Z is in FL (Launcher frame) -> Z is up, X is east
# Transform X,Y,Z to B Frame (Body Frame)
pos_b = np.array(pos_fl) @ self._dataset.launch_rail_to_body()
# Rocket in body frame is simply [1,0,0]^T by definition
rocket_b = np.array([1,0,0]).T
# Angle between rocket and pos returns elevation angle (Phi). Assume a rotation of 0° for now to get theta
theta = np.rad2deg(np.arccos(np.clip(np.dot(pos_b/np.linalg.norm(pos_b), rocket_b),-1.0,1.0))) #Clip trick from: https://stackoverflow.com/questions/2827393/angles-between-two-n-dimensional-vectors-in-python
#return phi
phi = 0
# # Get current rotation of rocket
# [pitch,roll,yaw] = self._dataset.fetch_values(['pitch','roll','yaw'])
# Fetch gain in this direction
return self._pattern.get_gain(phi,theta)
# # Calculate angle between the vectors
# # Fetch gain in this direction
# return 0
# def _sensor_specific_effects(self, x: ArrayLike) -> ArrayLike:
# return x
def _sensor_specific_effects(self, x: ArrayLike) -> ArrayLike:
return x
# def _get_name(self) -> AnyStr:
# return 'Generic Antenna TX'
def _get_name(self) -> AnyStr:
return 'antenna/tx_gain'
if __name__ == '__main__':