Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions sharc/antenna/antenna_f1245_fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import matplotlib.pyplot as plt
from sharc.antenna.antenna import Antenna
from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter
import numpy as np
import math


class Antenna_f1245_fs(Antenna):
"""Class that implements the ITU-R F.1245 antenna pattern for fixed
satellite service earth stations."""

def __init__(self, param: ParametersAntennaWithDiameter):
super().__init__()
self.peak_gain = param.gain
lmbda = 3e8 / (param.frequency * 1e6)
self.d_lmbda = param.diameter / lmbda
self.g_l = 2 + 15 * math.log10(self.d_lmbda)
self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l)
self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6)

def calculate_gain(self, *args, **kwargs) -> np.array:
"""
Calculate the antenna gain for the given parameters.

Parameters
----------
*args : tuple
Positional arguments (not used).
**kwargs : dict
Keyword arguments, expects 'phi_vec', 'theta_vec', and 'beams_l'.

Returns
-------
np.array
Calculated antenna gain values.
"""
# phi_vec = np.absolute(kwargs["phi_vec"])
# theta_vec = np.absolute(kwargs["theta_vec"])
# beams_l = np.absolute(kwargs["beams_l"])
off_axis = np.absolute(kwargs["off_axis_angle_vec"])
if self.d_lmbda > 100:
gain = self.calculate_gain_greater(off_axis)
else:
gain = self.calculate_gain_less(off_axis)
# idx_max_gain = np.where(beams_l == -1)[0]
# gain = self.peak_gain
return gain

def calculate_gain_greater(self, phi: float) -> np.array:
"""
For frequencies in the range 1 GHz to about 70 GHz, in cases where the
ratio between the antenna diameter and the wavelength is GREATER than
100, this method should be used.
Parameter
---------
phi : off-axis angle [deg]
Returns
-------
a numpy array containing the gains in the given angles
"""
gain = np.zeros(phi.shape)
idx_0 = np.where(phi < self.phi_m)[0]
gain[idx_0] = self.peak_gain - 2.5e-3 * \
np.power(self.d_lmbda * phi[idx_0], 2)
phi_thresh = max(self.phi_m, self.phi_r)
idx_1 = np.where((self.phi_m <= phi) & (phi < phi_thresh))[0]
gain[idx_1] = self.g_l
idx_2 = np.where((phi_thresh <= phi) & (phi < 48))[0]
gain[idx_2] = 29 - 25 * np.log10(phi[idx_2])
idx_3 = np.where((48 <= phi) & (phi <= 180))[0]
gain[idx_3] = -13
return gain

def calculate_gain_less(self, phi: float) -> np.array:
"""
For frequencies in the range 1 GHz to about 70 GHz, in cases where the
ratio between the antenna diameter and the wavelength is LESS than
or equal to 100, this method should be used.
Parameter
---------
phi : off-axis angle [deg]
Returns
-------
a numpy array containing the gains in the given angles
"""
gain = np.zeros(phi.shape)
idx_0 = np.where(phi < self.phi_m)[0]
gain[idx_0] = self.peak_gain - 0.0025 * \
np.power(self.d_lmbda * phi[idx_0], 2)
idx_1 = np.where((self.phi_m <= phi) & (phi < 48))[0]
gain[idx_1] = 39 - 5 * \
math.log10(self.d_lmbda) - 25 * np.log10(phi[idx_1])
idx_2 = np.where((48 <= phi) & (phi < 180))[0]
gain[idx_2] = -3 - 5 * math.log10(self.d_lmbda)
return gain

def add_beam(self, phi: float, theta: float):
"""
Add a new beam to the antenna.

Parameters
----------
phi : float
Azimuth angle in degrees.
theta : float
Elevation angle in degrees.
"""
self.beams_list.append((phi, theta))

def calculate_off_axis_angle(self, Az, b):
"""
Calculate the off-axis angle between the main beam and a given direction.

Parameters
----------
Az : float or np.array
Azimuth angle(s) in degrees.
b : float or np.array
Elevation angle(s) in degrees.

Returns
-------
float or np.array
Off-axis angle(s) in degrees.
"""
Az0 = self.beams_list[0][0]
a = 90 - self.beams_list[0][1]
C = Az0 - Az
off_axis_rad = np.arccos(
np.cos(
np.radians(a)) *
np.cos(
np.radians(b)) +
np.sin(
np.radians(a)) *
np.sin(
np.radians(b)) *
np.cos(
np.radians(C)),
)
off_axis_deg = np.degrees(off_axis_rad)
return off_axis_deg


if __name__ == '__main__':
off_axis_angle_vec = np.linspace(0.1, 180, num=1001)
# initialize antenna parameters
param = ParametersAntennaWithDiameter()
param.frequency = 2155
param.gain = 33.1
param.diameter = 2
antenna_gt = Antenna_f1245_fs(param)
antenna_gt.add_beam(0, 0)
gain_gt = antenna_gt.calculate_gain(
off_axis_angle_vec=off_axis_angle_vec,
)
param.diameter = 3
antenna_gt = Antenna_f1245_fs(param)
gain_gt_3 = antenna_gt.calculate_gain(
off_axis_angle_vec=off_axis_angle_vec,
)
param.diameter = 1.8
antenna_gt = Antenna_f1245_fs(param)
gain_gt_18 = antenna_gt.calculate_gain(
off_axis_angle_vec=off_axis_angle_vec,
)

fig = plt.figure(
figsize=(8, 7), facecolor='w',
edgecolor='k',
) # create a figure object
plt.semilogx(off_axis_angle_vec, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 2$ $m$")
plt.semilogx(off_axis_angle_vec, gain_gt_3, "-y", label="$f = 10.7$ $GHz,$ $D = 3$ $m$")
plt.semilogx(off_axis_angle_vec, gain_gt_18, "-g", label="$f = 10.7$ $GHz,$ $D = 1.8$ $m$")

plt.title("ITU-R F.1245 antenna radiation pattern")
plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Gain relative to $G_m$ [dB]")
plt.legend(loc="lower left")
# plt.xlim((phi[0], phi[-1]))
plt.ylim((-20, 50))
# ax = plt.gca()
# ax.set_yticks([-30, -20, -10, 0])
# ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist())
plt.grid()
plt.show()
3 changes: 3 additions & 0 deletions sharc/antenna/antenna_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from sharc.antenna.antenna_s580 import AntennaS580
from sharc.antenna.antenna_s1528 import AntennaS1528
from sharc.antenna.antenna_s1855 import AntennaS1855
from sharc.antenna.antenna_f1245_fs import Antenna_f1245_fs
from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Leo, AntennaS1528Taylor
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt

Expand Down Expand Up @@ -49,6 +50,8 @@ def create_antenna(
return AntennaS465(antenna_params.itu_r_s_465_modified)
case "ITU-R S.1855":
return AntennaS1855(antenna_params.itu_r_s_1855)
case "ITU-R F.1245_fs":
return Antenna_f1245_fs(antenna_params.itu_r_f_1245_fs)
case "ITU-R Reg. RR. Appendice 7 Annex 3":
return AntennaReg_RR_A7_3(antenna_params.itu_reg_rr_a7_3)
case "MSS Adjacent":
Expand Down
12 changes: 10 additions & 2 deletions sharc/parameters/parameters_antenna.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class ParametersAntenna(ParametersBase):
"ITU-R-S.1528-Taylor",
"ITU-R-S.1528-Section1.2",
"ITU-R-S.1528-LEO",
"MSS Adjacent"]
"MSS Adjacent",
"ITU-R F.1245_fs"]

# chosen antenna radiation pattern
pattern: typing.Literal["OMNI",
Expand All @@ -44,7 +45,8 @@ class ParametersAntenna(ParametersBase):
"ITU-R-S.1528-Taylor",
"ITU-R-S.1528-Section1.2",
"ITU-R-S.1528-LEO",
"MSS Adjacent"] = None
"MSS Adjacent",
"ITU-R F.1245_fs"] = None

# antenna gain [dBi]
gain: float = None
Expand Down Expand Up @@ -94,6 +96,10 @@ class ParametersAntenna(ParametersBase):
default_factory=ParametersAntennaS672,
)

itu_r_f_1245_fs: ParametersAntennaWithDiameter = field(
default_factory=ParametersAntennaWithDiameter,
)

def set_external_parameters(self, **kwargs):
"""
Set external parameters for all sub-parameters of the antenna.
Expand Down Expand Up @@ -198,6 +204,8 @@ def validate(self, ctx):
self.itu_r_s_672.validate(f"{ctx}.itu_r_s_672")
case "MSS Adjacent":
self.mss_adjacent.validate(f"{ctx}.mss_adjacent")
case "ITU-R F.1245_fs":
self.itu_r_f_1245_fs.validate(f"{ctx}.itu_r_f_1245_fs")
case _:
raise NotImplementedError(
"ParametersAntenna.validate does not implement this antenna validation!", )
3 changes: 3 additions & 0 deletions sharc/station_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from sharc.antenna.antenna_s672 import AntennaS672
from sharc.antenna.antenna_s1528 import AntennaS1528
from sharc.antenna.antenna_s1855 import AntennaS1855
from sharc.antenna.antenna_f1245_fs import Antenna_f1245_fs
from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Leo, AntennaS1528Taylor
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
from sharc.topology.topology import Topology
Expand Down Expand Up @@ -1327,6 +1328,8 @@ def generate_fs_station(param: ParametersFs):
fs_station.antenna = np.array([AntennaOmni(param.antenna_gain)])
elif param.antenna_pattern == "ITU-R F.699":
fs_station.antenna = np.array([AntennaF699(param)])
elif param.antenna_pattern == "ITU-R F.1245_fs":
fs_station.antenna == np.array([Antenna_f1245_fs(param)])
else:
sys.stderr.write(
"ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern,
Expand Down