- The password you provided is weak and can be easily guessed.
- To increase your security, you must update your password. After September 1st, 2019 we will automatically reset your password. Change your password on the settings page.
-
-
-
- You canโt perform that action at this time.
-
-
-
-
-
-
-
-
-
-
-
-
- You signed in with another tab or window. Reload to refresh your session.
- You signed out in another tab or window. Reload to refresh your session.
-
- The password you provided is weak and can be easily guessed.
- To increase your security, you must update your password. After September 1st, 2019 we will automatically reset your password. Change your password on the settings page.
-
-
-
- You canโt perform that action at this time.
-
-
-
-
-
-
-
-
-
-
-
-
- You signed in with another tab or window. Reload to refresh your session.
- You signed out in another tab or window. Reload to refresh your session.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Apr 4 17:08:00 2018
+@author: Calil
+"""
+from sharc.antenna.antenna import Antenna
+from sharc.parameters.imt.parameters_imt import ParametersImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+import numpy as np
+import math
+
+
+class AntennaRes122(Antenna):
+ """
+ Implements reference radiation patterns for HAPS gateway antennas
+ for use in coordination studies and interference assessment in the
+ frequency ranges 47.2-47.5 GHz & 47.9-48.2 GHz. (ITU RR Res 133, Resolve 3)
+ """
+
+ def __init__(self, param: ParametersImt, param_ant: ParametersAntennaImt):
+ super().__init__()
+ self.peak_gain = param_ant.peak_gain
+ lmbda = 3e8 / (param.frequency * 1e6)
+ self.d_lmbda = param_ant.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:
+ phi_vec = np.absolute(kwargs["phi_vec"])
+ theta_vec = np.absolute(kwargs["theta_vec"])
+ beams_l = np.absolute(kwargs["beams_l"])
+ off_axis = self.calculate_off_axis_angle(phi_vec, theta_vec)
+ gain = np.zeros(off_axis.shape)
+ idx_0 = np.where(off_axis < self.phi_m)[0]
+ gain[idx_0] = self.peak_gain - 0.0025 * \
+ np.power(self.d_lmbda * off_axis[idx_0], 2)
+ idx_1 = np.where((self.phi_m <= off_axis) & (off_axis < 48))[0]
+ gain[idx_1] = 39 - 5 * \
+ math.log10(self.d_lmbda) - 25 * np.log10(off_axis[idx_1])
+ idx_2 = np.where((48 <= off_axis) & (off_axis < 180))[0]
+ gain[idx_2] = -3 - 5 * math.log10(self.d_lmbda)
+ idx_max_gain = np.where(beams_l == -1)[0]
+ gain[idx_max_gain] = self.peak_gain
+ return gain
+
+ def add_beam(self, phi: float, theta: float):
+ self.beams_list.append((phi, theta))
+
+ def calculate_off_axis_angle(self, Az, b):
+ 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__':
+ import matplotlib.pyplot as plt
+ phi = np.linspace(0.1, 180, num=100000)
+ theta = 90 * np.ones_like(phi)
+ beams_idx = np.zeros_like(phi, dtype=int)
+ # initialize antenna parameters
+ param = ParametersImt()
+ param.frequency = 10700
+ param_gt = ParametersAntennaImt()
+ param_gt.peak_gain = 49.8
+ param_gt.diameter = 3
+ antenna_gt = AntennaRes122(param, param_gt)
+ antenna_gt.add_beam(0, 0)
+ gain_gt = antenna_gt.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ beams_l=beams_idx,
+ )
+ param.frequency = 27500
+ param_lt = ParametersAntennaImt()
+ param_lt.peak_gain = 36.9
+ param_lt.diameter = 0.3
+ antenna_lt = AntennaRes122(param, param_lt)
+ antenna_lt.add_beam(0, 0)
+ gain_lt = antenna_lt.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ beams_l=beams_idx,
+ )
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+ plt.semilogx(phi, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 3$ $m$")
+ plt.semilogx(phi, gain_lt, "-r", label="$f = 27.5$ $GHz,$ $D = 0.3$ $m$")
+ plt.title("ITU-R R.122 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()
diff --git a/sharc/antenna/antenna_rra7_3.py b/sharc/antenna/antenna_rra7_3.py
new file mode 100644
index 000000000..01c173f77
--- /dev/null
+++ b/sharc/antenna/antenna_rra7_3.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+from sharc.antenna.antenna import Antenna
+from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter
+from sharc.antenna.antenna_s465 import AntennaS465
+
+import numpy as np
+import math
+
+
+class AntennaReg_RR_A7_3(Antenna):
+ """
+ Implements the Earth station antenna pattern in the MetSat service
+ according to Recommendation ITU Radio Regulations Appendix 7, Annex 3
+ """
+
+ def __init__(self, param: ParametersAntennaWithDiameter):
+ super().__init__()
+ self.peak_gain = param.antenna_gain
+ lmbda = 3e8 / (param.frequency * 1e6)
+ if param.diameter:
+ D_lmbda = param.diameter / lmbda
+ else:
+ # From Note 1, we can estimate D_lmbda from
+ # 20 log(D_lmbda) =(aprox.)= G_max - 7.7
+ D_lmbda = math.pow(10, ((self.peak_gain - 7.7) / 20))
+
+ self.D_lmbda = D_lmbda
+
+ if D_lmbda >= 100:
+ self.g1 = -1 + 15 * np.log10(D_lmbda)
+ self.phi_r = 15.85 * math.pow(D_lmbda, -0.6)
+ elif D_lmbda >= 35:
+ self.g1 = -21 + 25 * np.log10(D_lmbda)
+ self.phi_r = 100 / D_lmbda
+ else:
+ raise ValueError(f"Recommendation does not define antenna pattern when D/lmbda = {D_lmbda}")
+
+ self.phi_m = 20 / D_lmbda * np.sqrt(self.peak_gain - self.g1)
+
+ # if np.sqrt(self.peak_gain - self.g1) >= 5, then phi_m >= phi_r, and that may be a problem
+ # if this is erroring in your simulation, you should check with a professor how to deal with this
+ # since the document doesn't specify
+ if self.phi_m >= self.phi_r:
+ raise ValueError(f"Recommendation doesn't specify what to do when phi_m ({self.phi_m}) >= phi_r ({self.phi_r})")
+
+ def calculate_gain(self, *args, **kwargs) -> np.array:
+ phi = np.absolute(kwargs["off_axis_angle_vec"])
+
+ gain = np.zeros(phi.shape)
+
+ idx_00 = np.where(phi < self.phi_m)[0]
+ gain[idx_00] = self.peak_gain - 2.5 * self.D_lmbda * self.D_lmbda * phi[idx_00] * phi[idx_00] / 1000
+
+ idx_0 = np.where((self.phi_m <= phi) & (phi < self.phi_r))[0]
+ gain[idx_0] = self.g1
+
+ idx_1 = np.where((self.phi_r <= phi) & (phi < 36))[0]
+ gain[idx_1] = 29 - 25 * np.log10(phi[idx_1])
+
+ idx_2 = np.where((36 <= phi) & (phi <= 180))[0]
+ gain[idx_2] = -10
+
+ return gain
+
+
+if __name__ == '__main__':
+ import matplotlib.pyplot as plt
+
+ phi = np.linspace(0.1, 100, num=100000)
+
+ # initialize antenna parameters
+ param27 = ParametersAntennaWithDiameter()
+ param27.antenna_pattern = "ITU-R S.465-6"
+ param27.frequency = 7500
+ param27.antenna_gain = 50
+ param27.diameter = 5
+ antenna27 = AntennaS465(param27)
+
+ gain27 = antenna27.calculate_gain(off_axis_angle_vec=phi)
+
+ param43 = ParametersAntennaWithDiameter()
+ param43.antenna_pattern = "ITU-R S.465-6"
+ param43.frequency = param27.frequency
+ param43.antenna_gain = 59
+ param43.diameter = 13
+ antenna43 = AntennaReg_RR_A7_3(param43)
+ gain43 = antenna43.calculate_gain(off_axis_angle_vec=phi)
+
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+
+ plt.semilogx(
+ phi, gain27, "-b",
+ label="ES for Sat Q (ITU-R S.465-6)",
+ )
+ plt.semilogx(
+ phi, gain43, "-r",
+ label="ES for Sat P (ITU-R Reg. R.R. Appendix 7, Annex 3)",
+ )
+
+ plt.title("ES antenna radiation patterns")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
+ plt.ylabel("Gain [dBi]")
+ plt.legend(loc="lower left")
+ plt.xlim((phi[0], phi[-1]))
+ # plt.ylim((-80, 10))
+
+ # 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()
diff --git a/sharc/antenna/antenna_rs1813.py b/sharc/antenna/antenna_rs1813.py
index d9ae05685..b2b779928 100644
--- a/sharc/antenna/antenna_rs1813.py
+++ b/sharc/antenna/antenna_rs1813.py
@@ -6,37 +6,43 @@
"""
from sharc.antenna.antenna import Antenna
-from sharc.parameters.parameters_eess_passive import ParametersEessPassive
+from sharc.parameters.parameters_eess_ss import ParametersEessSS
import numpy as np
import math
+
class AntennaRS1813(Antenna):
"""
- Implements the reference antenna pattern described in
- Recommendation ITU-R RS.1813-1. This is the antenna pattern for
- Earth exploration-satellite service (EESS) passive sensors to be
+ Implements the reference antenna pattern described in
+ Recommendation ITU-R RS.1813-1. This is the antenna pattern for
+ Earth exploration-satellite service (EESS) passive sensors to be
used in compatibility studies in the frequency range 1.4-100 GHz.
-
+
This implementation is in accordance with recommends 2, which refers to
- the case where a few interference sources dominate, or where peak
- interference values are required in the analysis, the following equations
- for the antenna pattern for spaceborne passive sensors should be used,
+ the case where a few interference sources dominate, or where peak
+ interference values are required in the analysis, the following equations
+ for the antenna pattern for spaceborne passive sensors should be used,
for antenna diameters greater than 2 times the wavelength.
"""
- def __init__(self, param: ParametersEessPassive):
+ def __init__(self, param: ParametersEessSS):
super().__init__()
- self.lmbda = 3e8 / ( param.frequency * 1e6 )
+ self.lmbda = 3e8 / (param.frequency * 1e6)
self.d_lmbda = param.antenna_diameter / self.lmbda
-
+
# for sensor F3 with n = 60% and D = 2.2 m, G_max = 52.7 dBi
- self.peak_gain = 10 * math.log10(param.antenna_efficiency * math.pow(math.pi * self.d_lmbda, 2))
- #self.peak_gain = param.antenna_gain
-
- self.phi_m = 22 / self.d_lmbda * \
- math.sqrt(5.5 + 5 * math.log10(math.pow(param.antenna_efficiency, 2) * self.d_lmbda))
+ self.peak_gain = 10 * \
+ math.log10(
+ param.antenna_efficiency *
+ math.pow(math.pi * self.d_lmbda, 2),
+ )
+ # self.peak_gain = param.antenna_gain
+ self.phi_m = 22 / self.d_lmbda * \
+ math.sqrt(
+ 5.5 + 5 * math.log10(math.pow(param.antenna_efficiency, 2) * self.d_lmbda),
+ )
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.absolute(kwargs["off_axis_angle_vec"])
@@ -44,15 +50,18 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
gain = np.zeros(phi.shape)
id0 = np.where(phi <= 69)[0]
- gain[id0] = self.peak_gain - 0.0018 * np.power(self.d_lmbda * phi[id0], 2)
-
+ gain[id0] = self.peak_gain - 0.0018 * \
+ np.power(self.d_lmbda * phi[id0], 2)
+
id1 = np.where((self.phi_m < phi) & (phi <= 69))[0]
- gain[id1] = np.maximum( gain[id1], 33 - 5*math.log10(self.d_lmbda) - 25*np.log10(phi[id1]) )
-
+ gain[id1] = np.maximum(
+ gain[id1], 33 - 5 * math.log10(self.d_lmbda) - 25 * np.log10(phi[id1]),
+ )
+
id2 = np.where((69 < phi) & (phi <= 180))[0]
- gain[id2] = -13 - 5*math.log10(self.d_lmbda)
-
- gain = np.maximum( -23, gain )
+ gain[id2] = -13 - 5 * math.log10(self.d_lmbda)
+
+ gain = np.maximum(-23, gain)
return gain
@@ -60,10 +69,10 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(0.1, 180, num = 10000)
+ phi = np.linspace(0.1, 180, num=10000)
# initialize antenna parameters
- param = ParametersEessPassive()
+ param = ParametersEessSS()
param.antenna_pattern = "ITU-R RS.1813-1"
param.frequency = 23900
param.antenna_gain = 52
@@ -71,13 +80,16 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param.antenna_efficiency = 0.6
antenna = AntennaRS1813(param)
- gain = antenna.calculate_gain(off_axis_angle_vec = phi)
+ gain = antenna.calculate_gain(off_axis_angle_vec=phi)
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
- plt.semilogx(phi, gain - param.antenna_gain, "-b", label = "$f = 23.9$ GHz")
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+ plt.semilogx(phi, gain - param.antenna_gain, "-b", label="$f = 23.9$ GHz")
plt.title("ITU-R RS.1813-1 antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Normalized antenna gain [dBi]")
plt.legend(loc="lower left")
plt.xlim((phi[0], phi[-1]))
diff --git a/sharc/antenna/antenna_rs1861_9a.py b/sharc/antenna/antenna_rs1861_9a.py
index d27b9be20..86aad9e0d 100644
--- a/sharc/antenna/antenna_rs1861_9a.py
+++ b/sharc/antenna/antenna_rs1861_9a.py
@@ -6,56 +6,60 @@
"""
from sharc.antenna.antenna import Antenna
-from sharc.parameters.parameters_eess_passive import ParametersEessPassive
+from sharc.parameters.parameters_eess_ss import ParametersEessSS
import numpy as np
+
class AntennaRS1861_9A(Antenna):
"""
Implements the reference antenna pattern described in Figure 9a from
- Recommendation ITU-R RS.1861.
+ Recommendation ITU-R RS.1861.
"""
- def __init__(self, param: ParametersEessPassive):
+ def __init__(self, param: ParametersEessSS):
super().__init__()
self.peak_gain = param.antenna_gain
-
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.absolute(kwargs["off_axis_angle_vec"])
gain = np.zeros(phi.shape)
id0 = np.where(phi <= 2.5)[0]
- gain[id0] = self.peak_gain - 5.2*np.power(phi[id0], 2) - 0.6*(phi[id0])
-
+ gain[id0] = self.peak_gain - 5.2 * \
+ np.power(phi[id0], 2) - 0.6 * (phi[id0])
+
id1 = np.where((2.5 < phi) & (phi <= 10))[0]
- gain[id1] = self.peak_gain - 3.47*phi[id1] - 25.3
-
+ gain[id1] = self.peak_gain - 3.47 * phi[id1] - 25.3
+
id2 = np.where((phi > 10))[0]
gain[id2] = self.peak_gain - 60
-
+
return gain
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(0, 18, num = 10000)
+ phi = np.linspace(0, 18, num=10000)
# initialize antenna parameters
- param = ParametersEessPassive()
+ param = ParametersEessSS()
param.antenna_pattern = "ITU-R RS.1861 Fig 9a"
param.antenna_gain = 40
antenna = AntennaRS1861_9A(param)
- gain = antenna.calculate_gain(off_axis_angle_vec = phi)
+ gain = antenna.calculate_gain(off_axis_angle_vec=phi)
- fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 5), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
plt.plot(phi, gain - param.antenna_gain, "-b")
plt.title("ITU-R RS.1861 Fig 9a antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Normalized antenna gain [dBi]")
plt.xlim((phi[0], phi[-1]))
plt.ylim((-60, 0))
diff --git a/sharc/antenna/antenna_rs1861_9b.py b/sharc/antenna/antenna_rs1861_9b.py
index 64dd89d66..91fc4d9c6 100644
--- a/sharc/antenna/antenna_rs1861_9b.py
+++ b/sharc/antenna/antenna_rs1861_9b.py
@@ -6,56 +6,60 @@
"""
from sharc.antenna.antenna import Antenna
-from sharc.parameters.parameters_eess_passive import ParametersEessPassive
+from sharc.parameters.parameters_eess_ss import ParametersEessSS
import numpy as np
+
class AntennaRS1861_9B(Antenna):
"""
Implements the reference antenna pattern described in Figure 9b from
- Recommendation ITU-R RS.1861.
+ Recommendation ITU-R RS.1861.
"""
- def __init__(self, param: ParametersEessPassive):
+ def __init__(self, param: ParametersEessSS):
super().__init__()
self.peak_gain = param.antenna_gain
-
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.absolute(kwargs["off_axis_angle_vec"])
gain = np.zeros(phi.shape)
id0 = np.where(phi <= 1.25)[0]
- gain[id0] = self.peak_gain - 22.8*np.power(phi[id0], 2) - 0.7*(phi[id0])
-
+ gain[id0] = self.peak_gain - 22.8 * \
+ np.power(phi[id0], 2) - 0.7 * (phi[id0])
+
id1 = np.where((1.25 < phi) & (phi <= 10))[0]
- gain[id1] = self.peak_gain - 3.257*phi[id1] - 32.429
-
+ gain[id1] = self.peak_gain - 3.257 * phi[id1] - 32.429
+
id2 = np.where((phi > 10))[0]
gain[id2] = self.peak_gain - 65
-
+
return gain
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(0, 12, num = 10000)
+ phi = np.linspace(0, 12, num=10000)
# initialize antenna parameters
- param = ParametersEessPassive()
+ param = ParametersEessSS()
param.antenna_pattern = "ITU-R RS.1861 Fig 9b"
param.antenna_gain = 46.7
antenna = AntennaRS1861_9B(param)
- gain = antenna.calculate_gain(off_axis_angle_vec = phi)
+ gain = antenna.calculate_gain(off_axis_angle_vec=phi)
- fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 5), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
plt.plot(phi, gain - param.antenna_gain, "-b")
plt.title("ITU-R RS.1861 Fig 9b antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Normalized antenna gain [dBi]")
plt.xlim((phi[0], phi[-1]))
plt.ylim((-70, 0))
diff --git a/sharc/antenna/antenna_rs1861_9c.py b/sharc/antenna/antenna_rs1861_9c.py
index d5919124a..ae3a07139 100644
--- a/sharc/antenna/antenna_rs1861_9c.py
+++ b/sharc/antenna/antenna_rs1861_9c.py
@@ -9,133 +9,135 @@
import numpy as np
+
class AntennaRS1861_9C(Antenna):
"""
Implements the reference antenna pattern described in Figure 9c from
- Recommendation ITU-R RS.1861.
+ Recommendation ITU-R RS.1861.
"""
def __init__(self):
super().__init__()
self.angle = np.linspace(-178.5, 173, 704)
- self.gain = np.array([-44.99, -45.02, -45.00, -44.90, -44.72, -44.58,
- -44.41, -44.19, -43.94, -43.64, -43.29, -42.88,
- -42.42, -41.91, -41.35, -40.76, -40.16, -39.58,
- -39.03, -38.53, -38.09, -37.37, -35.86, -36.22,
- -36.59, -35.48, -36.03, -37.46, -37.35, -37.14,
- -39.58, -40.71, -40.53, -40.53, -40.57, -40.61,
- -40.66, -41.51, -41.98, -42.50, -42.98, -43.37,
- -43.58, -42.34, -43.21, -44.54, -41.24, -40.92,
- -38.55, -42.53, -39.90, -39.94, -40.54, -41.38,
- -43.37, -43.09, -42.53, -42.46, -42.35, -42.17,
- -41.90, -41.51, -38.74, -39.84, -41.42, -42.00,
- -41.89, -41.54, -41.09, -40.65, -40.34, -40.54,
- -41.62, -42.07, -42.31, -42.49, -42.62, -42.70,
- -42.76, -42.61, -43.01, -43.62, -43.78, -43.91,
- -44.02, -44.10, -44.16, -44.18, -44.18, -44.22,
- -44.26, -44.30, -44.35, -44.40, -44.45, -44.48,
- -44.51, -44.53, -44.53, -44.51, -44.47, -44.41,
- -44.33, -44.24, -44.14, -44.03, -43.92, -43.41,
- -43.55, -44.00, -44.24, -44.25, -44.26, -44.25,
- -44.24, -44.23, -44.20, -44.17, -44.14, -44.11,
- -44.07, -44.04, -44.00, -43.96, -43.93, -43.89,
- -43.86, -43.84, -43.79, -43.51, -42.83, -44.48,
- -45.35, -43.95, -42.38, -41.08, -42.21, -43.02,
- -43.53, -43.54, -43.52, -42.96, -42.32, -41.92,
- -41.87, -41.97, -42.00, -40.43, -38.29, -37.30,
- -36.16, -34.96, -33.75, -32.62, -31.64, -30.80,
- -33.59, -37.23, -44.58, -43.50, -39.41, -37.12,
- -36.79, -37.05, -37.45, -37.52, -39.46, -38.97,
- -34.51, -31.88, -34.21, -36.81, -34.96, -34.09,
- -33.20, -32.80, -34.27, -34.46, -34.41, -34.69,
- -35.38, -35.40, -31.33, -28.54, -28.24, -34.10,
- -38.21, -36.14, -31.82, -27.22, -24.92, -25.85,
- -25.66, -25.61, -27.98, -27.19, -26.83, -26.97,
- -27.13, -25.93, -25.41, -24.69, -25.57, -26.66,
- -26.03, -24.96, -26.03, -27.23, -27.76, -28.49,
- -29.35, -30.31, -31.30, -32.65, -34.56, -34.14,
- -35.88, -37.02, -35.69, -34.56, -33.54, -33.55,
- -32.70, -29.91, -28.79, -27.74, -26.86, -25.98,
- -27.85, -27.84, -24.98, -28.11, -31.29, -34.43,
- -36.55, -32.95, -30.07, -30.52, -30.95, -31.31,
- -31.56, -31.64, -31.51, -31.12, -30.42, -29.22,
- -27.73, -26.09, -24.45, -22.97, -21.81, -21.11,
- -21.53, -23.84, -26.45, -25.60, -24.63, -22.21,
- -24.94, -21.85, -22.14, -22.42, -20.61, -18.24,
- -16.33, -15.85, -17.24, -17.36, -21.61, -25.45,
- -20.42, -22.94, -20.06, -14.79, -17.40, -13.46,
- -14.10, -12.54, -12.10, -11.62, -11.11, -10.57,
- -10.02, -9.48, -8.94, -8.43, -7.95, -7.51,
- -7.13, -6.82, -6.88, -8.11, -11.17, -12.35,
- -13.68, -15.07, -16.43, -17.68, -18.74, -19.52,
- -19.96, -19.84, -19.13, -18.08, -16.97, -16.07,
- -16.00, -15.08, -13.85, -12.46, -11.17, -10.27,
- -10.03, -10.73, -12.37, -14.68, -17.33, -20.03,
- -22.47, -25.97, -24.22, -25.91, -27.09, -26.43,
- -19.74, -10.09, -7.06, -7.26, -8.66, -10.51,
- -9.71, -8.90, -7.64, -5.13, -4.77, -4.60,
- -4.54, -5.12, -9.01, -10.72, -8.52, -5.68,
- -5.16, -4.34, -4.88, -3.50, 0.30, 4.92,
- 9.96, 15.04, 19.76, 23.72, 28.78, 29.68, 31.00,
- 32.00, 33.00, 34.40, 31.00, 28.90, 27.26, 24.64,
- 23.74, 22.84, 19.39, 15.44, 12.15, 6.36, -1.75,
- -10.11, -15.67, -10.96, -5.37, -3.10, -3.68,
- -7.87, -17.43, -29.34, -22.33, -18.07, -23.14,
- -21.89, -33.38, -35.00, -20.28, -16.43, -16.20,
- -17.35, -19.21, -22.32, -22.40, -19.40, -15.77,
- -12.03, -10.67, -10.44, -9.31, -8.27, -8.38,
- -8.41, -12.85, -11.45, -9.65, -7.73, -6.21,
- -6.44, -6.82, -6.80, -6.15, -5.39, -6.53,
- -18.72, -12.85, -11.70, -11.42, -11.01, -10.63,
- -10.46, -10.66, -11.39, -13.65, -17.35, -21.36,
- -24.72, -25.50, -25.27, -25.88, -25.83, -22.67,
- -20.65, -20.56, -19.68, -18.80, -18.71, -19.78,
- -20.04, -20.58, -22.58, -24.90, -27.03, -29.09,
- -28.45, -28.31, -28.19, -32.10, -29.01, -20.88,
- -21.45, -24.33, -25.21, -25.52, -26.47, -33.62,
- -32.81, -22.49, -16.86, -17.71, -17.69, -17.52,
- -17.31, -17.21, -17.64, -18.96, -20.86, -22.82,
- -24.64, -25.87, -25.98, -26.26, -26.62, -26.98,
- -27.30, -27.49, -27.49, -27.24, -22.06, -26.26,
- -23.21, -20.29, -19.36, -19.03, -18.97, -18.89,
- -19.30, -20.33, -21.54, -22.91, -24.23, -26.67,
- -26.13, -25.47, -25.41, -25.04, -24.92, -24.99,
- -25.08, -25.21, -25.36, -25.53, -27.37, -29.54,
- -25.92, -26.72, -28.23, -28.74, -28.55, -28.00,
- -27.38, -27.88, -28.97, -30.21, -31.07, -29.28,
- -24.89, -23.30, -22.23, -22.09, -23.27, -24.93,
- -26.79, -27.61, -28.34, -28.00, -28.97, -30.84,
- -33.27, -36.07, -42.84, -28.77, -27.17, -28.22,
- -25.00, -24.41, -24.80, -24.70, -24.71, -25.87,
- -26.70, -27.72, -28.93, -30.34, -32.52, -36.41,
- -39.71, -41.12, -40.40, -38.97, -33.59, -36.20,
- -34.81, -33.93, -32.75, -31.40, -29.99, -28.66,
- -26.95, -30.14, -28.23, -29.60, -31.93, -32.96,
- -40.79, -43.19, -42.76, -42.01, -41.19, -40.35,
- -39.46, -38.40, -37.03, -34.78, -32.51, -32.41,
- -31.74, -30.93, -31.91, -33.37, -35.39, -38.52,
- -39.64, -40.78, -41.82, -43.07, -41.41, -40.59,
- -39.60, -38.30, -36.87, -35.52, -34.33, -34.59,
- -35.14, -35.70, -36.24, -36.69, -37.01, -36.38,
- -40.25, -39.16, -40.97, -41.71, -40.35, -38.54,
- -37.17, -39.18, -39.07, -38.28, -36.92, -35.11,
- -33.70, -33.14, -35.18, -37.16, -39.40, -41.61,
- -43.46, -44.75, -43.97, -41.15, -41.18, -37.08,
- -39.43, -38.04, -38.53, -38.68, -38.76, -38.88,
- -38.86, -38.39, -38.96, -39.76, -39.18, -38.34,
- -37.39, -36.59, -36.15, -36.35, -37.51, -39.28,
- -41.21, -42.86, -43.65, -43.99, -44.06, -43.93,
- -43.64, -43.26, -42.83, -41.59, -43.01, -44.55,
- -44.87, -45.05, -45.13, -45.13, -45.08, -45.01,
- -44.96, -45.12, -43.85, -42.62, -42.87, -42.48,
- -41.70, -42.37, -43.00, -43.55, -43.82, -43.33,
- -40.58, -40.51, -34.91, -29.58, -32.33, -32.17,
- -31.31, -30.80, -31.71, -34.14, -37.80, -41.68,
- -44.42, -42.80, -41.95, -41.26, -40.40, -39.44,
- -38.48, -37.59, -36.87, -36.18, -36.64, -37.05,
- -38.90, -41.09, -43.16, -44.64, -45.12, -44.86,
- -44.13, -43.22, -42.38, -42.27, -42.06]),
-
+ self.gain = np.array([
+ -44.99, -45.02, -45.00, -44.90, -44.72, -44.58,
+ -44.41, -44.19, -43.94, -43.64, -43.29, -42.88,
+ -42.42, -41.91, -41.35, -40.76, -40.16, -39.58,
+ -39.03, -38.53, -38.09, -37.37, -35.86, -36.22,
+ -36.59, -35.48, -36.03, -37.46, -37.35, -37.14,
+ -39.58, -40.71, -40.53, -40.53, -40.57, -40.61,
+ -40.66, -41.51, -41.98, -42.50, -42.98, -43.37,
+ -43.58, -42.34, -43.21, -44.54, -41.24, -40.92,
+ -38.55, -42.53, -39.90, -39.94, -40.54, -41.38,
+ -43.37, -43.09, -42.53, -42.46, -42.35, -42.17,
+ -41.90, -41.51, -38.74, -39.84, -41.42, -42.00,
+ -41.89, -41.54, -41.09, -40.65, -40.34, -40.54,
+ -41.62, -42.07, -42.31, -42.49, -42.62, -42.70,
+ -42.76, -42.61, -43.01, -43.62, -43.78, -43.91,
+ -44.02, -44.10, -44.16, -44.18, -44.18, -44.22,
+ -44.26, -44.30, -44.35, -44.40, -44.45, -44.48,
+ -44.51, -44.53, -44.53, -44.51, -44.47, -44.41,
+ -44.33, -44.24, -44.14, -44.03, -43.92, -43.41,
+ -43.55, -44.00, -44.24, -44.25, -44.26, -44.25,
+ -44.24, -44.23, -44.20, -44.17, -44.14, -44.11,
+ -44.07, -44.04, -44.00, -43.96, -43.93, -43.89,
+ -43.86, -43.84, -43.79, -43.51, -42.83, -44.48,
+ -45.35, -43.95, -42.38, -41.08, -42.21, -43.02,
+ -43.53, -43.54, -43.52, -42.96, -42.32, -41.92,
+ -41.87, -41.97, -42.00, -40.43, -38.29, -37.30,
+ -36.16, -34.96, -33.75, -32.62, -31.64, -30.80,
+ -33.59, -37.23, -44.58, -43.50, -39.41, -37.12,
+ -36.79, -37.05, -37.45, -37.52, -39.46, -38.97,
+ -34.51, -31.88, -34.21, -36.81, -34.96, -34.09,
+ -33.20, -32.80, -34.27, -34.46, -34.41, -34.69,
+ -35.38, -35.40, -31.33, -28.54, -28.24, -34.10,
+ -38.21, -36.14, -31.82, -27.22, -24.92, -25.85,
+ -25.66, -25.61, -27.98, -27.19, -26.83, -26.97,
+ -27.13, -25.93, -25.41, -24.69, -25.57, -26.66,
+ -26.03, -24.96, -26.03, -27.23, -27.76, -28.49,
+ -29.35, -30.31, -31.30, -32.65, -34.56, -34.14,
+ -35.88, -37.02, -35.69, -34.56, -33.54, -33.55,
+ -32.70, -29.91, -28.79, -27.74, -26.86, -25.98,
+ -27.85, -27.84, -24.98, -28.11, -31.29, -34.43,
+ -36.55, -32.95, -30.07, -30.52, -30.95, -31.31,
+ -31.56, -31.64, -31.51, -31.12, -30.42, -29.22,
+ -27.73, -26.09, -24.45, -22.97, -21.81, -21.11,
+ -21.53, -23.84, -26.45, -25.60, -24.63, -22.21,
+ -24.94, -21.85, -22.14, -22.42, -20.61, -18.24,
+ -16.33, -15.85, -17.24, -17.36, -21.61, -25.45,
+ -20.42, -22.94, -20.06, -14.79, -17.40, -13.46,
+ -14.10, -12.54, -12.10, -11.62, -11.11, -10.57,
+ -10.02, -9.48, -8.94, -8.43, -7.95, -7.51,
+ -7.13, -6.82, -6.88, -8.11, -11.17, -12.35,
+ -13.68, -15.07, -16.43, -17.68, -18.74, -19.52,
+ -19.96, -19.84, -19.13, -18.08, -16.97, -16.07,
+ -16.00, -15.08, -13.85, -12.46, -11.17, -10.27,
+ -10.03, -10.73, -12.37, -14.68, -17.33, -20.03,
+ -22.47, -25.97, -24.22, -25.91, -27.09, -26.43,
+ -19.74, -10.09, -7.06, -7.26, -8.66, -10.51,
+ -9.71, -8.90, -7.64, -5.13, -4.77, -4.60,
+ -4.54, -5.12, -9.01, -10.72, -8.52, -5.68,
+ -5.16, -4.34, -4.88, -3.50, 0.30, 4.92,
+ 9.96, 15.04, 19.76, 23.72, 28.78, 29.68, 31.00,
+ 32.00, 33.00, 34.40, 31.00, 28.90, 27.26, 24.64,
+ 23.74, 22.84, 19.39, 15.44, 12.15, 6.36, -1.75,
+ -10.11, -15.67, -10.96, -5.37, -3.10, -3.68,
+ -7.87, -17.43, -29.34, -22.33, -18.07, -23.14,
+ -21.89, -33.38, -35.00, -20.28, -16.43, -16.20,
+ -17.35, -19.21, -22.32, -22.40, -19.40, -15.77,
+ -12.03, -10.67, -10.44, -9.31, -8.27, -8.38,
+ -8.41, -12.85, -11.45, -9.65, -7.73, -6.21,
+ -6.44, -6.82, -6.80, -6.15, -5.39, -6.53,
+ -18.72, -12.85, -11.70, -11.42, -11.01, -10.63,
+ -10.46, -10.66, -11.39, -13.65, -17.35, -21.36,
+ -24.72, -25.50, -25.27, -25.88, -25.83, -22.67,
+ -20.65, -20.56, -19.68, -18.80, -18.71, -19.78,
+ -20.04, -20.58, -22.58, -24.90, -27.03, -29.09,
+ -28.45, -28.31, -28.19, -32.10, -29.01, -20.88,
+ -21.45, -24.33, -25.21, -25.52, -26.47, -33.62,
+ -32.81, -22.49, -16.86, -17.71, -17.69, -17.52,
+ -17.31, -17.21, -17.64, -18.96, -20.86, -22.82,
+ -24.64, -25.87, -25.98, -26.26, -26.62, -26.98,
+ -27.30, -27.49, -27.49, -27.24, -22.06, -26.26,
+ -23.21, -20.29, -19.36, -19.03, -18.97, -18.89,
+ -19.30, -20.33, -21.54, -22.91, -24.23, -26.67,
+ -26.13, -25.47, -25.41, -25.04, -24.92, -24.99,
+ -25.08, -25.21, -25.36, -25.53, -27.37, -29.54,
+ -25.92, -26.72, -28.23, -28.74, -28.55, -28.00,
+ -27.38, -27.88, -28.97, -30.21, -31.07, -29.28,
+ -24.89, -23.30, -22.23, -22.09, -23.27, -24.93,
+ -26.79, -27.61, -28.34, -28.00, -28.97, -30.84,
+ -33.27, -36.07, -42.84, -28.77, -27.17, -28.22,
+ -25.00, -24.41, -24.80, -24.70, -24.71, -25.87,
+ -26.70, -27.72, -28.93, -30.34, -32.52, -36.41,
+ -39.71, -41.12, -40.40, -38.97, -33.59, -36.20,
+ -34.81, -33.93, -32.75, -31.40, -29.99, -28.66,
+ -26.95, -30.14, -28.23, -29.60, -31.93, -32.96,
+ -40.79, -43.19, -42.76, -42.01, -41.19, -40.35,
+ -39.46, -38.40, -37.03, -34.78, -32.51, -32.41,
+ -31.74, -30.93, -31.91, -33.37, -35.39, -38.52,
+ -39.64, -40.78, -41.82, -43.07, -41.41, -40.59,
+ -39.60, -38.30, -36.87, -35.52, -34.33, -34.59,
+ -35.14, -35.70, -36.24, -36.69, -37.01, -36.38,
+ -40.25, -39.16, -40.97, -41.71, -40.35, -38.54,
+ -37.17, -39.18, -39.07, -38.28, -36.92, -35.11,
+ -33.70, -33.14, -35.18, -37.16, -39.40, -41.61,
+ -43.46, -44.75, -43.97, -41.15, -41.18, -37.08,
+ -39.43, -38.04, -38.53, -38.68, -38.76, -38.88,
+ -38.86, -38.39, -38.96, -39.76, -39.18, -38.34,
+ -37.39, -36.59, -36.15, -36.35, -37.51, -39.28,
+ -41.21, -42.86, -43.65, -43.99, -44.06, -43.93,
+ -43.64, -43.26, -42.83, -41.59, -43.01, -44.55,
+ -44.87, -45.05, -45.13, -45.13, -45.08, -45.01,
+ -44.96, -45.12, -43.85, -42.62, -42.87, -42.48,
+ -41.70, -42.37, -43.00, -43.55, -43.82, -43.33,
+ -40.58, -40.51, -34.91, -29.58, -32.33, -32.17,
+ -31.31, -30.80, -31.71, -34.14, -37.80, -41.68,
+ -44.42, -42.80, -41.95, -41.26, -40.40, -39.44,
+ -38.48, -37.59, -36.87, -36.18, -36.64, -37.05,
+ -38.90, -41.09, -43.16, -44.64, -45.12, -44.86,
+ -44.13, -43.22, -42.38, -42.27, -42.06,
+ ]),
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.array(kwargs["off_axis_angle_vec"])
@@ -143,24 +145,27 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.minimum(phi, 173)
gain = np.interp(phi, self.angle, self.gain[0])
-
+
return gain
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(-180, 180, num = 1000)
+ phi = np.linspace(-180, 180, num=1000)
antenna = AntennaRS1861_9C()
- gain = antenna.calculate_gain(off_axis_angle_vec = phi)
+ gain = antenna.calculate_gain(off_axis_angle_vec=phi)
- fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 5), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
plt.plot(phi, gain, "-b")
plt.title("ITU-R RS.1861 Fig 9c antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Antenna gain [dBi]")
plt.xlim((phi[0], phi[-1]))
plt.ylim((-50, 40))
diff --git a/sharc/antenna/antenna_rs2043.py b/sharc/antenna/antenna_rs2043.py
new file mode 100644
index 000000000..3a402a3fa
--- /dev/null
+++ b/sharc/antenna/antenna_rs2043.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+@Created: Luciano Camilo on Tue Aug 10 19:42:00 2021
+
+"""
+
+import numpy as np
+
+
+class AntennaRS2043(object):
+ """
+ Implements the reference antenna pattern described in Table 9 from Recommendation
+ ITU-R RS.2043.
+
+ Attributes
+ ----------
+ antenna_gain (float): maximum gain of FS omni antenna
+ """
+
+ def calculate_gain(self, **kwargs) -> np.array:
+ phi = np.asarray(kwargs["off_axis_angle_vec"])
+ theta = np.asarray(kwargs["theta_vec"])
+ gain = self.get_gain_az(phi) + self.get_gain_el(theta)
+
+ return gain
+
+ def get_gain_az(self, phi: np.array) -> np.array:
+ """
+ Returns the antenna gain in the azimuth plane for the given direction.
+ """
+ gh = np.zeros(phi.shape)
+
+ for i in range(len(phi)):
+ if -0.542 <= phi[i] <= 0.542:
+ gh[i] = 0 - 45.53 * ((phi[i]) ** 2)
+
+ if 0.542 < phi[i] <= 5.053:
+ gh[i] = -11.210 - 4.0220 * phi[i]
+
+ if -0.542 > phi[i] >= -5.053:
+ gh[i] = -11.210 + 4.0220 * phi[i]
+
+ if 5.053 < phi[i] <= 14.708:
+ gh[i] = -26.720 - 0.9530 * phi[i]
+
+ if -5.053 > phi[i] >= -14.708:
+ gh[i] = -26.720 + 0.9530 * phi[i]
+
+ if 14.708 < phi[i] <= 30:
+ gh[i] = -35.031 - 0.3880 * phi[i]
+
+ if -14.708 > phi[i] >= -30:
+ gh[i] = -35.031 + 0.3880 * phi[i]
+
+ if 30 < phi[i] <= 59.915:
+ gh[i] = -41.836 - 0.1580 * phi[i]
+
+ if -30 > phi[i] >= -59.915:
+ gh[i] = -41.836 + 0.1580 * phi[i]
+
+ if phi[i] > 59.915:
+ gh[i] = -51.387
+
+ if phi[i] < -59.915:
+ gh[i] = -51.387
+
+ return gh
+
+ def get_gain_el(self, theta: np.array) -> np.array:
+ """
+ Returns the antenna gain in the elevation plane for the given direction.
+ """
+ gv = np.zeros(len(theta))
+
+ for i in range(len(theta)):
+ if -1.149 < theta[i] < 1.149:
+ gv[i] = 47 - 9.91 * ((theta[i]) ** 2)
+
+ if 1.149 <= theta[i] <= 9.587:
+ gv[i] = 35.189 - 1.9440 * theta[i]
+
+ if -1.149 >= theta[i] >= -9.587:
+ gv[i] = 35.189 + 1.9440 * theta[i]
+
+ if 9.587 <= theta[i] <= 29.976:
+ gv[i] = 21.043 - 0.4680 * theta[i]
+
+ if -9.587 >= theta[i] >= -29.976:
+ gv[i] = 21.043 + 0.4680 * theta[i]
+
+ if 29.976 <= theta[i] <= 50:
+ gv[i] = 12.562 - 0.1850 * theta[i]
+
+ if -29.976 >= theta[i] >= -50:
+ gv[i] = 12.562 + 0.1850 * theta[i]
+
+ if theta[i] > 50:
+ gv[i] = 3.291
+
+ if theta[i] < -50:
+ gv[i] = 3.291
+
+ return gv
+
+
+if __name__ == '__main__':
+
+ from matplotlib import pyplot as plt
+ """
+ Test routine - Comparison between SHARC code and Table 9 of ITU R - RS.2043-0
+ """
+
+ antenna = AntennaRS2043()
+ phi = np.arange(-90, 90, step=0.01)
+ theta = np.arange(-40, 40, step=0.01)
+ # phi = np.arange(0, 180, step=0.01)
+ # theta = np.arange(0, 90, step=0.01)
+ csfont = {'fontname': 'Times New Roman'}
+ hfont = {'fontname': 'Times New Roman'}
+
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 6))
+
+ # ax1.semilogx(theta, antenna.get_gain_el(theta=theta), '-',color='blue', label='ITU-R RS.2043-0 (Table 9)')
+ # ax2.semilogx(phi, antenna.get_gain_az(phi=phi), '-', color='darkorange', label='ITU-R RS.2043-0 (Table 9)')
+
+ ax1.plot(
+ theta, antenna.get_gain_el(theta=theta), '-',
+ color='blue', label='ITU-R RS.2043-0 (Table 9)',
+ )
+ ax2.plot(
+ phi, antenna.get_gain_az(phi=phi) + 47, '-',
+ color='darkorange', label='ITU-R RS.2043-0 (Table 9)',
+ )
+ ax1.grid()
+ ax2.grid()
+ ax1.legend()
+ ax2.legend()
+
+ ax1.set_title('ITU-R RS.2043-0 - Vertical Antenna Pattern')
+ ax2.set_title('ITU-R RS.2043-0 - Horizontal Antenna Pattern')
+ ax1.set_xlabel('Elevation angle (degrees)', fontsize=12, color='black')
+ ax1.set_ylabel('Gain (dBi)', fontsize=12, color='black')
+ ax2.set_xlabel('Azimuth (degrees)', fontsize=12, color='black')
+ ax2.set_ylabel('Gain (dBi)', fontsize=12, color='black')
+
+ plt.show()
diff --git a/sharc/antenna/antenna_s1528.py b/sharc/antenna/antenna_s1528.py
index a4e52ba58..c2b95bad3 100644
--- a/sharc/antenna/antenna_s1528.py
+++ b/sharc/antenna/antenna_s1528.py
@@ -4,22 +4,146 @@
@author: edgar
"""
-
+import sys
from sharc.antenna.antenna import Antenna
-from sharc.parameters.parameters_fss_ss import ParametersFssSs
-
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+from sharc.parameters.constants import SPEED_OF_LIGHT
import math
import numpy as np
-import sys
+from scipy.special import jn, jn_zeros
+
+NUM_OF_BESSEL_ROOTS = 3
+
+
+class AntennaS1528Taylor(Antenna):
+ """
+ Implements Recommendation ITU-R S.1528-0 Section 1.4: Satellite antenna reference pattern given by an analytical
+ function which models the side lobes of the non-GSO satellite operating in the fixed-satellite service below 30 GHz.
+ It is proposed to use a circular Taylor illumination function which gives the maximum flexibility to
+ adapt the theoretical pattern to the real one. It takes into account the side-lobes effect of an antenna
+ diagram.
+ """
+
+ def __init__(self, param: ParametersAntennaS1528):
+ super().__init__()
+ # Gmax
+ self.peak_gain = param.antenna_gain
+ self.frequency_mhz = param.frequency
+ self.bandwidth_mhz = param.bandwidth
+
+ # Wavelength of the lowest frequency of the band of interest (in meters).
+ self.lamb = (SPEED_OF_LIGHT / 1e6) / (self.frequency_mhz - self.bandwidth_mhz / 2)
+
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ self.slr = param.slr
+
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ self.n_side_lobes = param.n_side_lobes
+
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ self.l_r = param.l_r
+ self.l_t = param.l_t
+
+ # Intermediary variables
+ self.A = (1 / np.pi) * np.arccosh(10 ** (self.slr / 20))
+ self.j1_roots = jn_zeros(1, self.n_side_lobes) / np.pi
+ self.sigma = self.j1_roots[-1] / np.sqrt(self.A ** 2 + (self.n_side_lobes - 1 / 2) ** 2)
+ self.mu = jn_zeros(1, NUM_OF_BESSEL_ROOTS) / np.pi
+
+ def calculate_gain(self, *args, **kwargs) -> np.array:
+ # The reference angles for the simulator and the antenna realisation are switched.
+ # Local theta is simulator off_axis_angle and local phi is simulator theta_vec
+ if 'off_axis_angle_vec' not in kwargs:
+ raise ValueError("off_axis_angle_vec vector must be given")
+ if 'theta_vec' not in kwargs:
+ raise ValueError("theta vector must be given")
+ theta = np.abs(np.radians(kwargs.get('off_axis_angle_vec', 0)))
+ phi = np.abs(np.radians(kwargs.get('theta_vec', 0)))
+
+ u = (np.pi / self.lamb) * np.sqrt((self.l_r * np.sin(theta) * np.cos(phi)) ** 2 +
+ (self.l_t * np.sin(theta) * np.sin(phi)) ** 2)
+
+ v = np.ones(u.shape + (NUM_OF_BESSEL_ROOTS,))
+
+ for i, ui in enumerate(self.mu):
+ v[..., i] = (1 - u ** 2 / (np.pi ** 2 * self.sigma ** 2 *
+ (self.A ** 2 + (i + 1 - 0.5) ** 2))) / (1 - (u / (np.pi * ui)) ** 2)
+
+ # Take care of divide-by-zero
+ with np.errstate(divide='ignore', invalid='ignore'):
+ gain = self.peak_gain + 20 * \
+ np.log10(np.abs((2 * jn(1, u) / u) * np.prod(v, axis=-1)))
+
+ # Replace undefined values with -inf (or other desired value)
+ gain = np.nan_to_num(gain, nan=-np.inf)
+ # Replace values that were substituded specifically
+ # because u == 0 with Lim (gain)_(u -> 0) = peak_gain
+ gain[u == 0] = self.peak_gain
+
+ return gain
+
+
+class AntennaS1528Leo(Antenna):
+ """
+ Implements Recommendation ITU-R S.1528-0 Section 1.3 - LEO: Satellite antenna radiation
+ patterns for LEO orbit satellite antennas operating in the
+ fixed-satellite service below 30 GHz.
+ """
+
+ def __init__(self, param: ParametersAntennaS1528):
+ super().__init__()
+ self.peak_gain = param.antenna_gain
+ self.psi_b = param.antenna_3_dB_bw / 2
+ # near-in-side-lobe level (dB) relative to the peak gain required by
+ # the system design
+ self.l_s = -6.75
+ # for elliptical antennas, this is the ratio major axis/minor axis
+ # we assume circular antennas, so z = 1
+ # self.z = 1
+ # far-out side-lobe level [dBi]
+ self.l_f = 5
+ self.y = 1.5 * self.psi_b
+ self.z = self.y * np.power(10, 0.04 * (self.peak_gain + self.l_s - self.l_f))
+
+ def calculate_gain(self, *args, **kwargs) -> np.array:
+ """
+ Calculates the gain in the given direction.
+
+ Parameters
+ ----------
+ off_axis_angle_vec (np.array): azimuth angles (phi_vec) [degrees]
+
+ Returns
+ -------
+ gain (np.array): gain corresponding to each of the given directions
+ """
+ psi = np.absolute(kwargs["off_axis_angle_vec"])
+ gain = np.zeros(len(psi))
+
+ idx_0 = np.where((psi <= self.y))[0]
+ gain[idx_0] = self.peak_gain - 3 * \
+ np.power(psi[idx_0] / self.psi_b, 2)
+
+ idx_1 = np.where((self.y < psi) & (psi <= self.z))[0]
+ gain[idx_1] = self.peak_gain + self.l_s - \
+ 25 * np.log10(psi[idx_1] / self.y)
+
+ idx_3 = np.where((self.z < psi) & (psi <= 180))[0]
+ gain[idx_3] = self.l_f
+
+ return gain
+
class AntennaS1528(Antenna):
"""
Implements Recommendation ITU-R S.1528-0: Satellite antenna radiation
patterns for non-geostationary orbit satellite antennas operating in the
- fixed-satellite service below 30 GHz
+ fixed-satellite service below 30 GHz.
+ This implementation refers to the pattern described in S.1528-0 Section 1.2
"""
- def __init__(self, param: ParametersFssSs):
+ def __init__(self, param: ParametersAntennaS1528):
super().__init__()
self.peak_gain = param.antenna_gain
@@ -35,28 +159,34 @@ def __init__(self, param: ParametersFssSs):
self.l_f = 0
# back-lobe level
- self.l_b = np.maximum(0, 15 + self.l_s + 0.25*self.peak_gain + 5*math.log10(self.z))
-
+ self.l_b = np.maximum(
+ 0, 15 + self.l_s + 0.25 *
+ self.peak_gain + 5 * math.log10(self.z),
+ )
# one-half the 3 dB beamwidth in the plane of interest
- self.psi_b = param.antenna_3_dB/2
+ self.psi_b = param.antenna_3_dB_bw / 2
if self.l_s == -15:
- self.a = 2.58*math.sqrt(1 - 1.4*math.log10(self.z))
+ self.a = 2.58 * math.sqrt(1 - 1.4 * math.log10(self.z))
elif self.l_s == -20:
- self.a = 2.58*math.sqrt(1 - 1.0*math.log10(self.z))
+ self.a = 2.58 * math.sqrt(1 - 1.0 * math.log10(self.z))
elif self.l_s == -25:
- self.a = 2.58*math.sqrt(1 - 0.6*math.log10(self.z))
+ self.a = 2.58 * math.sqrt(1 - 0.6 * math.log10(self.z))
elif self.l_s == -30:
- self.a = 2.58*math.sqrt(1 - 0.4*math.log10(self.z))
+ self.a = 2.58 * math.sqrt(1 - 0.4 * math.log10(self.z))
else:
- sys.stderr.write("ERROR\nInvalid AntennaS1528 L_s parameter: " + str(self.l_s))
+ sys.stderr.write(
+ "ERROR\nInvalid AntennaS1528 L_s parameter: " + str(self.l_s),
+ )
sys.exit(1)
self.b = 6.32
self.alpha = 1.5
- self.x = self.peak_gain + self.l_s + 25*math.log10(self.b * self.psi_b)
- self.y = self.b * self.psi_b * math.pow(10, 0.04 * (self.peak_gain + self.l_s - self.l_f))
+ self.x = self.peak_gain + self.l_s + \
+ 25 * math.log10(self.b * self.psi_b)
+ self.y = self.b * self.psi_b * \
+ math.pow(10, 0.04 * (self.peak_gain + self.l_s - self.l_f))
def calculate_gain(self, *args, **kwargs) -> np.array:
psi = np.absolute(kwargs["off_axis_angle_vec"])
@@ -64,12 +194,15 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
gain = np.zeros(len(psi))
idx_0 = np.where(psi < self.a * self.psi_b)[0]
- gain[idx_0] = self.peak_gain - 3 * np.power(psi[idx_0] / self.psi_b, self.alpha)
+ gain[idx_0] = self.peak_gain - 3 * \
+ np.power(psi[idx_0] / self.psi_b, self.alpha)
- idx_1 = np.where((self.a * self.psi_b < psi) & (psi <= 0.5 * self.b * self.psi_b))[0]
+ idx_1 = np.where((self.a * self.psi_b < psi) &
+ (psi <= 0.5 * self.b * self.psi_b))[0]
gain[idx_1] = self.peak_gain + self.l_s + 20 * math.log10(self.z)
- idx_2 = np.where((0.5 * self.b * self.psi_b < psi) & (psi <= self.b * self.psi_b))[0]
+ idx_2 = np.where((0.5 * self.b * self.psi_b < psi) &
+ (psi <= self.b * self.psi_b))[0]
gain[idx_2] = self.peak_gain + self.l_s
idx_3 = np.where((self.b * self.psi_b < psi) & (psi <= self.y))[0]
@@ -87,46 +220,164 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
if __name__ == '__main__':
import matplotlib.pyplot as plt
+ ## Plot gains for ITU-R-S.1528-SECTION1.2
# initialize antenna parameters
- param = ParametersFssSs()
- param.antenna_gain = 39
- param.antenna_pattern = "ITU-R S.1528-0"
- param.antenna_3_dB = 2
- psi = np.linspace(0, 30, num = 1000)
+ param = ParametersAntennaS1528()
+ param.antenna_gain = 37
+ param.antenna_pattern = "ITU-R-S.1528-SECTION1.2"
+ param.antenna_3_dB_bw = 4.4127
+
+ max_psi = 30 * param.antenna_3_dB_bw / 2
+ psi = np.linspace(0, max_psi, num=100)
param.antenna_l_s = -15
antenna = AntennaS1528(param)
- gain15 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain15 = antenna.calculate_gain(off_axis_angle_vec=psi)
param.antenna_l_s = -20
antenna = AntennaS1528(param)
- gain20 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain20 = antenna.calculate_gain(off_axis_angle_vec=psi)
param.antenna_l_s = -25
antenna = AntennaS1528(param)
- gain25 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain25 = antenna.calculate_gain(off_axis_angle_vec=psi)
param.antenna_l_s = -30
antenna = AntennaS1528(param)
- gain30 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain30 = antenna.calculate_gain(off_axis_angle_vec=psi)
+
+ ## Plot gains for ITU-R-S.1528-LEO
+ param.antenna_pattern = "ITU-R-S.1528-LEO"
+
+ param.antenna_l_s = -6.75
+ antenna = AntennaS1528Leo(param)
+ gain_leo = antenna.calculate_gain(off_axis_angle_vec=psi)
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(figsize=(8, 7), facecolor='w',
+ edgecolor='k') # create a figure object
- plt.plot(psi, gain15 - param.antenna_gain, "-b", label="$L_S = -15$ dB")
- plt.plot(psi, gain20 - param.antenna_gain, "-r", label="$L_S = -20$ dB")
- plt.plot(psi, gain25 - param.antenna_gain, "-g", label="$L_S = -25$ dB")
- plt.plot(psi, gain30 - param.antenna_gain, "-k", label="$L_S = -30$ dB")
+ psi_norm = psi / (param.antenna_3_dB_bw / 2)
+ plt.plot(psi_norm, gain15 - param.antenna_gain, "-b", label="$L_S = -15$ dB")
+ plt.plot(psi_norm, gain20 - param.antenna_gain, "-r", label="$L_S = -20$ dB")
+ plt.plot(psi_norm, gain25 - param.antenna_gain, "-g", label="$L_S = -25$ dB")
+ plt.plot(psi_norm, gain30 - param.antenna_gain, "-k", label="$L_S = -30$ dB")
+ plt.plot(psi_norm, gain_leo - param.antenna_gain, "-c", label="$L_S = -6.75$ dB (R1.3 - LEO)")
plt.ylim((-40, 10))
- plt.xlim((0, 30))
+ plt.yticks(np.arange(-40, 11, 5))
+ plt.xlim((0, np.max(psi_norm)))
+ plt.xticks(np.arange(0, 35, 5))
plt.title("ITU-R S.1528-0 antenna radiation pattern")
- plt.xlabel("Relative off-axis angle, $\psi/\psi_{3dB}$")
- plt.ylabel("Gain relative to $G_{max}$ [dB]")
+ plt.xlabel(r"Relative off-axis angle, $\psi/\psi_{3dB}$")
+ plt.ylabel(r"Gain relative to $G_{max}$ [dB]")
plt.legend(loc="upper right")
+ plt.grid()
-# 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())
-
+ ## Plot gains for ITU-R-S.1528-LEO
+ # initialize antenna parameters
+ param = ParametersAntennaS1528()
+ param.antenna_gain = 35
+ param.antenna_pattern = "ITU-R-S.1528-LEO"
+ param.antenna_3_dB_bw = 1.6
+ psi = np.linspace(0, 20, num=1000)
+
+ param.antenna_l_s = -6.75
+ antenna = AntennaS1528Leo(param)
+ gain_leo = antenna.calculate_gain(off_axis_angle_vec=psi)
+
+ fig = plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k') # create a figure object
+ psi_norm = psi / (param.antenna_3_dB_bw / 2)
+ plt.plot(psi_norm, gain_leo, "-b", label="$L_S = -6.75$ dB")
+
+ # plt.ylim((-40, 10))
+ plt.xlim((0, np.max(psi_norm)))
+ plt.xticks(np.arange(np.floor(np.max(psi_norm))))
+ plt.title("ITU-R S.1528-0 LEO antenna radiation pattern")
+ plt.xlabel(r"Relative off-axis angle, $\psi/\psi_{3dB}$")
+ plt.ylabel(r"Gain relative to $G_{max}$ [dB]")
+ plt.legend(loc="upper right")
plt.grid()
+
+ # Section 1.4 (Taylor) - Compare to Fig 6
+ frequency = 12000 # MHz
+ bandwidth = 10 # MHz
+ antenna_gain = 0 # dBi
+ slr = 20 # dB
+ n_side_lobes = 4
+ lamb = (SPEED_OF_LIGHT / 1e6) / (frequency - bandwidth / 2)
+ beam_radius = 350 # km
+ sat_altitude = 1446 # km
+ a = np.arctan(beam_radius / (sat_altitude)) # radians
+ l_r = 0.74 * lamb / np.sin(a)
+ l_t = l_r
+ params_rolloff_7 = ParametersAntennaS1528(
+ antenna_gain=0,
+ frequency=12000,
+ bandwidth=10,
+ slr=20,
+ n_side_lobes=4,
+ l_r=l_r,
+ l_t=l_t,
+ )
+
+ # Create an instance of AntennaS1528Taylor
+ antenna_rolloff_7 = AntennaS1528Taylor(params_rolloff_7)
+
+ # Define phi angles from 0 to 60 degrees for plotting
+ theta_angles = np.arange(0, 60.1, 0.1)
+
+ # Calculate gains for each phi angle at a fixed theta angle (e.g., theta=0)
+ gain_rolloff_7 = antenna_rolloff_7.calculate_gain(off_axis_angle_vec=theta_angles,
+ theta_vec=np.zeros_like(theta_angles))
+
+ l_r = 0.64 * lamb / np.sin(a)
+ l_t = l_r
+ params_rolloff_5 = ParametersAntennaS1528(
+ antenna_gain=0,
+ frequency=12000,
+ bandwidth=10,
+ slr=20,
+ n_side_lobes=4,
+ l_r=l_r,
+ l_t=l_t,
+ )
+
+ # Create an instance of AntennaS1528Taylor
+ antenna_rolloff_5 = AntennaS1528Taylor(params_rolloff_5)
+
+ gain_rolloff_5 = antenna_rolloff_5.calculate_gain(off_axis_angle_vec=theta_angles,
+ theta_vec=np.zeros_like(theta_angles))
+
+ l_r = 0.51 * lamb / np.sin(a)
+ l_t = l_r
+ params_rolloff_3 = ParametersAntennaS1528(
+ antenna_gain=0,
+ frequency=12000,
+ bandwidth=10,
+ slr=20,
+ n_side_lobes=4,
+ l_r=l_r,
+ l_t=l_t,
+ )
+
+ # Create an instance of AntennaS1528Taylor
+ antenna_rolloff_3 = AntennaS1528Taylor(params_rolloff_3)
+
+ gain_rolloff_3 = antenna_rolloff_3.calculate_gain(off_axis_angle_vec=theta_angles,
+ theta_vec=np.zeros_like(theta_angles))
+
+ # Plot the antenna gain as a function of phi angle
+ plt.figure(figsize=(10, 6))
+ plt.plot(theta_angles, gain_rolloff_3, label='roll_off=3')
+ plt.plot(theta_angles, gain_rolloff_5, label='roll_off=5')
+ plt.plot(theta_angles, gain_rolloff_7, label='roll_off=7')
+ plt.xlabel('Theta (degrees)')
+ plt.ylabel('Gain (dB)')
+ plt.title('Normalized Antenna - Section 1.4')
+ plt.legend()
+ plt.xticks(np.arange(0, 60, 10))
+ plt.minorticks_on()
+ plt.gca().xaxis.set_minor_locator(plt.MultipleLocator(2))
+ plt.grid(True, which='both')
+
plt.show()
diff --git a/sharc/antenna/antenna_s1855.py b/sharc/antenna/antenna_s1855.py
index 320c0cd31..b762cd9b8 100644
--- a/sharc/antenna/antenna_s1855.py
+++ b/sharc/antenna/antenna_s1855.py
@@ -11,6 +11,7 @@
from sharc.antenna.antenna import Antenna
from sharc.parameters.parameters_fss_ss import ParametersFssSs
+
class AntennaS1855(Antenna):
""""
Implements amntenna radiation pattern for Earth Station according
@@ -40,7 +41,6 @@ def __init__(self, params: ParametersFssSs):
self.frequency = params.frequency
self.antenna_gain = params.antenna_gain
-
def calculate_gain(self, *args, **kwargs) -> np.array:
"""
Calculates the gain of the antenna af arrays of angles.
@@ -62,15 +62,14 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
phi_list = kwargs["off_axis_angle_vec"]
theta_list = kwargs["theta_vec"]
- gain = np.empty(phi_list.shape, dtype = np.float)
+ gain = np.empty(phi_list.shape, dtype=float)
for i in range(len(phi_list)):
gain[i] = self.get_gain_pair(phi_list[i], theta_list[i])
return gain
-
- def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float:
+ def get_gain_pair(self, phi: float, theta: float) -> float:
"""
Calculates the gain of the antenna of a pair of angles.
@@ -88,7 +87,7 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float:
"""
gain = None
wavelength = 3e8 / (self.frequency * 1000000)
- d_to_wavel = self.diameter/wavelength
+ d_to_wavel = self.diameter / wavelength
phimin1 = 15.85 * math.pow(d_to_wavel, -0.6)
phimin2 = 118 * math.pow(d_to_wavel, -1.06)
if phimin1 > phimin2:
@@ -96,25 +95,29 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float:
else:
phimin = phimin2
-
if d_to_wavel >= 46.8:
- if phi < phimin:
+ if phi < phimin:
gain = self.antenna_gain
elif phi >= phimin and phi <= 7:
- gain = 29 + 3 * np.power(np.sin(theta * math.pi / 180) , 2) - 25 * np.log10(phi)
+ gain = 29 + 3 * \
+ np.power(np.sin(theta * math.pi / 180), 2) - \
+ 25 * np.log10(phi)
elif phi > 7 and phi <= 9.2:
- gain = 7.9 + (3 * np.power(np.sin(theta * np.pi / 180),2)) * (9.2 - phi) / 2.2
+ gain = 7.9 + \
+ (3 * np.power(np.sin(theta * np.pi / 180), 2)) * (9.2 - phi) / 2.2
elif phi > 9.2 and phi <= 48:
gain = 32 - 25 * np.log10(phi)
else:
return -10
elif d_to_wavel < 46.8 and d_to_wavel >= 15:
- if phi < phimin:
+ if phi < phimin:
gain = self.antenna_gain
elif phi >= phimin and phi <= 7:
- gain = 29 + 3 * np.pow(np.sin(theta * np.pi / 180),2) - 25 * np.log10(phi)
+ gain = 29 + 3 * \
+ np.pow(np.sin(theta * np.pi / 180), 2) - 25 * np.log10(phi)
elif phi > 7 and phi <= 9.2:
- gain = 7.9 + (3 * np.pow(np.sin(theta * np.pi / 180)),2) * (9.2 - phi) / 2.2
+ gain = 7.9 + \
+ (3 * np.pow(np.sin(theta * np.pi / 180)), 2) * (9.2 - phi) / 2.2
elif phi > 9.2 and phi <= 30.2:
gain = 32 - 25 * np.log10(phi)
elif phi > 30.2 and phi <= 70:
@@ -138,16 +141,20 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float:
antenna = AntennaS1855(params_fss_ss)
# Plot radiation pattern for theta = 90 degrees
- off_axis_angle_vec = np.linspace(0.01, 180, num = 10000)
- theta_90 = 90*np.ones(off_axis_angle_vec.shape)
- theta = 0*np.ones(off_axis_angle_vec.shape)
-
- gain_90 = antenna.calculate_gain(off_axis_angle_vec = off_axis_angle_vec, theta_vec = theta_90)
- gain = antenna.calculate_gain(off_axis_angle_vec = off_axis_angle_vec, theta_vec = theta)
-
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k')
- plt.semilogx(off_axis_angle_vec, gain_90, "-b", label = "$\\theta = 90$ deg")
- plt.semilogx(off_axis_angle_vec, gain, "-r", label = "$\\theta = 0$ deg")
+ off_axis_angle_vec = np.linspace(0.01, 180, num=10000)
+ theta_90 = 90 * np.ones(off_axis_angle_vec.shape)
+ theta = 0 * np.ones(off_axis_angle_vec.shape)
+
+ gain_90 = antenna.calculate_gain(
+ off_axis_angle_vec=off_axis_angle_vec, theta_vec=theta_90,
+ )
+ gain = antenna.calculate_gain(
+ off_axis_angle_vec=off_axis_angle_vec, theta_vec=theta,
+ )
+
+ fig = plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k')
+ plt.semilogx(off_axis_angle_vec, gain_90, "-b", label="$\\theta = 90$ deg")
+ plt.semilogx(off_axis_angle_vec, gain, "-r", label="$\\theta = 0$ deg")
plt.xlabel("Off-axis angle, $\\varphi$ [deg]")
plt.ylabel("Gain [dBi]")
diff --git a/sharc/antenna/antenna_s465.py b/sharc/antenna/antenna_s465.py
index 23826435e..4ae77d868 100644
--- a/sharc/antenna/antenna_s465.py
+++ b/sharc/antenna/antenna_s465.py
@@ -11,6 +11,7 @@
import numpy as np
import math
+
class AntennaS465(Antenna):
"""
Implements the Earth station antenna pattern in the fixed-satellite service
@@ -20,7 +21,7 @@ class AntennaS465(Antenna):
def __init__(self, param: ParametersFssEs):
super().__init__()
self.peak_gain = param.antenna_gain
- lmbda = 3e8 / ( param.frequency * 1e6 )
+ lmbda = 3e8 / (param.frequency * 1e6)
D_lmbda = param.diameter / lmbda
if D_lmbda >= 50:
@@ -40,7 +41,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
gain[idx_1] = 32 - 25 * np.log10(phi[idx_1])
idx_2 = np.where((48 <= phi) & (phi <= 180))[0]
- gain [idx_2] = -10
+ gain[idx_2] = -10
return gain
@@ -48,7 +49,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(0.1, 100, num = 100000)
+ phi = np.linspace(0.1, 100, num=100000)
# initialize antenna parameters
param27 = ParametersFssEs()
@@ -58,7 +59,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param27.diameter = 0.45
antenna27 = AntennaS465(param27)
- gain27 = antenna27.calculate_gain(off_axis_angle_vec = phi)
+ gain27 = antenna27.calculate_gain(off_axis_angle_vec=phi)
param43 = ParametersFssEs()
param43.antenna_pattern = "ITU-R S.465-6"
@@ -66,23 +67,32 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param43.antenna_gain = 50
param43.diameter = 1.8
antenna43 = AntennaS465(param43)
- gain43 = antenna43.calculate_gain(off_axis_angle_vec = phi)
-
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
-
- plt.semilogx(phi, gain27 - param27.antenna_gain, "-b", label = "$f = 27$ $GHz,$ $D = 0.45$ $m$")
- plt.semilogx(phi, gain43 - param43.antenna_gain, "-r", label = "$f = 43$ $GHz,$ $D = 1.8$ $m$")
+ gain43 = antenna43.calculate_gain(off_axis_angle_vec=phi)
+
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+
+ plt.semilogx(
+ phi, gain27 - param27.antenna_gain, "-b",
+ label="$f = 27$ $GHz,$ $D = 0.45$ $m$",
+ )
+ plt.semilogx(
+ phi, gain43 - param43.antenna_gain, "-r",
+ label="$f = 43$ $GHz,$ $D = 1.8$ $m$",
+ )
plt.title("ITU-R S.465-6 antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ 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((-80, 10))
- #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())
+ # 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()
diff --git a/sharc/antenna/antenna_s580.py b/sharc/antenna/antenna_s580.py
index c2fc9fcd3..0a3213b9a 100644
--- a/sharc/antenna/antenna_s580.py
+++ b/sharc/antenna/antenna_s580.py
@@ -10,6 +10,7 @@
import numpy as np
+
class AntennaS580(Antenna):
"""
Implements the Earth station antenna pattern in the EESS/ISS service
@@ -19,11 +20,11 @@ class AntennaS580(Antenna):
def __init__(self, param: ParametersFssEs):
super().__init__()
self.peak_gain = param.antenna_gain
- lmbda = 3e8 / ( param.frequency * 1e6 )
+ lmbda = 3e8 / (param.frequency * 1e6)
self.phi_min = 1
if 100 * lmbda / param.diameter > 1:
- self.phi_min = 100 * lmbda / param.diameter
+ self.phi_min = 100 * lmbda / param.diameter
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.absolute(kwargs["off_axis_angle_vec"])
@@ -45,7 +46,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
if __name__ == '__main__':
import matplotlib.pyplot as plt
- phi = np.linspace(0.1, 100, num = 100000)
+ phi = np.linspace(0.1, 100, num=100000)
# initialize antenna parameters
param27 = ParametersFssEs()
@@ -55,7 +56,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param27.diameter = 9.6
antenna27 = AntennaS580(param27)
- gain27 = antenna27.calculate_gain(off_axis_angle_vec = phi)
+ gain27 = antenna27.calculate_gain(off_axis_angle_vec=phi)
param = ParametersFssEs()
param.antenna_pattern = "ITU-R S.580-6"
@@ -63,23 +64,32 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param.antenna_gain = 50
param.diameter = 0.45
antenna = AntennaS580(param)
- gain = antenna.calculate_gain(off_axis_angle_vec = phi)
-
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
-
- plt.semilogx(phi, gain27 - param27.antenna_gain, "-b", label = "$f = 27$ $GHz,$ $D = 9.6$ $m$")
- plt.semilogx(phi, gain - param.antenna_gain, "-r", label = "$f = 27$ $GHz,$ $D = 0.45$ $m$")
+ gain = antenna.calculate_gain(off_axis_angle_vec=phi)
+
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+
+ plt.semilogx(
+ phi, gain27 - param27.antenna_gain, "-b",
+ label="$f = 27$ $GHz,$ $D = 9.6$ $m$",
+ )
+ plt.semilogx(
+ phi, gain - param.antenna_gain, "-r",
+ label="$f = 27$ $GHz,$ $D = 0.45$ $m$",
+ )
plt.title("ITU-R S.580 antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ 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((-80, 10))
- #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())
+ # 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()
diff --git a/sharc/antenna/antenna_s672.py b/sharc/antenna/antenna_s672.py
index f71c13a63..9c5eb62bb 100644
--- a/sharc/antenna/antenna_s672.py
+++ b/sharc/antenna/antenna_s672.py
@@ -11,6 +11,7 @@
import numpy as np
import sys
+
class AntennaS672(Antenna):
"""
Implements the satellite antenna pattern in the fixed-satellite service
@@ -29,14 +30,16 @@ def __init__(self, param: ParametersFssSs):
elif self.l_s == -30:
self.a = 3.16
else:
- sys.stderr.write("ERROR\nInvalid AntennaS672 L_s parameter: " + self.l_s)
+ sys.stderr.write(
+ "ERROR\nInvalid AntennaS672 L_s parameter: " + self.l_s,
+ )
sys.exit(1)
self.b = 6.32
- self.psi_0 = param.antenna_3_dB/2
- self.psi_1 = self.psi_0 * np.power(10, (self.peak_gain + self.l_s + 20)/25)
-
+ self.psi_0 = param.antenna_3_dB / 2
+ self.psi_1 = self.psi_0 * \
+ np.power(10, (self.peak_gain + self.l_s + 20) / 25)
def calculate_gain(self, *args, **kwargs) -> np.array:
psi = np.absolute(kwargs["off_axis_angle_vec"])
@@ -47,13 +50,15 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
gain[idx_0] = self.peak_gain
idx_1 = np.where((self.psi_0 <= psi) & (psi <= self.a * self.psi_0))[0]
- gain[idx_1] = self.peak_gain - 3 * np.power(psi[idx_1]/self.psi_0, 2)
+ gain[idx_1] = self.peak_gain - 3 * np.power(psi[idx_1] / self.psi_0, 2)
- idx_2 = np.where((self.a * self.psi_0 < psi) & (psi <= self.b * self.psi_0))[0]
+ idx_2 = np.where((self.a * self.psi_0 < psi) &
+ (psi <= self.b * self.psi_0))[0]
gain[idx_2] = self.peak_gain + self.l_s
idx_3 = np.where((self.b * self.psi_0 < psi) & (psi <= self.psi_1))[0]
- gain[idx_3] = self.peak_gain + self.l_s + 20 - 25 * np.log10(psi[idx_3]/self.psi_0)
+ gain[idx_3] = self.peak_gain + self.l_s + \
+ 20 - 25 * np.log10(psi[idx_3] / self.psi_0)
return gain
@@ -66,36 +71,51 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
param.antenna_gain = 50
param.antenna_pattern = "ITU-R S.672-4"
param.antenna_3_dB = 2
- psi = np.linspace(1, 30, num = 1000)
+ psi = np.linspace(1, 30, num=1000)
param.antenna_l_s = -20
antenna = AntennaS672(param)
- gain20 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain20 = antenna.calculate_gain(off_axis_angle_vec=psi)
param.antenna_l_s = -25
antenna = AntennaS672(param)
- gain25 = antenna.calculate_gain(off_axis_angle_vec = psi)
+ gain25 = antenna.calculate_gain(off_axis_angle_vec=psi)
param.antenna_l_s = -30
antenna = AntennaS672(param)
- gain30 = antenna.calculate_gain(off_axis_angle_vec = psi)
-
- fig = plt.figure(figsize=(12,7), facecolor='w', edgecolor='k') # create a figure object
-
- plt.semilogx(psi, gain20 - param.antenna_gain, "-b", label="$L_S = -20$ dB")
- plt.semilogx(psi, gain25 - param.antenna_gain, "-r", label="$L_S = -25$ dB")
- plt.semilogx(psi, gain30 - param.antenna_gain, "-g", label="$L_S = -30$ dB")
+ gain30 = antenna.calculate_gain(off_axis_angle_vec=psi)
+
+ fig = plt.figure(
+ figsize=(12, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
+
+ plt.semilogx(
+ psi, gain20 - param.antenna_gain,
+ "-b", label="$L_S = -20$ dB",
+ )
+ plt.semilogx(
+ psi, gain25 - param.antenna_gain,
+ "-r", label="$L_S = -25$ dB",
+ )
+ plt.semilogx(
+ psi, gain30 - param.antenna_gain,
+ "-g", label="$L_S = -30$ dB",
+ )
plt.ylim((-33.8, 0))
plt.xlim((1, 100))
plt.title("ITU-R S.672-4 antenna radiation pattern")
- plt.xlabel("Relative off-axis angle, $\psi/\psi_0$")
+ plt.xlabel(r"Relative off-axis angle, $\psi/\psi_0$")
plt.ylabel("Gain relative to $G_m$ [dB]")
plt.legend(loc="upper right")
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())
+ ax.set_xticks(
+ np.linspace(1, 9, 9).tolist() +
+ np.linspace(10, 100, 10).tolist(),
+ )
plt.grid()
plt.show()
diff --git a/sharc/antenna/antenna_sa509.py b/sharc/antenna/antenna_sa509.py
index 0f000bfe7..b26ca374b 100644
--- a/sharc/antenna/antenna_sa509.py
+++ b/sharc/antenna/antenna_sa509.py
@@ -4,11 +4,12 @@
@author: Calil
"""
+import numpy as np
from sharc.antenna.antenna import Antenna
from sharc.parameters.parameters_ras import ParametersRas
+from sharc.parameters.constants import SPEED_OF_LIGHT
-import numpy as np
class AntennaSA509(Antenna):
"""
@@ -21,19 +22,21 @@ def __init__(self, param: ParametersRas):
# Set basic attributes
self.diameter = param.diameter
self.efficiency = param.antenna_efficiency
- self.wavelength = param.SPEED_OF_LIGHT/(param.frequency*1e6)
+ self.wavelength = SPEED_OF_LIGHT / (param.frequency * 1e6)
# Effective area
- self.effective_area = self.efficiency*(np.pi*self.diameter**2)/4
+ self.effective_area = self.efficiency * (np.pi * self.diameter**2) / 4
# Diagram parameters
- self.g_0 = 10*np.log10(self.efficiency*\
- (np.pi*self.diameter/self.wavelength)**2)
- self.phi_0 = 20*np.sqrt(3)/(self.diameter/self.wavelength)
+ self.g_0 = 10 * np.log10(
+ self.efficiency *
+ (np.pi * self.diameter / self.wavelength)**2,
+ )
+ self.phi_0 = 20 * np.sqrt(3) / (self.diameter / self.wavelength)
# Limit parameters
- self.phi_1 = self.phi_0*np.sqrt(20/3)
- self.phi_2 = 10**((49-self.g_0)/25)
+ self.phi_1 = self.phi_0 * np.sqrt(20 / 3)
+ self.phi_2 = 10**((49 - self.g_0) / 25)
def calculate_gain(self, *args, **kwargs) -> np.array:
phi = np.absolute(kwargs["off_axis_angle_vec"])
@@ -42,13 +45,17 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
# First part
interval_idx = np.where(np.logical_and(phi >= 0, phi < self.phi_1))
- gain[interval_idx] = self.g_0 - 3*(phi[interval_idx]/self.phi_0)**2
+ gain[interval_idx] = self.g_0 - 3 * (phi[interval_idx] / self.phi_0)**2
# Second part
- interval_idx = np.where(np.logical_and(phi >= self.phi_1, phi < self.phi_2))
+ interval_idx = np.where(
+ np.logical_and(
+ phi >= self.phi_1, phi < self.phi_2,
+ ),
+ )
gain[interval_idx] = self.g_0 - 20
# Third part
interval_idx = np.where(np.logical_and(phi >= self.phi_2, phi < 48))
- gain[interval_idx] = 29 - 25*np.log10(phi[interval_idx])
+ gain[interval_idx] = 29 - 25 * np.log10(phi[interval_idx])
# Fourth part
interval_idx = np.where(np.logical_and(phi >= 48, phi < 80))
gain[interval_idx] = -13
@@ -61,14 +68,14 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
return gain
+
if __name__ == '__main__':
import matplotlib.pyplot as plt
- par = ParametersRas();
+ par = ParametersRas()
par.diameter = 1
par.antenna_efficiency = 1
par.frequency = 43000
- par.SPEED_OF_LIGHT = 3e8
antenna1 = AntennaSA509(par)
par.diameter = 7
@@ -76,23 +83,25 @@ def calculate_gain(self, *args, **kwargs) -> np.array:
par.diameter = 10
antenna10 = AntennaSA509(par)
- phi = np.linspace(0.1, 180, num = 100000)
- gain1 = antenna1.calculate_gain(off_axis_angle_vec = phi)
- gain7 = antenna7.calculate_gain(off_axis_angle_vec = phi)
- gain10 = antenna10.calculate_gain(off_axis_angle_vec = phi)
+ phi = np.linspace(0.1, 180, num=100000)
+ gain1 = antenna1.calculate_gain(off_axis_angle_vec=phi)
+ gain7 = antenna7.calculate_gain(off_axis_angle_vec=phi)
+ gain10 = antenna10.calculate_gain(off_axis_angle_vec=phi)
- fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 7), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
- plt.semilogx(phi, gain1, "-b", label = "$f = 43$ $GHz,$ $D = 1$ $m$")
- plt.semilogx(phi, gain7, "-r", label = "$f = 43$ $GHz,$ $D = 7$ $m$")
- plt.semilogx(phi, gain10, "-k", label = "$f = 43$ $GHz,$ $D = 10$ $m$")
+ plt.semilogx(phi, gain1, "-b", label="$f = 43$ $GHz,$ $D = 1$ $m$")
+ plt.semilogx(phi, gain7, "-r", label="$f = 43$ $GHz,$ $D = 7$ $m$")
+ plt.semilogx(phi, gain10, "-k", label="$f = 43$ $GHz,$ $D = 10$ $m$")
plt.title("ITU-R SA.509-3 antenna radiation pattern")
- plt.xlabel("Off-axis angle $\phi$ [deg]")
+ plt.xlabel(r"Off-axis angle $\phi$ [deg]")
plt.ylabel("Gain [dBi]")
plt.legend(loc="lower left")
plt.xlim((phi[0], phi[-1]))
plt.grid()
plt.show()
-
diff --git a/sharc/antenna/antenna_subarray_imt.py b/sharc/antenna/antenna_subarray_imt.py
new file mode 100644
index 000000000..6d0db33b6
--- /dev/null
+++ b/sharc/antenna/antenna_subarray_imt.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+import numpy as np
+import typing
+
+from sharc.antenna.antenna_element_imt_m2101 import AntennaElementImtM2101
+
+
+# Maybe we should consider using a single array to also make the subarrays
+# Equations are basically the same
+# To make it possible, we should probably update the Beamforming so that, when using subarray:
+# - impl doesn't transform coordinates to local twice
+# - impl allows fixed eletrical downtilt (no beamforming in this case?)
+# - parameters are more clearly defined and passed from Parameters class
+class AntennaSubarrayIMT(object):
+ """
+ Implements a Subarray "Element" for IMT antenna.
+ Subarray implementation defined in R23-WP5D-C-0413, Annex 4.2, Table 8.
+ Most equations are already implemented in SHARC beamforming antenna, and M2101 element
+
+ Attributes
+ ----------
+ element: Antenna element to form subarray
+ eletrical: Antenna element to form subarray
+ n_rows: How many rows subarray has
+ """
+
+ def __init__(
+ self,
+ *,
+ element: AntennaElementImtM2101,
+ eletrical_downtilt: float,
+ n_rows: int,
+ element_vert_spacing: float
+ ):
+ """
+ Constructs an AntennaElementImt object.
+
+ Parameters
+ ---------
+ """
+ self.element = element
+ self.eletrical_downtilt = eletrical_downtilt
+ self.n_rows = n_rows
+ # self.n_columns = 1
+ self.dv_sub = element_vert_spacing
+
+ def _super_position_vector(
+ self,
+ theta: float,
+ n_rows: int,
+ dv_sub: float
+ ) -> np.array:
+ """
+ Calculates super position vector.
+ Angles are in the local coordinate system.
+
+ Parameters
+ ----------
+ theta (float): elevation angle [degrees]
+ phi (float): azimuth angle [degrees]
+
+ Returns
+ -------
+ v_vec (np.array): superposition vector
+ """
+ r_theta = np.deg2rad(theta)
+
+ m = np.arange(n_rows) + 1
+
+ v_n = np.exp(
+ 1.0j * 2 * np.pi * (m[: np.newaxis] - 1) * dv_sub * np.cos(r_theta)
+ )
+
+ return v_n
+
+ def _weight_vector(self, eletrical_downtilt: float, n_rows: int, dv_sub: float) -> np.array:
+ """
+ Calculates weight/sub-array excitation.
+ Angles are in the local coordinate system.
+
+ Parameters
+ ----------
+ phi_tilt (float): eletrical horizontal steering [degrees]
+ theta_tilt (float): eletrical down-tilt steering [degrees]
+
+ Side Effect
+ -------
+ w_vec (np.array): weighting vector
+ Returns
+ -------
+ None
+ """
+ m = np.arange(self.n_rows) + 1
+
+ return 1 / np.sqrt(n_rows) * np.exp(
+ 1.0j * 2 * np.pi * (m - 1) * dv_sub * np.sin(np.deg2rad(eletrical_downtilt))
+ )
+
+ def _calculate_single_dir_gain(self, phi: float, theta: float):
+ """
+ There is no beamforming, this is just for better code
+ """
+ elem_g = self.element.element_pattern(
+ phi, theta
+ )
+
+ v_vec = self._super_position_vector(theta, self.n_rows, self.dv_sub)
+
+ w_vec = self._weight_vector(self.eletrical_downtilt, self.n_rows, self.dv_sub)
+
+ array_g = 10 * np.log10(abs(np.sum(np.multiply(v_vec, w_vec)))**2)
+
+ return array_g + elem_g
+
+ def calculate_gain(
+ self,
+ phi_arr: typing.Union[np.array, float],
+ theta_arr: typing.Union[np.array, float]
+ ) -> typing.Union[np.array, float]:
+ """
+ Calculates the subarray radiation pattern gain.
+ Assumes the angles are already received as local coordinates
+
+ Parameters
+ ----------
+ theta (np.array): elevation angle [degrees]
+ phi (np.array): azimuth angle [degrees]
+
+ Returns
+ -------
+ gain (np.array): element radiation pattern gain value [dBi]
+ """
+ if isinstance(phi_arr, float) and isinstance(theta_arr, float):
+ return self._calculate_single_dir_gain(phi_arr, theta_arr)
+
+ n_direct = len(phi_arr)
+
+ gains = np.zeros(n_direct)
+
+ for i in range(n_direct):
+ gains[i] = self._calculate_single_dir_gain(phi_arr[i], theta_arr[i])
+
+ return gains
+
+
+if __name__ == '__main__':
+ from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+ from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt, PlotAntennaPattern
+
+ figs_dir = "figs/"
+
+ bs_param = ParametersAntennaImt()
+ bs2_param = ParametersAntennaImt()
+ bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ bs2_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+
+ bs_param.normalization = False
+ bs2_param.normalization = False
+ bs_param.normalization_file = 'beamforming_normalization\\bs_indoor_norm.npz'
+ bs2_param.normalization_file = 'beamforming_normalization\\bs2_norm.npz'
+ bs_param.minimum_array_gain = -200
+ bs2_param.minimum_array_gain = -200
+
+ bs_param.element_pattern = "M2101"
+ bs_param.element_max_g = 6.4
+ bs_param.element_phi_3db = 90
+ bs_param.element_theta_3db = 65
+ bs_param.element_am = 30
+ bs_param.element_sla_v = 30
+ bs_param.n_rows = 16
+ bs_param.n_columns = 8
+ bs_param.subarray.is_enabled = True
+ bs_param.subarray.element_vert_spacing = 0.7
+ bs_param.subarray.eletrical_downtilt = 3
+ bs_param.subarray.n_rows = 3
+ # bs_param.n_rows = 8
+ # bs_param.n_columns = 16
+ bs_param.element_horiz_spacing = 0.5
+ bs_param.element_vert_spacing = 2.1
+ bs_param.multiplication_factor = 12
+ bs_param.downtilt = 0
+
+ bs2_param.element_pattern = "M2101"
+ bs2_param.element_max_g = 6.4
+ bs2_param.element_phi_3db = 90
+ bs2_param.element_theta_3db = 65
+ bs2_param.element_am = 30
+ bs2_param.element_sla_v = 30
+ bs2_param.n_rows = 3
+ bs2_param.n_columns = 1
+ bs2_param.element_horiz_spacing = 0.5
+ bs2_param.element_vert_spacing = 0.7
+ bs2_param.multiplication_factor = 12
+
+ plot = PlotAntennaPattern(figs_dir)
+
+ # Plot BS TX radiation patterns
+ par = bs_param.get_antenna_parameters()
+ bs_array = AntennaBeamformingImt(par, 0, 0, bs_param.subarray)
+ f = plot.plot_element_pattern(bs_array, "BS", "ELEMENT")
+ # f.savefig(figs_dir + "BS_element.pdf", bbox_inches='tight')
+ f = plot.plot_element_pattern(bs_array, "BS", "SUBARRAY")
+ f = plot.plot_element_pattern(bs_array, "BS", "ARRAY")
+ # f.savefig(figs_dir + "BS_array.pdf", bbox_inches='tight')
+
+ # Plot UE TX radiation patterns
+ par = bs2_param.get_antenna_parameters()
+ bs2_array = AntennaBeamformingImt(par, 0, 0, bs2_param.subarray)
+ # plot.plot_element_pattern(bs2_array, "BS 2", "ELEMENT")
+ # plot.plot_element_pattern(bs2_array, "BS 2", "ARRAY")
+
+ print('END')
diff --git a/sharc/antenna/beamforming_normalization/__init__.py b/sharc/antenna/beamforming_normalization/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/antenna/beamforming_normalization/__init__.py
+++ b/sharc/antenna/beamforming_normalization/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/antenna/beamforming_normalization/beamforming_normalizer.py b/sharc/antenna/beamforming_normalization/beamforming_normalizer.py
index d9db97119..f95e01ce9 100644
--- a/sharc/antenna/beamforming_normalization/beamforming_normalizer.py
+++ b/sharc/antenna/beamforming_normalization/beamforming_normalizer.py
@@ -39,6 +39,7 @@ class BeamformingNormalizer(object):
antenna (AntennaBeamformingImt): antenna to which calculate
normalization
"""
+
def __init__(self, res_deg: float, tol: float):
"""
Class constructor
@@ -61,16 +62,22 @@ def __init__(self, res_deg: float, tol: float):
self.theta_min_rad = np.deg2rad(self.theta_min_deg)
self.theta_max_rad = np.deg2rad(self.theta_max_deg)
- self.phi_vals_deg = np.arange(self.phi_min_deg,
- self.phi_max_deg,res_deg)
- self.theta_vals_deg = np.arange(self.theta_min_deg,
- self.theta_max_deg,res_deg)
+ self.phi_vals_deg = np.arange(
+ self.phi_min_deg,
+ self.phi_max_deg, res_deg,
+ )
+ self.theta_vals_deg = np.arange(
+ self.theta_min_deg,
+ self.theta_max_deg, res_deg,
+ )
self.antenna = None
- def generate_correction_matrix(self,
- par: AntennaPar,
- file_name: str,
- testing = False):
+ def generate_correction_matrix(
+ self,
+ par: AntennaPar,
+ file_name: str,
+ testing=False,
+ ):
"""
Generates the correction factor matrix and saves it in a file
@@ -80,34 +87,44 @@ def generate_correction_matrix(self,
file_name (str): name of file to which save the correction matrix
"""
# Create antenna object
- azi = 0 # Antenna azimuth: 0 degrees for simplicity
- ele = 0 # Antenna elevation: 0 degrees as well
- self.antenna = AntennaBeamformingImt(par,azi,ele)
+ azi = 0 # Antenna azimuth: 0 degrees for simplicity
+ ele = 0 # Antenna elevation: 0 degrees as well
+ self.antenna = AntennaBeamformingImt(par, azi, ele)
# For co-channel beamforming
# Correction factor numpy array
- correction_factor_co = np.zeros((len(self.phi_vals_deg),len(self.theta_vals_deg)))
- error_co = np.empty((len(self.phi_vals_deg),len(self.theta_vals_deg)), dtype=tuple)
+ correction_factor_co = np.zeros(
+ (len(self.phi_vals_deg), len(self.theta_vals_deg)),
+ )
+ error_co = np.empty(
+ (len(self.phi_vals_deg), len(self.theta_vals_deg)), dtype=tuple,
+ )
# Loop throug all the possible beams
for phi_idx, phi in enumerate(self.phi_vals_deg):
- if not testing: print('\n' + str(100*phi_idx/len(self.phi_vals_deg)) + '%')
+ if not testing:
+ print('\n' + str(100 * phi_idx / len(self.phi_vals_deg)) + '%')
for theta_idx, theta in enumerate(self.theta_vals_deg):
s = '\tphi = ' + str(phi) + ', theta = ' + str(theta)
- if not testing: print(s)
+ if not testing:
+ print(s)
stdout.flush()
- correction_factor_co[phi_idx,theta_idx], error_co[phi_idx,theta_idx] = self.calculate_correction_factor(phi,theta,True)
-
+ correction_factor_co[phi_idx, theta_idx], error_co[phi_idx, theta_idx] = \
+ self.calculate_correction_factor(phi, theta, True)
- correction_factor_adj, error_adj = self.calculate_correction_factor(0,0,False)
+ correction_factor_adj, error_adj = self.calculate_correction_factor(
+ 0, 0, False,
+ )
# Save in file
- self._save_files(correction_factor_co,
- error_co,
- correction_factor_adj,
- error_adj,
- par,
- file_name)
+ self._save_files(
+ correction_factor_co,
+ error_co,
+ correction_factor_adj,
+ error_adj,
+ par,
+ file_name,
+ )
def calculate_correction_factor(self, phi_beam: float, theta_beam: float, c_chan: bool):
"""
@@ -125,26 +142,29 @@ def calculate_correction_factor(self, phi_beam: float, theta_beam: float, c_chan
error (tuple): upper and lower error bounds [dB]
"""
if c_chan:
- self.antenna.add_beam(phi_beam,theta_beam)
+ self.antenna.add_beam(phi_beam, theta_beam)
beam = int(len(self.antenna.beams_list) - 1)
- int_f = lambda t,p: \
- np.power(10,self.antenna._beam_gain(np.rad2deg(p),np.rad2deg(t),beam)/10)*np.sin(t)
+
+ def int_f(t, p):
+ return np.power(10, self.antenna._beam_gain(np.rad2deg(p), np.rad2deg(t), beam) / 10) * np.sin(t)
else:
- int_f = lambda t,p: \
- np.power(10,self.antenna.element.element_pattern(np.rad2deg(p),np.rad2deg(t))/10)*np.sin(t)
+ def int_f(t, p):
+ return np.power(10, self.antenna.element.element_pattern(np.rad2deg(p), np.rad2deg(t)) / 10) * np.sin(t)
- integral_val, err = dblquad(int_f,self.phi_min_rad,self.phi_max_rad,
- lambda p: self.theta_min_rad,
- lambda p: self.theta_max_rad,
- epsabs=self.tolerance,
- epsrel=0.0)
+ integral_val, err = dblquad(
+ int_f, self.phi_min_rad, self.phi_max_rad,
+ lambda p: self.theta_min_rad,
+ lambda p: self.theta_max_rad,
+ epsabs=self.tolerance,
+ epsrel=0.0,
+ )
- correction_factor = -10*np.log10(integral_val/(4*np.pi))
+ correction_factor = -10 * np.log10(integral_val / (4 * np.pi))
- hig_bound = -10*np.log10((integral_val - err)/(4*np.pi))
- low_bound = -10*np.log10((integral_val + err)/(4*np.pi))
+ hig_bound = -10 * np.log10((integral_val - err) / (4 * np.pi))
+ low_bound = -10 * np.log10((integral_val + err) / (4 * np.pi))
- return correction_factor, (low_bound,hig_bound)
+ return correction_factor, (low_bound, hig_bound)
def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name):
"""
@@ -182,15 +202,18 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name):
par (AtennaPar): antenna parameters used in normalization
file_name (str): name of file to which save normalization data
"""
- np.savez(file_name,
- resolution = self.resolution_deg,
- phi_range = (self.phi_min_deg, self.phi_max_deg),
- theta_range = (self.theta_min_deg, self.theta_max_deg),
- correction_factor_co_channel = cf_co,
- error_co_channel = err_co,
- correction_factor_adj_channel = cf_adj,
- error_adj_channel = err_adj,
- parameters = par)
+ np.savez(
+ file_name,
+ resolution=self.resolution_deg,
+ phi_range=(self.phi_min_deg, self.phi_max_deg),
+ theta_range=(self.theta_min_deg, self.theta_max_deg),
+ correction_factor_co_channel=cf_co,
+ error_co_channel=err_co,
+ correction_factor_adj_channel=cf_adj,
+ error_adj_channel=err_adj,
+ parameters=par,
+ )
+
if __name__ == '__main__':
"""
@@ -202,7 +225,7 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name):
# Create normalizer object
resolution = 5
tolerance = 1e-1
- norm = BeamformingNormalizer(resolution,tolerance)
+ norm = BeamformingNormalizer(resolution, tolerance)
# Antenna parameters
adjacent_antenna_model = "SINGLE_ELEMENT"
@@ -220,32 +243,36 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name):
vert_spacing = 0.5
minimum_array_gain = -200
down_tilt = 0
- par = AntennaPar(normalization,
- norm_file,
- element_pattern,
- element_max_g,
- element_phi_deg_3db,
- element_theta_deg_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- horiz_spacing,
- vert_spacing,
- down_tilt)
+ par = AntennaPar(
+ normalization,
+ norm_file,
+ element_pattern,
+ element_max_g,
+ element_phi_deg_3db,
+ element_theta_deg_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ horiz_spacing,
+ vert_spacing,
+ down_tilt,
+ )
# Set range of values & calculate correction factor
norm.theta_vals_deg = np.array([90])
file_name = 'main_test.npz'
- norm.generate_correction_matrix(par,file_name)
+ norm.generate_correction_matrix(par, file_name)
data = np.load(file_name)
correction_factor = data['correction_factor_co_channel']
err_low, err_high = zip(*np.ravel(data['error_co_channel']))
- plt.plot(norm.phi_vals_deg,correction_factor,
- norm.phi_vals_deg,err_low,'r--',
- norm.phi_vals_deg,err_high,'r--')
- plt.xlim(-180,180)
+ plt.plot(
+ norm.phi_vals_deg, correction_factor,
+ norm.phi_vals_deg, err_low, 'r--',
+ norm.phi_vals_deg, err_high, 'r--',
+ )
+ plt.xlim(-180, 180)
plt.ylabel(r"Correction factor [dB]")
plt.xlabel(r"Azimuth angle $\phi$ [deg]")
plt.title(r"Elevation angle $\theta$ = 90 deg")
@@ -253,21 +280,22 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name):
data.close()
# Set range of values & calculate correction factor
- norm = BeamformingNormalizer(resolution,tolerance)
+ norm = BeamformingNormalizer(resolution, tolerance)
norm.phi_vals_deg = np.array([0])
- norm.generate_correction_matrix(par,file_name)
+ norm.generate_correction_matrix(par, file_name)
data = np.load(file_name)
correction_factor = data['correction_factor_co_channel']
err_low, err_high = zip(*np.ravel(data['error_co_channel']))
- plt.plot(norm.theta_vals_deg,np.transpose(correction_factor),
- norm.theta_vals_deg,np.transpose(err_low),'r--',
- norm.theta_vals_deg,np.transpose(err_high),'r--')
- plt.xlim(0,180)
+ plt.plot(
+ norm.theta_vals_deg, np.transpose(correction_factor),
+ norm.theta_vals_deg, np.transpose(err_low), 'r--',
+ norm.theta_vals_deg, np.transpose(err_high), 'r--',
+ )
+ plt.xlim(0, 180)
plt.ylabel(r"Correction factor [dB]")
plt.xlabel(r"Elevation angle $\theta$ [deg]")
plt.title(r"Azimuth angle $\phi$ = 0 deg")
plt.show()
data.close()
os.remove(file_name)
-
diff --git a/sharc/antenna/beamforming_normalization/normalize_script.py b/sharc/antenna/beamforming_normalization/normalize_script.py
index 3bfa0cb53..71084570c 100644
--- a/sharc/antenna/beamforming_normalization/normalize_script.py
+++ b/sharc/antenna/beamforming_normalization/normalize_script.py
@@ -67,18 +67,18 @@
parameters (AntennaPar): antenna parameters used in the normalization
"""
+from sharc.antenna.beamforming_normalization.beamforming_normalizer import BeamformingNormalizer
+from sharc.support.named_tuples import AntennaPar
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
-from sharc.support.named_tuples import AntennaPar
-from sharc.antenna.beamforming_normalization.beamforming_normalizer import BeamformingNormalizer
if __name__ == "__main__":
###########################################################################
- ## List of antenna parameters to which calculate the normalization factors.
- adjacent_antenna_model = "" # not needed here
+ # List of antenna parameters to which calculate the normalization factors.
+ adjacent_antenna_model = "" # not needed here
normalization = False # not needed here
normalization_data = None # not needed here
element_pattern = "M2101"
@@ -94,36 +94,40 @@
multiplication_factor = 12
minimum_array_gain = -200
downtilt = 0
-
+
file_names = ["bs_norm_8x8_050.npz"]
- param_list = [AntennaPar(adjacent_antenna_model,
- normalization,
- normalization_data,
- element_pattern,
- element_max_g,
- element_phi_3db,
- element_theta_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- element_horiz_spacing,
- element_vert_spacing,
- multiplication_factor,
- minimum_array_gain,
- downtilt)]
+ param_list = [
+ AntennaPar(
+ adjacent_antenna_model,
+ normalization,
+ normalization_data,
+ element_pattern,
+ element_max_g,
+ element_phi_3db,
+ element_theta_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ element_horiz_spacing,
+ element_vert_spacing,
+ multiplication_factor,
+ minimum_array_gain,
+ downtilt,
+ ),
+ ]
###########################################################################
- ## Setup
+ # Setup
# General parameters
resolution = 5
tolerance = 1e-2
-
+
# Create object
norm = BeamformingNormalizer(resolution, tolerance)
###########################################################################
- ## Normalize and save
+ # Normalize and save
for par, file in zip(param_list, file_names):
s = 'Generating ' + file
print(s)
-
- norm.generate_correction_matrix(par,file)
+
+ norm.generate_correction_matrix(par, file)
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md b/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md
new file mode 100644
index 000000000..b84dfe69e
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md
@@ -0,0 +1,39 @@
+
+# Spectrum Sharing Study: IMT HIBS vs. Radio Astronomy (2.6 GHz)
+This directory holds the code and data for a simulation study investigating spectrum sharing between:
+
+IMT HIBS as a (NTN) system and Radio Astronomy Service (RAS) operating in the 2.6 GHz band.
+
+Each campaing puts the RAS station farther from the IMT BS nadir point over the Earth's surface.
+
+Main campaign parameters:
+- IMT topolgy: NTN with IMT BS at 20km of altitude
+- IMT @2680MHz/20MHz BW
+- RAS @2695/10MHz BW
+- Channel model: P.619 for both IMT and IMT-RAS links.
+
+# Folder Structure
+inputs: This folder contains parameter files used to configure the simulation campaigns. Each file defines specific scenarios for the NTN and RAS systems.
+scripts: This folder holds post-processing and plotting scripts used to analyze the simulation data. These scripts generate performance metrics and visualizations based on the simulation outputs.
+
+# Dependencies
+This project may require additional software or libraries to run the simulations and post-processing scripts. Please refer to the individual script files for specific dependencies.
+
+# Running the Simulations
+`python3 main_cli.py -p campaigns/imt_hibs_ras_2600_MHz/input/`
+or on root
+`python3 sharc/main_cli.py -p sharc/campaigns/imt_hibs_ras_2600_MHz/input/`
+
+# Running the scripts
+
+## For plotting
+
+`python3 sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py`
+
+## For starting simulation multi thread
+
+`sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py`
+
+## For starting simulation single thread
+
+`sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py`
\ No newline at end of file
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml
new file mode 100644
index 000000000..d54ba5d98
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml
@@ -0,0 +1,374 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots : 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link : DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel : FALSE
+ enable_adjacent_channel : TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed : 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output : FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir : campaigns/imt_hibs_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix : output_imt_hibs_ras_2600_MHz_0km
+imt:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: NTN
+ ntn:
+ ###########################################################################
+ # Number of clusters in NTN topology
+ num_clusters : 1
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius : 90000
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue : 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with : FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency : 2680
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth : 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth : 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask : 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions : -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio : 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability : 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power : 37
+ ###########################################################################
+ # Base station height [m]
+ height : 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure : 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature : 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss : 2
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt : 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max : 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k : 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m : 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent : 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type : ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance : RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth : NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control : ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch : -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha : 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax : 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range : 63
+ ###########################################################################
+ # UE height [m]
+ height : 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure : 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss : 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss : 4
+ antenna:
+ # If normalization of M2101 should be applied for UE
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max : 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model : P619
+ param_p619:
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ space_station_alt_m : 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m : 1000
+ # The RAS station lat
+ earth_station_lat_deg : -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg : 0.0
+ season : SUMMER
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model : SINGLE_ELEMENT
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # frequency [MHz]
+ frequency: 2695
+ ###########################################################################
+ # bandwidth [MHz]
+ bandwidth: 10
+ ###########################################################################
+ # Station noise temperature [K]
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ antenna:
+ pattern: OMNI
+ gain: 0
+ channel_model: P619
+
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 15
+ # The RAS station lat
+ earth_station_lat_deg: -23.17889
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml
new file mode 100644
index 000000000..7345c4302
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml
@@ -0,0 +1,373 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots : 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link : DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel : FALSE
+ enable_adjacent_channel : TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed : 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output : FALSE
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir : campaigns/imt_hibs_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix : output_imt_hibs_ras_2600_MHz_45km
+imt:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: NTN
+ ntn:
+ ###########################################################################
+ # Number of clusters in NTN topology
+ num_clusters : 1
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius : 90000
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue : 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with : FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency : 2680
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth : 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth : 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask : 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions : -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio : 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability : 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power : 37
+ ###########################################################################
+ # Base station height [m]
+ height : 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure : 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature : 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss : 2
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt : 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max : 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k : 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m : 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent : 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type : ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance : RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth : NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control : ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch : -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha : 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax : 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range : 63
+ ###########################################################################
+ # UE height [m]
+ height : 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure : 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss : 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss : 4
+ antenna:
+ # If normalization of M2101 should be applied for UE
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max : 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model : P619
+ param_p619:
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ space_station_alt_m : 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m : 1000
+ # The RAS station lat
+ earth_station_lat_deg : -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg : 0.0
+ season : SUMMER
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model : SINGLE_ELEMENT
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # frequency [MHz]
+ frequency: 2695
+ ###########################################################################
+ # bandwidth [MHz]
+ bandwidth: 10
+ ###########################################################################
+ # Station noise temperature [K]
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 45000
+ ###########################################################################
+ y: 0
+ antenna:
+ pattern: OMNI
+ gain: 0
+ channel_model: P619
+
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 15
+ # The RAS station lat
+ earth_station_lat_deg: -23.17889
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml
new file mode 100644
index 000000000..b1821243f
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml
@@ -0,0 +1,373 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots : 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link : DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel : FALSE
+ enable_adjacent_channel : TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed : 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output : FALSE
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir : campaigns/imt_hibs_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix : output_imt_hibs_ras_2600_MHz_500km
+imt:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: NTN
+ ntn:
+ ###########################################################################
+ # Number of clusters in NTN topology
+ num_clusters : 1
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius : 90000
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue : 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with : FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency : 2680
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth : 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth : 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask : 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions : -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio : 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability : 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power : 37
+ ###########################################################################
+ # Base station height [m]
+ height : 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure : 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature : 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss : 2
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt : 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max : 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k : 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m : 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent : 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type : ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance : RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth : NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control : ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch : -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha : 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax : 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range : 63
+ ###########################################################################
+ # UE height [m]
+ height : 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure : 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss : 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss : 4
+ antenna:
+ # If normalization of M2101 should be applied for UE
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max : 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model : P619
+ param_p619:
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ space_station_alt_m : 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m : 1000
+ # The RAS station lat
+ earth_station_lat_deg : -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg : 0.0
+ season : SUMMER
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model : SINGLE_ELEMENT
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # frequency [MHz]
+ frequency: 2695
+ ###########################################################################
+ # bandwidth [MHz]
+ bandwidth: 10
+ ###########################################################################
+ # Station noise temperature [K]
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 500000
+ ###########################################################################
+ y: 0
+ antenna:
+ pattern: OMNI
+ gain: 0
+ channel_model: P619
+
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 15
+ # The RAS station lat
+ earth_station_lat_deg: -23.17889
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml
new file mode 100644
index 000000000..ae126fade
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml
@@ -0,0 +1,373 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots : 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link : DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel : FALSE
+ enable_adjacent_channel : TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed : 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output : FALSE
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir : campaigns/imt_hibs_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix : output_imt_hibs_ras_2600_MHz_90km
+imt:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: NTN
+ ntn:
+ ###########################################################################
+ # Number of clusters in NTN topology
+ num_clusters : 1
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius : 90000
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue : 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with : FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency : 2680
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth : 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth : 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask : 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions : -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio : 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability : 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power : 37
+ ###########################################################################
+ # Base station height [m]
+ height : 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure : 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature : 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss : 2
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt : 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max : 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k : 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m : 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent : 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type : ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance : RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth : NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control : ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch : -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha : 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax : 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range : 63
+ ###########################################################################
+ # UE height [m]
+ height : 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure : 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss : 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss : 4
+ antenna:
+ # If normalization of M2101 should be applied for UE
+ normalization : FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max : 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model : P619
+ param_p619:
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ space_station_alt_m : 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m : 1000
+ # The RAS station lat
+ earth_station_lat_deg : -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg : 0.0
+ season : SUMMER
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model : SINGLE_ELEMENT
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # frequency [MHz]
+ frequency: 2695
+ ###########################################################################
+ # bandwidth [MHz]
+ bandwidth: 10
+ ###########################################################################
+ # Station noise temperature [K]
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 90000
+ ###########################################################################
+ y: 0
+ antenna:
+ pattern: OMNI
+ gain: 0
+ channel_model: P619
+
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 15
+ # The RAS station lat
+ earth_station_lat_deg: -23.17889
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md b/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md
new file mode 100644
index 000000000..aef44111d
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md
@@ -0,0 +1,3 @@
+# output folder
+This folder holds simulation output data.
+Don't push the output files to the repository.
\ No newline at end of file
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py
new file mode 100644
index 000000000..2d842fd32
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py
@@ -0,0 +1,92 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+# import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="_0km",
+ legend="0 Km"
+ ).add_plot_legend_pattern(
+ dir_name_contains="_45km",
+ legend="45 Km"
+ ).add_plot_legend_pattern(
+ dir_name_contains="_90km",
+ legend="90 Km"
+ ).add_plot_legend_pattern(
+ dir_name_contains="_500km",
+ legend="500 Km"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+# ^: typing.List[Results]
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# # This function aggregates IMT downlink and uplink
+# aggregated_results = PostProcessor.aggregate_results(
+# downlink_result=post_processor.get_results_by_output_dir("MHz_60deg_dl"),
+# uplink_result=post_processor.get_results_by_output_dir("MHz_60deg_ul"),
+# ul_tdd_factor=(3, 4),
+# n_bs_sim=7 * 19 * 3 * 3,
+# n_bs_actual=int
+# )
+
+# Add a protection criteria line:
+# protection_criteria = 160
+
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .add_vline(protection_criteria, line_dash="dash")
+
+# Show a single plot:
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .show()
+
+# Plot every plot:
+for plot in plots:
+ plot.show()
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
+ # # do whatever you want here:
+ # if "fspl_45deg" in stats.results_output_dir:
+ # get some stat and do something
+
+# # example on how to aggregate results and add it to plot:
+# dl_res = post_processor.get_results_by_output_dir("1_cluster")
+# aggregated_results = PostProcessor.aggregate_results(
+# dl_samples=dl_res.system_dl_interf_power,
+# ul_samples=ul_res.system_ul_interf_power,
+# ul_tdd_factor=0.75,
+# n_bs_sim=1 * 19 * 3 * 3,
+# n_bs_actual=7 * 19 * 3 * 3
+# )
+
+# relevant = post_processor\
+# .get_plot_by_results_attribute_name("system_ul_interf_power")
+
+# aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results)
+
+# relevant.add_trace(
+# go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',),
+# )
+
+# relevant.show()
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..3851af953
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hibs_ras_2600_MHz"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..468e76764
--- /dev/null
+++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hibs_ras_2600_MHz"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv b/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv
new file mode 100644
index 000000000..65ce1b706
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv
@@ -0,0 +1,78 @@
+# Contrib 20 INR
+x,y
+-11.358820993101236,-0.000281063728990949
+-11.138772461094177,-0.00022228782340016906
+-10.918723929087118,-0.00004596010662760719
+-10.69867539708006,-0.00022228782340016906
+-10.478626865073,0.0009532302884172061
+-10.258578333065941,0.002892835172915831
+-10.038529801058882,0.005361423207732585
+-9.818481269051821,0.009416960693502618
+-9.598432737044762,0.017586811570633465
+-9.378384205037703,0.030399958989443254
+-9.188342291031606,0.04291922688029848
+-9.048311407027114,0.05725075186020567
+-8.928284935023264,0.07169003266702956
+-8.808258463019413,0.08647951991133218
+-8.720461321461041,0.10189758281264838
+-8.646794280442805,0.11634712607917863
+-8.568205519011713,0.1316561503461149
+-8.498190077009466,0.14625706489331347
+-8.42817463500722,0.16285146223846914
+-8.358159193004974,0.1800923945451245
+-8.29814595700305,0.19604025692878047
+-8.238132721001124,0.21209587513935335
+-8.178119484999199,0.22944456327292517
+-8.118106248997274,0.24700876306033026
+-8.049519693566502,0.2664355992844364
+-7.988077570993102,0.2838612558658058
+-7.948068746991819,0.29765400171113
+-7.918062128990856,0.3116191568795207
+-7.878053304989573,0.32778253091701004
+-7.8180400689876475,0.34506656555443194
+-7.748024626985401,0.36279239908221184
+-7.718018008984439,0.3791174068600761
+-7.674675116316383,0.39366444349381646
+-7.634666292315099,0.4093967942236393
+-7.594657468313816,0.4252369007803789
+-7.5546486443125325,0.4402149607217857
+-7.510472234477781,0.4571865034611494
+-7.447958446975775,0.475936017344637
+-7.405449071474411,0.49508961557906184
+-7.354604524306115,0.5120880972751547
+-7.31459570030483,0.5267428897358117
+-7.274586876303546,0.5419364613310517
+-7.234578052302263,0.5579920795416243
+-7.186472204395958,0.5751098623318035
+-7.147892266966149,0.5917196533665188
+-7.107883442964866,0.6066977133079254
+-7.067874618963582,0.6215680174224156
+-7.027865794962299,0.6358995424023228
+-6.997859176961336,0.6492397137745973
+-6.946419260388257,0.6647011355681728
+-6.909268209529921,0.6822499416660184
+-6.857828292956844,0.6987642918254646
+-6.817819468955561,0.7152078310129371
+-6.7578062329536355,0.7330952982810919
+-6.69779299695171,0.7520603238184126
+-6.637779760949785,0.7696245236058177
+-6.57776652494786,0.7883740374893052
+-6.517753288945933,0.8053994581421273
+-6.457740052944008,0.8204852739104507
+-6.387724610941762,0.8370257933421481
+-6.307706962939195,0.8552095891343235
+-6.227689314936628,0.8722619487438747
+-6.147671666934061,0.8881020553006143
+-6.092659533932297,0.9005478533094811
+-5.997638576929248,0.9182467478805318
+-5.88761431092572,0.9344909387882085
+-5.757585632921547,0.9490841564906275
+-5.597550336916413,0.9638005237095225
+-5.397506216909996,0.9776912293974982
+-5.177457684902937,0.9869778224808556
+-4.9574091528958775,0.9917386708337161
+-4.737360620888818,0.9944423624908962
+-4.517312088881759,0.9956766565083044
+-4.2972635568747,0.9966170709977584
+-4.077215024867641,0.9972636059592579
+-3.937184140863149,0.9976358533613334
diff --git a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml
new file mode 100644
index 000000000..06747adbf
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml
@@ -0,0 +1,376 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 2400
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_SS, FSS_SS, FSS_ES, FS, RAS
+ system: EESS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: TRUE
+ enable_adjacent_channel: FALSE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 31
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_hotspot_eess_active/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_hotspot_eess_active_beam_small_dl
+imt:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: HOTSPOT
+ hotspot:
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: 100
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: 0
+ ###########################################################################
+ # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
+ wrap_around: FALSE
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ #intersite_distance = 1740
+ #intersite_distance = 665
+ intersite_distance: 660
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 10250
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: IMT-2020
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: BEAMFORMING
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: .2
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 16
+ ###########################################################################
+ # Base station height [m]
+ height: 6
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 10
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 0
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5.5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 120
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 120
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 1.0
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 10
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 0
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -4
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 360
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 180
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 1.0
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ channel_model: UMi
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 29
+ ###########################################################################
+ # If shadowing should be applied or not
+ shadowing: TRUE
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 500
+eess_ss:
+ ###########################################################################
+ # sensor center frequency [MHz]
+ frequency: 9800
+ ###########################################################################
+ # sensor bandwidth [MHz]
+ bandwidth: 1200
+ ###########Creates a statistical distribution of nadir angle###############
+ ##############following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: OFF
+ distribution_type: UNIFORM
+ nadir_angle_distribution: 18.6,49.4
+ ###########################################################################
+ # Off-nadir pointing angle [deg]
+ nadir_angle: 18
+ ###########################################################################
+ # sensor altitude [m]
+ altitude: 514000.0
+ ###########################################################################
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813"
+ # "ITU-R RS.1861 9a"
+ # "ITU-R RS.1861 9b"
+ # "ITU-R RS.1861 9c"
+ # "ITU-R RS.2043"
+ # "OMNI"
+ antenna_pattern: ITU-R RS.2043
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ antenna_efficiency: 1.0
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: 2.2
+ ###########################################################################
+ # receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: 47
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "P619"
+ channel_model: P619
+ # Parameters for the P.619 propagation model
+ # space_station_alt_m - altitude of space station
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ earth_station_alt_m: 1172
+ earth_station_lat_deg: -15.8
+ earth_station_long_diff_deg: 0.0
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml
new file mode 100644
index 000000000..ccb1d74f2
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml
@@ -0,0 +1,371 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 2400
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: UPLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_SS, FSS_SS, FSS_ES, FS, RAS
+ system: EESS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: TRUE
+ enable_adjacent_channel: FALSE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_hotspot_eess_active/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_hotspot_eess_active_beam_small_ul
+imt:
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: HOTSPOT
+ hotspot:
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: 100
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: 0
+ ###########################################################################
+ # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
+ wrap_around: FALSE
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 660
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 10250
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: IMT-2020
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: .2
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 16
+ ###########################################################################
+ # Base station height [m]
+ height: 6
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 10
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 3
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5.5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 1.0
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 10
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 0
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -4
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 360
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 180
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 1.0
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ channel_model: UMi
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 29
+ ###########################################################################
+ # If shadowing should be applied or not
+ shadowing: TRUE
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 500
+eess_ss:
+ ###########################################################################
+ # sensor center frequency [MHz]
+ frequency: 9800
+ ###########################################################################
+ # sensor bandwidth [MHz]
+ bandwidth: 1200
+ ###########Creates a statistical distribution of nadir angle###############
+ ##############following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: OFF
+ distribution_type: UNIFORM
+ nadir_angle_distribution: 18.6,49.4
+ ###########################################################################
+ # Off-nadir pointing angle [deg]
+ nadir_angle: 18
+ ###########################################################################
+ # sensor altitude [m]
+ altitude: 514000.0
+ ###########################################################################
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813"
+ # "ITU-R RS.1861 9a"
+ # "ITU-R RS.1861 9b"
+ # "ITU-R RS.1861 9c"
+ # "ITU-R RS.2043"
+ # "OMNI"
+ antenna_pattern: ITU-R RS.2043
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ antenna_efficiency: 0.7
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: 2.2
+ ###########################################################################
+ # receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: 47
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "P619"
+ channel_model: P619
+ # Parameters for the P.619 propagation model
+ # space_station_alt_m - altitude of space station
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ earth_station_alt_m: 1172
+ earth_station_lat_deg: -15.8
+ earth_station_long_diff_deg: 0.0
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hotspot_eess_active/readme.md b/sharc/campaigns/imt_hotspot_eess_active/readme.md
new file mode 100644
index 000000000..66a2e8738
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/readme.md
@@ -0,0 +1,15 @@
+# IMT Hotspot EESS Active Campaign
+
+This campaign's objective is to try and replicate Luciano's contribution 20.
+
+Using the script `plot_results.py` automatically aggregates results and compares it to Luciano's INR results in
+contrib 20
+
+The document that should be referenced is
+"HIBS and IMT-2020 Coexistence with other Space/Terrestrial
+Communications and Radar Systems", a Doctoral Thesis by Luciano Camilo Alexandre publicated on Dec 2022
+
+The document can be downloaded found [here](https://www2.inatel.br/biblioteca/teses-de-doutorado)
+and
+[downloaded here](https://biblioteca.inatel.br/cict/acervo%20publico/sumarios/Teses%20de%20Doutorado%20do%20Inatel/Luciano%20Camilo%20Alexandre.pdf)
+
diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py
new file mode 100644
index 000000000..861738050
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py
@@ -0,0 +1,77 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+import pandas
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="beam_small_dl",
+ legend="Small Beam DL"
+ ).add_plot_legend_pattern(
+ dir_name_contains="beam_small_ul",
+ legend="Small Beam UL"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# This function aggregates IMT downlink and uplink
+aggregated_results = PostProcessor.aggregate_results(
+ dl_samples=post_processor.get_results_by_output_dir("_dl").system_inr,
+ ul_samples=post_processor.get_results_by_output_dir("_ul").system_inr,
+ ul_tdd_factor=0.25,
+ # SF is not exactly 1, but approx
+ n_bs_sim=1,
+ n_bs_actual=1
+)
+
+# Add a protection criteria line:
+# protection_criteria = int
+
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .add_vline(protection_criteria, line_dash="dash")
+
+# Show a single plot:
+relevant = post_processor\
+ .get_plot_by_results_attribute_name("system_inr")
+
+aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results)
+
+relevant.add_trace(
+ go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',),
+)
+
+compare_to = pandas.read_csv(
+ os.path.join(campaign_base_dir, "comparison", "contribution_20.csv"),
+ skiprows=1
+)
+
+comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1])
+
+relevant.add_trace(
+ go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Contrib 20 INR',),
+)
+
+relevant.show()
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..dd0ad2c54
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hotspot_eess_active"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..9dce5cd36
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hotspot_eess_active"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv
new file mode 100644
index 000000000..cd95f43fe
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv
@@ -0,0 +1,37 @@
+# Fig 15
+x, y
+-159.05775075987842, 0.9633492947617223
+-158.48024316109422, 0.9543984596408218
+-157.75075987841944, 0.9633492947617223
+-156.99088145896656, 0.9194190830166598
+-156.50455927051672, 0.8453314502151946
+-156.20060790273556, 0.7699924848865801
+-155.95744680851064, 0.6570033251510908
+-155.80547112462006, 0.5658518012594294
+-155.62310030395136, 0.49191706844917393
+-155.56231003039514, 0.43165336925959
+-155.44072948328267, 0.3787724458933014
+-155.34954407294833, 0.3201882609885405
+-155.22796352583586, 0.2583224566423367
+-155.10638297872342, 0.20647375917915298
+-154.98480243161094, 0.1531563949210873
+-154.89361702127658, 0.13190773718012405
+-154.83282674772036, 0.11574800476981631
+-154.74164133738603, 0.0850608522508284
+-154.68085106382978, 0.06611047389133969
+-154.62006079027356, 0.05333680870333699
+-154.52887537993922, 0.041842885079015825
+-154.43768996960486, 0.032520870510413455
+-154.40729483282675, 0.024808162306509743
+-154.28571428571428, 0.018061622323899008
+-154.25531914893617, 0.014708472321254355
+-154.19452887537994, 0.012318009665808635
+-154.19452887537994, 0.010609029955405396
+-154.16413373860183, 0.009137151183368584
+-154.10334346504558, 0.007869478368773596
+-154.07294832826747, 0.005267707132875599
+-154.01215805471125, 0.004370594758137502
+-153.9209726443769, 0.0028984256351332048
+-153.82978723404256, 0.001958357122672717
+-153.76899696048633, 0.0014526539259467812
+-153.7386018237082, 0.0010284002230430917
\ No newline at end of file
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv
new file mode 100644
index 000000000..729134dc3
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv
@@ -0,0 +1,27 @@
+# Results collected from luciano's contribution 21, Figure 8.
+x,y
+-160.4357066950053,0.9613501484827673
+-159.798087141339,0.9613501484827673
+-158.84165781083954,0.9613501484827673
+-157.778958554729,0.9519233878378311
+-156.58342189160467,0.9333462695554301
+-155.3347502656748,0.8797619862955459
+-153.9798087141339,0.7663916856958233
+-153.182784272051,0.6231307348505225
+-152.4123273113709,0.45910450561838223
+-151.96068012752391,0.344987324360697
+-151.5356004250797,0.25417657344926176
+-151.29649309245482,0.20064350554165133
+-150.8182784272051,0.12877841672100215
+-150.57917109458023,0.1026626022035896
+-150.26036131774708,0.06334492703460955
+-149.9415515409139,0.044426921419985395
+-149.70244420828905,0.02768378019158192
+-149.43676939426143,0.01794415314903341
+-149.0913921360255,0.010333925713156772
+-148.93198724760893,0.00631371983651216
+-148.63974495217855,0.003530115158319504
+-148.50690754516472,0.0026008856310466007
+-148.34750265674813,0.0017194043905352397
+-148.1615302869288,0.0011592951222250565
+-148.1615302869288,0.0011592951222250565
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml
new file mode 100644
index 000000000..9440c604d
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml
@@ -0,0 +1,270 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 10000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_SS, FSS_SS, FSS_ES, FS, RAS
+ system: EESS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 39
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_hotspot_eess_passive/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_DL
+imt:
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 10250
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: BEAMFORMING
+ bs:
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 16
+ ###########################################################################
+ # Base station height [m]
+ height: 6
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 0
+ antenna:
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 120.0
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90.0
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5.5
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5195
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5195
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 30
+ enable_beamsteering_vertical_limit: "ON"
+ beamsteering_vertical_limit: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: HOTSPOT
+ hotspot:
+ ###########################################################################
+ # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
+ wrap_around: FALSE
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ # 1,14884 / 1,000,000 m2
+ intersite_distance: 4647
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 3
+ # ###########################################################################
+ # # Maximum 2D distance between hotspot and UE [m]
+ # # This is the hotspot radius
+ # max_dist_hotspot_ue: 100
+ # ###########################################################################
+ # # Minimum 2D distance between macro cell base station and hotspot [m]
+ # min_dist_bs_hotspot: 0
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ antenna:
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 360
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 180
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -4
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+eess_ss:
+ # sensor C7
+ ###########################################################################
+ # satellite altitude [m]
+ altitude: 407000.0
+ ###########################################################################
+ # Off-nadir pointing angle [deg]
+ nadir_angle: 48.6
+ ###########################################################################
+ # satellite center frequency [MHz]
+ frequency: 10650
+ ###########################################################################
+ # satellite bandwidth [MHz]
+ bandwidth: 100
+ ###########Creates a statistical distribution of nadir angle###############
+ ##############following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: OFF
+ # # distribution_type = UNIFORM
+ # # nadir_angle_distribution = 18.6,49.4
+ # ###########################################################################
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813"
+ # "ITU-R RS.1861 9a"
+ # "ITU-R RS.1861 9b"
+ # "ITU-R RS.1861 9c"
+ # "ITU-R RS.2043"
+ # "OMNI"
+ antenna_pattern: ITU-R RS.1813
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ antenna_efficiency: 0.606
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: 1.1
+ ###########################################################################
+ # receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: 39.6
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "P619"
+ channel_model: P619
+ # earth station at Brasรญlia - Brasil
+ earth_station_alt_m: 6
+ earth_station_lat_deg: -15.8
+ earth_station_long_diff_deg: 0.0
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml
new file mode 100644
index 000000000..82c6210ce
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml
@@ -0,0 +1,278 @@
+# ALTERNATIVE:
+# set the following attributes to use default:
+# bs_element_phi_3db: 90.0
+# bs_element_theta_3db: 90.0
+# element_spacing: 5
+# REASONING:
+# Figure 6 presents element pattern found by using the above config
+
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 10000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_SS, FSS_SS, FSS_ES, FS, RAS
+ system: EESS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 39
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_hotspot_eess_passive/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_DL_alternative
+imt:
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 10250
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: BEAMFORMING
+ bs:
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 16
+ ###########################################################################
+ # Base station height [m]
+ height: 6
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 0
+ antenna:
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 90.0
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90.0
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5.5
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: HOTSPOT
+ hotspot:
+ ###########################################################################
+ # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
+ wrap_around: FALSE
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ # 1,14884 / 1,000,000 m2
+ intersite_distance: 4647
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 3
+ # ###########################################################################
+ # # Maximum 2D distance between hotspot and UE [m]
+ # # This is the hotspot radius
+ # max_dist_hotspot_ue: 100
+ # ###########################################################################
+ # # Minimum 2D distance between macro cell base station and hotspot [m]
+ # min_dist_bs_hotspot: 0
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ antenna:
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 360
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 180
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -4
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+
+eess_ss:
+ # sensor C7
+ ###########################################################################
+ # satellite altitude [m]
+ altitude: 407000.0
+ ###########################################################################
+ # Off-nadir pointing angle [deg]
+ nadir_angle: 48.6
+ ###########################################################################
+ # satellite center frequency [MHz]
+ frequency: 10650
+ ###########################################################################
+ # satellite bandwidth [MHz]
+ bandwidth: 100
+ ###########Creates a statistical distribution of nadir angle###############
+ ##############following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: OFF
+ # # distribution_type = UNIFORM
+ # # nadir_angle_distribution = 18.6,49.4
+ # ###########################################################################
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813"
+ # "ITU-R RS.1861 9a"
+ # "ITU-R RS.1861 9b"
+ # "ITU-R RS.1861 9c"
+ # "ITU-R RS.2043"
+ # "OMNI"
+ antenna_pattern: ITU-R RS.1813
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ antenna_efficiency: 0.606
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: 1.1
+ ###########################################################################
+ # receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: 39.6
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "P619"
+ channel_model: P619
+ # earth station at Brasรญlia - Brasil
+ earth_station_alt_m: 6
+ earth_station_lat_deg: -15.8
+ earth_station_long_diff_deg: 0.0
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml
new file mode 100644
index 000000000..ac91bca59
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml
@@ -0,0 +1,271 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 10000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: UPLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_SS, FSS_SS, FSS_ES, FS, RAS
+ system: EESS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 57
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_hotspot_eess_passive/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_UL
+imt:
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 10250
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ bs:
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 16
+ ###########################################################################
+ # Base station height [m]
+ height: 6
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 0
+ antenna:
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 120.0
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90.0
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5.5
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5195
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5195
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ topology:
+ type: HOTSPOT
+ hotspot:
+ ###########################################################################
+ # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
+ wrap_around: FALSE
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 4647
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 3
+ # ###########################################################################
+ # # Maximum 2D distance between hotspot and UE [m]
+ # # This is the hotspot radius
+ # max_dist_hotspot_ue: 100
+ # ###########################################################################
+ # # Minimum 2D distance between macro cell base station and hotspot [m]
+ # min_dist_bs_hotspot: 0
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ antenna:
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: 360
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 180
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ # element_sla_v = 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+eess_ss:
+ # sensor C7
+ ###########################################################################
+ # satellite altitude [m]
+ altitude: 407000.0
+ ###########################################################################
+ # Off-nadir pointing angle [deg]
+ nadir_angle: 48.6
+ ###########################################################################
+ # satellite center frequency [MHz]
+ frequency: 10650
+ ###########################################################################
+ # satellite bandwidth [MHz]
+ bandwidth: 100
+ ###########Creates a statistical distribution of nadir angle###############
+ ##############following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: OFF
+ # # distribution_type = UNIFORM
+ # # nadir_angle_distribution = 18.6,49.4
+ # ###########################################################################
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813"
+ # "ITU-R RS.1861 9a"
+ # "ITU-R RS.1861 9b"
+ # "ITU-R RS.1861 9c"
+ # "ITU-R RS.2043"
+ # "OMNI"
+ antenna_pattern: ITU-R RS.1813
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ antenna_efficiency: 0.606
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: 1.1
+ ###########################################################################
+ # receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: 39.6
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "P619"
+ channel_model: P619
+ # earth station at Brasรญlia - Brasil
+ earth_station_alt_m: 1.5
+ earth_station_lat_deg: -15.8
+ earth_station_long_diff_deg: 0.0
+ season: SUMMER
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/readme.md b/sharc/campaigns/imt_hotspot_eess_passive/readme.md
new file mode 100644
index 000000000..8867c10f8
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/readme.md
@@ -0,0 +1,16 @@
+# IMT Hotspot EESS Passive Campaign
+
+This campaign's objective is to try and replicate Luciano's contribution 21.
+
+You can compare SHARC results against Luciano's results with `plot_results.py`.
+It automatically aggregates UL and DL considering TDD and SF (segment factor).
+
+We did not take the time to process the Uplink analysis to try and reach the same results.
+
+The document that should be referenced is
+"HIBS and IMT-2020 Coexistence with other Space/Terrestrial
+Communications and Radar Systems", a Doctoral Thesis by Luciano Camilo Alexandre publicated on Dec 2022
+
+The document can be downloaded found [here](https://www2.inatel.br/biblioteca/teses-de-doutorado)
+and
+[downloaded here](https://biblioteca.inatel.br/cict/acervo%20publico/sumarios/Teses%20de%20Doutorado%20do%20Inatel/Luciano%20Camilo%20Alexandre.pdf)
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py
new file mode 100644
index 000000000..4c1f3d529
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py
@@ -0,0 +1,125 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+import pandas
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="1_cluster_DL_alternative",
+ legend="Alternative Downlink"
+ ).add_plot_legend_pattern(
+ dir_name_contains="1_cluster_DL",
+ legend="Downlink"
+ ).add_plot_legend_pattern(
+ dir_name_contains="1_cluster_UL",
+ legend="Uplink"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+uplink_interf_samples = post_processor.get_results_by_output_dir("_UL").system_ul_interf_power
+
+# This function aggregates IMT downlink and uplink
+aggregated_results = PostProcessor.aggregate_results(
+ dl_samples=post_processor.get_results_by_output_dir("_DL_alternative").system_dl_interf_power,
+ ul_samples=uplink_interf_samples,
+ ul_tdd_factor=0.25,
+ # SF is not exactly 3, but approx
+ n_bs_sim=1,
+ n_bs_actual=3
+)
+
+# Add a protection criteria line:
+# protection_criteria = int
+
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .add_vline(protection_criteria, line_dash="dash")
+
+# Show a single plot:
+relevant = post_processor\
+ .get_plot_by_results_attribute_name("system_dl_interf_power")
+
+# Title of CDF updated because ul interf power will be included
+relevant.update_layout(
+ title='CDF Plot for Interference',
+)
+
+aggr_x, aggr_y = PostProcessor.cdf_from(uplink_interf_samples)
+
+relevant.add_trace(
+ go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Uplink',),
+)
+
+aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results)
+
+relevant.add_trace(
+ go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',),
+)
+
+# TODO: put some more stuff into PostProcessor if ends up being really used
+compare_to = pandas.read_csv(
+ os.path.join(campaign_base_dir, "comparison", "Fig. 8 EESS (Passive) Sensor.csv"),
+ skiprows=1
+)
+
+comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1])
+# inverting given chart from P of I > x to P of I < x
+comp_y = 1 - comp_y
+# converting dB to dBm
+comp_x = comp_x + 30
+
+relevant.add_trace(
+ go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Fig. 8 EESS (Passive) Sensor',),
+)
+
+compare_to = pandas.read_csv(
+ os.path.join(campaign_base_dir, "comparison", "Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv"),
+ skiprows=1
+)
+
+comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1])
+# inverting given chart from P of I > x to P of I < x
+comp_y = 1 - comp_y
+# converting dB to dBm
+comp_x = comp_x + 30
+
+relevant.add_trace(
+ go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Fig. 15 (IMT Uplink) EESS (Passive) Sensor',),
+)
+
+relevant.show()
+
+post_processor\
+ .get_plot_by_results_attribute_name("system_ul_interf_power").show()
+
+# for result in many_results:
+# # This generates the mean, median, variance, etc
+# stats = PostProcessor.generate_statistics(
+# result=result
+# ).write_to_results_dir()
+
+aggregated_res_statistics = PostProcessor.generate_sample_statistics(
+ "Aggregate Results Statistics",
+ aggregated_results
+)
+
+print("\n###########################")
+
+print(aggregated_res_statistics)
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..0a480dee6
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hotspot_eess_passive"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..d77396906
--- /dev/null
+++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_hotspot_eess_passive"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml
new file mode 100644
index 000000000..56da3c38f
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml
@@ -0,0 +1,387 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_30deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 30
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 20000.0
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1000
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: P619
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml
new file mode 100644
index 000000000..2c71d5fed
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml
@@ -0,0 +1,391 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_45deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 45
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: P619
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml
new file mode 100644
index 000000000..587c95b83
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml
@@ -0,0 +1,391 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_60deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 60
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: P619
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml
new file mode 100644
index 000000000..3c7928171
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml
@@ -0,0 +1,391 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_90deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 90
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+ ###########################################################################
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # space_station_alt_m - altitude of IMT space station (BS) (in meters)
+ # earth_station_alt_m - altitude of the system's earth station (in meters)
+ # earth_station_lat_deg - latitude of the system's earth station (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space station and system's earth station
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ # Enter the IMT space station height
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the UE antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: P619
+ param_p619:
+ space_station_alt_m: 20000.0
+ # Enter the RAS antenna heigth above sea level
+ earth_station_alt_m: 1000
+ # The RAS station lat
+ earth_station_lat_deg: -15.7801
+ # This parameter is ignored as it will be calculated from x,y positions in run time
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml
new file mode 100644
index 000000000..5bf3b0451
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml
@@ -0,0 +1,377 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_fspl_30deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 30
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 20000.0
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1000
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: FSPL
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml
new file mode 100644
index 000000000..afa02e826
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml
@@ -0,0 +1,377 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_fspl_45deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 45
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 20000.0
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1000
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: FSPL
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml
new file mode 100644
index 000000000..231043535
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml
@@ -0,0 +1,377 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_fspl_60deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 60
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: &imt_bs_height 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 20000.0
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1000
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ frequency: 2695
+ ###########################################################################
+ bandwidth: 10
+ ###########################################################################
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ ####
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ ###########################################################################
+ antenna:
+ pattern: OMNI
+ gain: 0
+
+ channel_model: FSPL
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml
new file mode 100644
index 000000000..f667ca5dc
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml
@@ -0,0 +1,377 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 1000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS,
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_mss_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_mss_ras_2600_MHz_fspl_90deg
+imt:
+ topology:
+ ###########################################################################
+ # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: NTN
+ ntn:
+ ###########################################################################
+ # NTN cell radius in network topology [m]
+ cell_radius: 90000
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ bs_elevation: 90
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 0.5
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2680.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 37
+ ###########################################################################
+ # Base station height [m]
+ height: 20000
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5
+ ###########################################################################
+ # User equipment noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor or NTN simulations
+ downtilt: 90
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -91
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: P619
+
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 20000.0
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1000
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+
+ season: SUMMER
+#### System Parameters
+single_earth_station:
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # frequency [MHz]
+ frequency: 2695
+ ###########################################################################
+ # bandwidth [MHz]
+ bandwidth: 10
+ ###########################################################################
+ # Station noise temperature [K]
+ noise_temperature: 90
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 0
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 15
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ type: FIXED
+ fixed: -90
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ type: FIXED
+ fixed: 45
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ type: FIXED
+ fixed:
+ ###########################################################################
+ x: 0
+ ###########################################################################
+ y: 0
+ antenna:
+ pattern: OMNI
+ gain: 0
+ channel_model: FSPL
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py
new file mode 100644
index 000000000..009aa54fb
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py
@@ -0,0 +1,101 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+# import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="MHz_30deg",
+ legend="30 deg (P619)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="MHz_45deg",
+ legend="45 deg (P619)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="MHz_60deg",
+ legend="60 deg (P619)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="MHz_90deg",
+ legend="90 deg (P619)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="fspl_30deg",
+ legend="30 deg (FSPL)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="fspl_45deg",
+ legend="45 deg (FSPL)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="fspl_60deg",
+ legend="60 deg (FSPL)"
+ ).add_plot_legend_pattern(
+ dir_name_contains="fspl_90deg",
+ legend="90 deg (FSPL)"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+# ^: typing.List[Results]
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# # This function aggregates IMT downlink and uplink
+# aggregated_results = PostProcessor.aggregate_results(
+# downlink_result=post_processor.get_results_by_output_dir("MHz_60deg_dl"),
+# uplink_result=post_processor.get_results_by_output_dir("MHz_60deg_ul"),
+# ul_tdd_factor=(3, 4),
+# n_bs_sim=7 * 19 * 3 * 3,
+# n_bs_actual=int
+# )
+
+# Add a protection criteria line:
+# protection_criteria = 160
+
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .add_vline(protection_criteria, line_dash="dash")
+
+# Show a single plot:
+# post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")\
+# .show()
+
+# Plot every plot:
+for plot in plots:
+ plot.show()
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
+
+# # example on how to aggregate results and add it to plot:
+# dl_res = post_processor.get_results_by_output_dir("1_cluster")
+# aggregated_results = PostProcessor.aggregate_results(
+# dl_samples=dl_res.system_dl_interf_power,
+# ul_samples=ul_res.system_ul_interf_power,
+# ul_tdd_factor=0.25,
+# n_bs_sim=1 * 19 * 3 * 3,
+# n_bs_actual=7 * 19 * 3 * 3
+# )
+
+# relevant = post_processor\
+# .get_plot_by_results_attribute_name("system_dl_interf_power")
+
+# aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results)
+
+# relevant.add_trace(
+# go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',),
+# )
+
+# relevant.show()
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..2300f1819
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_mss_ras_2600_MHz"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..4a6551c3b
--- /dev/null
+++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_mss_ras_2600_MHz"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/imt_ntn_footprint/scripts/imt_ntn_footprint.py b/sharc/campaigns/imt_ntn_footprint/scripts/imt_ntn_footprint.py
new file mode 100644
index 000000000..eddd24c54
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_footprint/scripts/imt_ntn_footprint.py
@@ -0,0 +1,92 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+from sharc.topology.topology_ntn import TopologyNTN
+from sharc.station_factory import StationFactory
+from sharc.parameters.imt.parameters_imt import ParametersImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.parameters_mss_ss import ParametersMssSs
+
+
+if __name__ == "__main__":
+
+ # Input parameters for MSS_SS
+ param_mss = ParametersMssSs()
+ param_mss.frequency = 2100.0 # MHz
+ param_mss.bandwidth = 20.0 # MHz
+ param_mss.altitude = 500e3 # meters
+ param_mss.azimuth = 0
+ param_mss.elevation = 90 # degrees
+ param_mss.cell_radius = 19e3 # meters
+ param_mss.intersite_distance = param_mss.cell_radius * np.sqrt(3)
+ param_mss.num_sectors = 19
+ param_mss.antenna_gain = 30 # dBi
+ param_mss.antenna_3_dB_bw = 4.4127
+ param_mss.antenna_l_s = 20 # in dB
+ # Parameters used for the S.1528 antenna
+ param_mss.antenna_pattern = "ITU-R-S.1528-Taylor"
+ # param_mss.antenna_pattern = "ITU-R-S.1528-LEO"
+ wavelength = 3e8 / (param_mss.frequency * 1e6) # in meters
+ # assuming 7dB roll-off factor and circular antenna
+ l_r = 0.74 * wavelength / np.sin(param_mss.antenna_3_dB_bw / 2) # in meters
+ l_t = l_r
+ param_mss.antenna_s1528.set_external_parameters(frequency=param_mss.frequency,
+ bandwidth=param_mss.bandwidth,
+ antenna_gain=param_mss.antenna_gain,
+ antenna_l_s=param_mss.antenna_l_s,
+ antenna_3_dB_bw=param_mss.antenna_3_dB_bw,
+ l_r=l_r,
+ l_t=l_t)
+ beam_idx = 15 # beam index used for gain analysis
+
+ seed = 100
+ rng = np.random.RandomState(seed)
+
+ # Parameters used for IMT-NTN and UE distribution
+ param_imt = ParametersImt()
+ param_imt.topology.type = "NTN"
+ param_imt.ue.azimuth_range = (-180, 180)
+ param_imt.bandwidth = 10 # MHz
+ param_imt.frequency = 2100 # MHz
+ param_imt.spurious_emissions = -13 # dB
+ param_imt.ue.distribution_azimuth = "UNIFORM"
+ param_imt.ue.k = 1000
+
+ ntn_topology = TopologyNTN(param_mss.intersite_distance,
+ param_mss.cell_radius,
+ param_mss.altitude,
+ param_mss.azimuth,
+ param_mss.elevation,
+ param_mss.num_sectors)
+
+ ntn_topology.calculate_coordinates()
+ param_ue_ant = ParametersAntennaImt()
+ ntn_ue = StationFactory.generate_imt_ue_outdoor(
+ param_imt, param_ue_ant, rng, ntn_topology)
+
+ ntn_ue.active = np.ones(ntn_ue.num_stations, dtype=bool)
+ ntn_bs = StationFactory.generate_mss_ss(param_mss)
+ phi, theta = ntn_bs.get_pointing_vector_to(ntn_ue)
+ station_1_active = np.where(ntn_bs.active)[0]
+ station_2_active = np.where(ntn_ue.active)[0]
+ beams_idx = np.zeros(len(station_2_active), dtype=int)
+ off_axis_angle = ntn_bs.get_off_axis_angle(ntn_ue)
+ gains = np.zeros(phi.shape)
+ for k in station_1_active:
+ gains[k, station_2_active] = \
+ ntn_bs.antenna[k].calculate_gain(
+ off_axis_angle_vec=off_axis_angle[k, station_2_active], theta_vec=theta[k, station_2_active])
+ # phi=off_axis_angle[k, station_2_active], theta=theta[k, station_2_active])
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d')
+ # ax.set_xlim([-200, 200])
+ # ax.set_ylim([-200, 200])
+ ntn_topology.plot_3d(ax, False) # Plot the 3D topology
+ im = ax.scatter(xs=ntn_ue.x / 1000, ys=ntn_ue.y / 1000,
+ c=gains[beam_idx] - np.max(param_mss.antenna_gain), vmin=-50, cmap='jet')
+ ax.view_init(azim=0, elev=90)
+ fig.colorbar(im, label='Normalized antenna gain (dBi)')
+
+ plt.show()
+ exit()
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/README.md b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/README.md
new file mode 100644
index 000000000..b81d3b0ee
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/README.md
@@ -0,0 +1,39 @@
+MSS-SS to IMT-DL Simulation โ 2300โฏMHz Band
+
+๐ Overview
+
+This folder contains the simulation setup for evaluating interference from a Mobile Satellite Service - Single Station (MSS-SS) to an IMT downlink (IMT-DL) system operating in the 2300โฏMHz frequency band.
+
+The scenario models a single MSS-SS satellite footprint located near the IMT coverage area. The simulation varies the distance between the MSS-SS footprint border and the IMT coverage edge, and computes the resulting Interference-to-Noise Ratio (INR).
+
+โธป
+
+๐ Running the Simulation
+ 1. Generate simulation parameters
+Run the parameter generation script:
+
+./scripts/parameter_gen.py
+
+
+ 2. Start the simulation
+ โข For parallel execution (multi-threaded):
+
+./scripts/start_simulations_multi_thread.py
+
+
+ โข For serial execution (single-threaded):
+
+./scripts/start_simulations_single_thread.py
+
+
+
+โธป
+
+๐ Generating Results
+
+After the simulations are complete, generate the result plots by running:
+
+./scripts/plot_resutls.py
+
+This will produce interactive Plotly HTML graphs summarizing the simulation outcomes.
+
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/input/parameters_imt_ntn_to_imt_tn_sep_distance_template.yaml b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/input/parameters_imt_ntn_to_imt_tn_sep_distance_template.yaml
new file mode 100644
index 000000000..ff5a730c5
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/input/parameters_imt_ntn_to_imt_tn_sep_distance_template.yaml
@@ -0,0 +1,514 @@
+# ###########################################################################
+# Parameters for the MSS_SS to IMT-DL interferece scenario in the 2300MHz band.
+# In this scenario the distance between the border of the MSS-SS footprint
+# and the IMT cluster is varied.
+# ###########################################################################
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 10000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, MSS_SS
+ system: MSS_SS
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: TRUE
+ enable_adjacent_channel: false
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/imt_ntn_to_imt_tn_co_channel/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_imt_ntn_to_imt_tn_co_channel_sep_0km
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 35
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: TRUE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2300.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20.0
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.0
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: MACROCELL
+ ###########################################################################
+ # Macrocell Topology parameters. Relevant when imt.topology.type == "MACROCELL"
+ macrocell:
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 750.0
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: false
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 543
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 2
+ ###########################################################################
+ # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT"
+ hotspot:
+ ###########################################################################
+ # Inter-site distance in hotspot network topology [m]
+ intersite_distance: 321
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: 99.9
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: 1.2
+ ###########################################################################
+ # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR"
+ indoor:
+ ###########################################################################
+ # Basic path loss model for indoor topology. Possible values:
+ # "FSPL" (free-space path loss),
+ # "INH_OFFICE" (3GPP Indoor Hotspot - Office)
+ basic_path_loss: FSPL
+ ###########################################################################
+ # Number of rows of buildings in the simulation scenario
+ n_rows: 3
+ ###########################################################################
+ # Number of colums of buildings in the simulation scenario
+ n_colums: 2
+ ###########################################################################
+ # Number of buildings containing IMT stations. Options:
+ # 'ALL': all buildings contain IMT stations.
+ # Number of buildings.
+ num_imt_buildings: 2
+ ###########################################################################
+ # Street width (building separation) [m]
+ street_width: 30.1
+ ###########################################################################
+ # Intersite distance [m]
+ intersite_distance: 40.1
+ ###########################################################################
+ # Number of cells per floor
+ num_cells: 3
+ ###########################################################################
+ # Number of floors per building
+ num_floors: 1
+ ###########################################################################
+ # Percentage of indoor UE's [0, 1]
+ ue_indoor_percent: .95
+ ###########################################################################
+ # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
+ building_class: THERMALLY_EFFICIENT
+ ###########################################################################
+ # NTN Topology Parameters
+ ntn:
+ ###########################################################################
+ # NTN cell radius or intersite distance in network topology [m]
+ # @important: You can set only one of cell_radius or intersite_distance
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ # NOTE: note that intersite distance has a different geometric meaning in ntn
+ cell_radius: 123
+ # intersite_distance: 155884
+ ###########################################################################
+ # NTN space station azimuth [degree]
+ bs_azimuth: 45
+ ###########################################################################
+ # NTN space station elevation [degree]
+ bs_elevation: 45
+ ###########################################################################
+ # number of sectors [degree]
+ num_sectors: 19
+ ###########################################################################
+ # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2
+ bs_backoff_power: 3
+ ###########################################################################
+ # NTN Antenna configuration
+ bs_n_rows_layer1 : 2
+ bs_n_columns_layer1: 2
+ bs_n_rows_layer2 : 4
+ bs_n_columns_layer2: 2
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: BEAMFORMING
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: .5
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 28.0
+ ###########################################################################
+ # Base station height [m]
+ height: 20.0
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5.0
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2.0
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 6
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 6.4
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 2.1
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 1
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 70
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: UNIFORM
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: UNIFORM
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -92.2
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 0.8
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -3
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+#### System Parameters
+mss_ss:
+ # Satellite bore-sight - this is the center of the central beam in meters (m)
+ x: 0.0
+ y: 0.0
+ # MSS_SS system center frequency in MHz
+ frequency: 2300.0
+ # MSS_SS system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_SS altitude w.r.t. sea level in meters (m)
+ altitude: 525000
+ # NTN cell radius in network topology [m]
+ cell_radius: 39475.0
+ # Satellite power density in dBW/Hz
+ tx_power_density: -52.2
+ # Satellite Tx max Gain in dBi
+ antenna_gain: 34.1
+ # Satellite azimuth w.r.t. simulation x-axis
+ azimuth: 0.0
+ # Satellite elevation w.r.t. simulation xy-plane (horizon)
+ elevation: 90.0
+ # Number of sectors
+ num_sectors: 19
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # Maximum Antenna gain in dBi
+ antenna_gain: 34.1
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ channel_model: FSPL
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 0
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 0
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.75
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
\ No newline at end of file
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/calculate_cluster_positions.py b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/calculate_cluster_positions.py
new file mode 100644
index 000000000..672ccc641
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/calculate_cluster_positions.py
@@ -0,0 +1,100 @@
+# Helper script that calculates the coordintaes of the NTN footprint based on the distance between borders with IMT-TN
+from sharc.parameters.parameters_mss_ss import ParametersMssSs
+from sharc.topology.topology_ntn import TopologyNTN
+from sharc.topology.topology_macrocell import TopologyMacrocell
+import matplotlib.pyplot as plt
+import math
+import numpy as np
+
+if __name__ == "__main__":
+
+ # distance from topology boarders in meters
+ border_distances_array = np.array(
+ [0, 20e3, 50e3, 100e3, 200e3, 300e3, 400e3, 500e3, 600e3, 700e3, 1000e3])
+
+ # Index in border_distances_array used for plotting
+ dist_idx = 5
+
+ # Input parameters for MSS_SS
+ param_mss = ParametersMssSs()
+ param_mss.frequency = 2160 # MHz
+ # param_mss.altitude = 500e3 # meters
+ param_mss.altitude = 1200e3 # meters
+ param_mss.azimuth = 0
+ param_mss.elevation = 90 # degrees
+ # param_mss.cell_radius = 19e3 # meters
+ param_mss.cell_radius = 45e3 # meters
+ param_mss.intersite_distance = param_mss.cell_radius * np.sqrt(3)
+ param_mss.num_sectors = 19
+
+ # Input paramters for IMT-TN macrocell topology
+ macro_cell_radius = 500 # meters
+
+ macrocell_num_clusters = 1
+
+ #######################################################
+
+ # calculate the position of the NTN footprint center
+ ntn_footprint_left_edge = - 4 * param_mss.cell_radius
+ ntn_footprint_radius = 5 * param_mss.cell_radius * np.sin(np.pi / 3)
+ macro_topology_radius = 4 * macro_cell_radius
+ ntn_footprint_x_offset = macro_topology_radius + ntn_footprint_radius + border_distances_array
+ param_mss.x = ntn_footprint_x_offset[dist_idx]
+
+ ntn_topology = TopologyNTN(param_mss.intersite_distance,
+ param_mss.cell_radius,
+ param_mss.altitude,
+ param_mss.azimuth,
+ param_mss.elevation,
+ param_mss.num_sectors)
+ ntn_topology.calculate_coordinates()
+ ntn_topology.x = ntn_topology.x + param_mss.x
+
+ seed = 100
+ rng = np.random.RandomState(seed)
+
+ intersite_distance = macro_cell_radius * 3 / 2
+ macro_topology = TopologyMacrocell(intersite_distance, macrocell_num_clusters)
+ macro_topology.calculate_coordinates()
+
+ ntn_circle = plt.Circle((param_mss.x, 0), radius=ntn_footprint_radius, fill=False, color='green')
+ macro_cicle = plt.Circle((0, 0), radius=macro_topology_radius, fill=False, color='red')
+
+ ## Print simulation information
+ print("\n######## Simulation scenario parameters ########")
+ for i, d in enumerate(border_distances_array):
+ print(f"Satellite altitude = {param_mss.altitude / 1000} km")
+ print(f"Beam footprint radius = {param_mss.cell_radius / 1000} km")
+ print(f"Satellite elevation w.r.t to boresight = {param_mss.elevation} deg")
+ print(f"Satellite azimuth w.r.t to boresight = {param_mss.azimuth} deg")
+ print(f"Border distance = {d / 1000} km")
+ print(f"NTN nadir offset w.r.t. IMT-TN cluster center = {ntn_footprint_x_offset[i] / 1000} km")
+ print(f"Satellite elevation w.r.t. IMT-TN cluster center = {np.round(
+ np.degrees(np.arctan(param_mss.altitude / ntn_footprint_x_offset[i])))} deg")
+ slant_path_len = np.sqrt(param_mss.altitude**2 + ntn_footprint_x_offset[i]**2)
+ print(f"Slant path lenght w.r.t. IMT-TN cluster center = {slant_path_len / 1000} km")
+ fspl = np.round(20 * np.log10(slant_path_len) + 20 * np.log10(param_mss.frequency * 1e6) - 147.55, 2)
+ print(f"Free space pathloss w.r.t. IMT-TN cluster center = {fspl}\n")
+
+ ## Plot the coverage areas for NTN and TN
+ fig = plt.figure()
+ ax = fig.add_subplot(111)
+
+ ntn_topology.plot(ax, scale=1)
+ macro_topology.plot(ax)
+
+ ax.add_patch(macro_cicle)
+ ax.add_patch(ntn_circle)
+ ax.arrow(macro_topology_radius, 0, param_mss.x - ntn_footprint_radius - macro_topology_radius, 0,
+ width=0.1, shape='full', color='red')
+ ax.annotate(f'Border distance\n{border_distances_array[dist_idx] / 1000} km',
+ ((macro_topology_radius + border_distances_array[dist_idx] / 2), 1000))
+
+ plt.title("IMT-NTN vs IMT-TN Footprints")
+ plt.xlabel("x-coordinate [m]")
+ plt.ylabel("y-coordinate [m]")
+ plt.tight_layout()
+ plt.grid()
+
+ plt.show()
+ exit()
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/parameter_gen.py b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/parameter_gen.py
new file mode 100644
index 000000000..2fcec85e6
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/parameter_gen.py
@@ -0,0 +1,44 @@
+"""Generates the parameters for the MSS-SS to IMT with varying border distances campaign.
+Parameters for the MSS_SS to IMT-DL interferece scenario in the 2300MHz band.
+In this scenario the distance between the border of the MSS-SS footprint
+and the IMT cluster is varied.
+"""
+import numpy as np
+import yaml
+import os
+
+from sharc.parameters.parameters_base import tuple_constructor
+
+yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor)
+
+local_dir = os.path.dirname(os.path.abspath(__file__))
+parameter_file_name = os.path.join(local_dir, "../input/parameters_imt_ntn_to_imt_tn_sep_distance_template.yaml")
+
+# load the base parameters from the yaml file
+with open(parameter_file_name, 'r') as file:
+ parameters_template = yaml.safe_load(file)
+
+# Distance from topology boarders in meters
+border_distances_array = np.array(
+ [0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3, 80e3, 90e3, 100e3])
+
+for dist in border_distances_array:
+ print(f'Generating parameters for distance {dist / 1e3} km')
+ # Create a copy of the base parameters
+ params = parameters_template.copy()
+
+ ntn_footprint_left_edge = - 4 * params['mss_ss']['cell_radius']
+ ntn_footprint_radius = 5 * params['mss_ss']['cell_radius'] * np.sin(np.pi / 3)
+ macro_topology_radius = 4 * params['imt']['topology']['macrocell']['intersite_distance'] / 3
+ params['mss_ss']['x'] = float(macro_topology_radius + ntn_footprint_radius + dist)
+
+ # Set the right campaign prefix
+ params['general']['output_dir_prefix'] = 'output_imt_ntn_to_imt_tn_co_channel_sep_' + str(dist / 1e3) + "_km"
+ # Save the parameters to a new yaml file
+ parameter_file_name = "../input/parameters_imt_ntn_to_imt_tn_co_channel_sep_" + str(dist / 1e3) + "_km.yaml"
+ with open(os.path.join(local_dir, parameter_file_name), 'w') as file:
+ yaml.dump(params, file, default_flow_style=False)
+
+ print(f'Parameters saved to {parameter_file_name} file.')
+
+ del params
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/plot_results.py b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/plot_results.py
new file mode 100644
index 000000000..e10655aae
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/plot_results.py
@@ -0,0 +1,56 @@
+import os
+import numpy as np
+from pathlib import Path
+from sharc.results import Results
+# import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+
+post_processor = PostProcessor()
+
+# Distance from topology boarders in meters
+border_distances_array = np.array(
+ [0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3, 80e3, 90e3, 100e3])
+
+# Add a legend to results in folder that match the pattern
+for dist in border_distances_array:
+ post_processor.add_plot_legend_pattern(
+ dir_name_contains=f"_imt_ntn_to_imt_tn_co_channel_sep_{dist / 1e3}_km",
+ legend=f"separation {dist / 1e3} Km"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+# ^: typing.List[Results]
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# Add a protection criteria line:
+protection_criteria = -6
+post_processor\
+ .get_plot_by_results_attribute_name("imt_dl_inr")\
+ .add_vline(protection_criteria, line_dash="dash")
+
+# Plot every plot
+plots_dir = Path(campaign_base_dir) / "output" / "plots"
+plots_dir.mkdir(parents=True, exist_ok=True)
+for plot in plots:
+ plot.update_layout(legend_traceorder="normal")
+ plot.write_html(
+ plots_dir / f"{plot.layout.meta['related_results_attribute']}.html",
+ include_plotlyjs="cdn",
+ auto_open=False,
+ config={"displayModeBar": False}
+ )
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..4254dd3cf
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,12 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign_re
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_ntn_to_imt_tn_co_channel"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+regex_pattern = r'^parameters_imt_ntn_to_imt_tn_co_channel_sep_.*_km.yaml'
+run_campaign_re("imt_ntn_to_imt_tn_co_channel", regex_pattern)
+print("Executing campaign with regex pattern:", regex_pattern)
diff --git a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..94fa30dae
--- /dev/null
+++ b/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_single_thread.py
@@ -0,0 +1,12 @@
+from sharc.run_multiple_campaigns import run_campaign_re
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "imt_ntn_to_imt_tn_co_channel"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+regex_pattern = r'^parameters_imt_ntn_to_imt_tn_co_channel_sep_.*_km.yaml'
+run_campaign_re("imt_ntn_to_imt_tn_co_channel", regex_pattern)
+print("Executing campaign with regex pattern:", regex_pattern)
diff --git a/sharc/campaigns/mss_d2d_to_imt/input/.gitignore b/sharc/campaigns/mss_d2d_to_imt/input/.gitignore
new file mode 100644
index 000000000..aaefe7107
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/input/.gitignore
@@ -0,0 +1,6 @@
+# This file is used to ignore files in the input directory of the MSS D2D to IMT campaign.
+*
+!.gitignore
+!parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml
+!parameters_mss_d2d_to_imt_ul_co_channel_system_A.yaml
+!parameters_mss_d2d_to_imt_lat_variation_template.yaml
diff --git a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml
new file mode 100644
index 000000000..a518d109b
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml
@@ -0,0 +1,460 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 50000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, MSS_SS, MSS_D2D
+ system: MSS_D2D
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: true
+ enable_adjacent_channel: false
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/mss_d2d_to_imt/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_mss_d2d_to_imt_dl_co_channel_system_A
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 35
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: TRUE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2300.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20.0
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ # IMT resource block bandwidth [MHz]
+ adjacent_ch_reception: ACS
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.0
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # The latitude position
+ central_latitude: -15.7801 # Brasรญlia
+ ###########################################################################
+ # The longitude position
+ central_longitude: -47.9292 # Brasรญlia
+ ##########################
+ central_altitude: 1200
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: SINGLE_BS
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 250
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 1
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1.0
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 46.0
+ ###########################################################################
+ # Base station height [m]
+ height: 20.0
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5.0
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 3.0
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-60., 60.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 16
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 0
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 2.1
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 1
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0.0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: UNIFORM_IN_CELL
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: UNIFORM
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -92.2
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 0.8
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 0
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -3
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+#### System Parameters
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # Adjacent channel emissions type
+ # Possible values are "ACLR", "SPECTRAL_MASK" and "OFF"
+ adjacent_ch_emissions: SPECTRAL_MASK
+ # chosen spectral Mask
+ spectral_mask: 3GPP E-UTRA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2300.0
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 39475.0
+ # Satellite power density in dBW/Hz
+ tx_power_density: -52.2
+ # Number of sectors
+ num_sectors: 1
+ # conditional to select active satellites
+ # in simulation we sample uniformly from time
+ # if no active satellites are active on that time
+ # we resample
+ # only active satellites will contribute to interference
+ sat_is_active_if:
+ # for a satellite to be active, it needs to respect ALL conditions
+ conditions:
+ # - LAT_LONG_INSIDE_COUNTRY
+ - MINIMUM_ELEVATION_FROM_ES
+ minimum_elevation_from_es: 5
+ # lat_long_inside_country:
+ # # You may specify another shp file for country borders reference
+ # # country_shapes_filename: sharc/topology/countries/ne_110m_admin_0_countries.shp
+ # country_name: BRAZIL
+ # # margin from inside of border [km]
+ # # if positive, makes border smaller by x km
+ # # if negative, makes border bigger by x km
+ # margin_from_border: 0
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # Maximum Antenna gain in dBi
+ antenna_gain: 34.1
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: FSPL
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
+ # Orbit parameters
+ orbits:
+ # Number of planes
+ - n_planes: 28
+ # Inclination in degrees
+ inclination_deg: 53.0
+ # Perigee in km
+ perigee_alt_km: 525.0
+ # Apogee in km
+ apogee_alt_km: 525.0
+ # Number of satellites per plane
+ sats_per_plane: 120
+ # Longitude of the first ascending node in degrees
+ long_asc_deg: 0.0
+ # Phasing in degrees
+ phasing_deg: 1.5
diff --git a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_lat_variation_template.yaml b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_lat_variation_template.yaml
new file mode 100644
index 000000000..ee4aae509
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_lat_variation_template.yaml
@@ -0,0 +1,461 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 50000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, MSS_SS, MSS_D2D
+ system: MSS_D2D
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: true
+ enable_adjacent_channel: false
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/mss_d2d_to_imt/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_mss_d2d_to_imt_dl_lat_variation
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 35
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: TRUE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2300.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20.0
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ # IMT resource block bandwidth [MHz]
+ adjacent_ch_reception: ACS
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.0
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # The latitude position
+ central_latitude: -15.7801 # Brasรญlia
+ ###########################################################################
+ # The longitude position
+ central_longitude: -47.9292 # Brasรญlia
+ ##########################
+ central_altitude: 1200
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: SINGLE_BS
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 250
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 1
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1.0
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 46.0
+ ###########################################################################
+ # Base station height [m]
+ height: 20.0
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5.0
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 3.0
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-60., 60.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 16
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 0
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 2.1
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 1
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0.0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: UNIFORM_IN_CELL
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: UNIFORM
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -92.2
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 0.8
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -3
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+#### System Parameters
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # Adjacent channel emissions type
+ # Possible values are "ACLR", "SPECTRAL_MASK" and "OFF"
+ adjacent_ch_emissions: SPECTRAL_MASK
+ # chosen spectral Mask
+ spectral_mask: 3GPP E-UTRA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2300.0
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 39475.0
+ # Satellite power density in dBW/Hz
+ tx_power_density: -52.2
+ # Number of sectors
+ num_sectors: 1
+ # conditional to select active satellites
+ # in simulation we sample uniformly from time
+ # if no active satellites are active on that time
+ # we resample
+ # only active satellites will contribute to interference
+ sat_is_active_if:
+ # for a satellite to be active, it needs to respect ALL conditions
+ conditions:
+ # - LAT_LONG_INSIDE_COUNTRY
+ - MINIMUM_ELEVATION_FROM_ES
+ minimum_elevation_from_es: 5
+ # lat_long_inside_country:
+ # # You may specify another shp file for country borders reference
+ # # country_shapes_filename: sharc/topology/countries/ne_110m_admin_0_countries.shp
+ # country_name: BRAZIL
+ # # margin from inside of border [km]
+ # # if positive, makes border smaller by x km
+ # # if negative, makes border bigger by x km
+ # margin_from_border: 0
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # Maximum Antenna gain in dBi
+ antenna_gain: 34.1
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: FSPL
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
+ # Orbit parameters
+ orbits:
+ # Number of planes
+ - n_planes: 28
+ # Inclination in degrees
+ inclination_deg: 53.0
+ # Perigee in km
+ perigee_alt_km: 525.0
+ # Apogee in km
+ apogee_alt_km: 525.0
+ # Number of satellites per plane
+ sats_per_plane: 120
+ # Longitude of the first ascending node in degrees
+ long_asc_deg: 0.0
+ # Phasing in degrees
+ phasing_deg: 1.5
+
diff --git a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_ul_co_channel_system_A.yaml b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_ul_co_channel_system_A.yaml
new file mode 100644
index 000000000..f7d54c064
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_ul_co_channel_system_A.yaml
@@ -0,0 +1,461 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 50000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: UPLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, MSS_SS, MSS_D2D
+ system: MSS_D2D
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: true
+ enable_adjacent_channel: false
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/mss_d2d_to_imt/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_mss_d2d_to_imt_ul_co_channel_system_A
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 35
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: TRUE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2300.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20.0
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ # IMT resource block bandwidth [MHz]
+ adjacent_ch_reception: ACS
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.0
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # The latitude position
+ central_latitude: -15.7801 # Brasรญlia
+ ###########################################################################
+ # The longitude position
+ central_longitude: -47.9292 # Brasรญlia
+ ##########################
+ central_altitude: 1200
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: SINGLE_BS
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 250
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 1
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1.0
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 46.0
+ ###########################################################################
+ # Base station height [m]
+ height: 20.0
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5.0
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 3.0
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-60., 60.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 10
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 16
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 0
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 2.1
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 1
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0.0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: UNIFORM_IN_CELL
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: UNIFORM
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -92.2
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 0.8
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -3
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+#### System Parameters
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # Adjacent channel emissions type
+ # Possible values are "ACLR", "SPECTRAL_MASK" and "OFF"
+ adjacent_ch_emissions: SPECTRAL_MASK
+ # chosen spectral Mask
+ spectral_mask: 3GPP E-UTRA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2300.0
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 39475.0
+ # Satellite power density in dBW/Hz
+ tx_power_density: -52.2
+ # Number of sectors
+ num_sectors: 1
+ # conditional to select active satellites
+ # in simulation we sample uniformly from time
+ # if no active satellites are active on that time
+ # we resample
+ # only active satellites will contribute to interference
+ sat_is_active_if:
+ # for a satellite to be active, it needs to respect ALL conditions
+ conditions:
+ # - LAT_LONG_INSIDE_COUNTRY
+ - MINIMUM_ELEVATION_FROM_ES
+ minimum_elevation_from_es: 5
+ # lat_long_inside_country:
+ # # You may specify another shp file for country borders reference
+ # # country_shapes_filename: sharc/topology/countries/ne_110m_admin_0_countries.shp
+ # country_name: BRAZIL
+ # # margin from inside of border [km]
+ # # if positive, makes border smaller by x km
+ # # if negative, makes border bigger by x km
+ # margin_from_border: 0
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # Maximum Antenna gain in dBi
+ antenna_gain: 34.1
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: FSPL
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: -15.7801
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.0
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
+ # Orbit parameters
+ orbits:
+ # Number of planes
+ - n_planes: 28
+ # Inclination in degrees
+ inclination_deg: 53.0
+ # Perigee in km
+ perigee_alt_km: 525.0
+ # Apogee in km
+ apogee_alt_km: 525.0
+ # Number of satellites per plane
+ sats_per_plane: 120
+ # Longitude of the first ascending node in degrees
+ long_asc_deg: 0.0
+ # Phasing in degrees
+ phasing_deg: 1.5
+
diff --git a/sharc/campaigns/mss_d2d_to_imt/readme.md b/sharc/campaigns/mss_d2d_to_imt/readme.md
new file mode 100644
index 000000000..124aa6e42
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/readme.md
@@ -0,0 +1,26 @@
+# Introduction
+
+This campaign simulates a MSS-DC constellation and computes the interference of it into a IMT station positioned
+on Earth surface.
+The MSS-DC orbit elements are given as parameteres as well as the IMT coordinates.
+
+The selection of interferer satellites is done as follows:
+- At each drop a random intant of the satellites is generated
+- Those satellies that are visible from the IMT terrestrial station are set as "active".
+- The visibily constraint is set as the satellites with elevation angles equal or greater that 5 deg (from the IMT
+station horizon)
+
+# Running the simulation
+
+Inside the `./scripts` folder there are two scritps for executing the simulation:
+
+### MSS-DC System A campaign
+`start_simulation_system_A.py` runs the scenario where the UE station is fixed in Brasรญlia.
+
+### MSS-DC Varying UE latitude
+`start_simulations_lat_variation.py` runs a batch of simulations where the UE longitude is varied over the 0 deg meridian. This is to access the impact of longitude values into the total interference.
+
+Before running the `start_simulations_lat_variation.py` campaing the parameters for the simulation must be generated by runing the `parameter_gen.py` script.
+
+# Plotting the results
+Run `plot_resutls.py`for the "simulation system A" campaign and `plot_resutls_lat_variation.py` for the "Varying UE latitude" campaign.
\ No newline at end of file
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/orbit_model_anaylisis.py b/sharc/campaigns/mss_d2d_to_imt/scripts/orbit_model_anaylisis.py
new file mode 100644
index 000000000..13088c18a
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/orbit_model_anaylisis.py
@@ -0,0 +1,116 @@
+# Description: This script is used to analyze the orbit model of an NGSO constellation.
+# The script calculates the number of visible satellites from a ground station and the elevation angles of the satellites.
+# The script uses the OrbitModel class from the sharc.satellite.ngso.orbit_model module to calculate the satellite positions.
+import numpy as np
+from pathlib import Path
+import plotly.graph_objects as go
+
+from sharc.parameters.parameters_mss_d2d import ParametersMssD2d
+from sharc.satellite.ngso.orbit_model import OrbitModel
+from sharc.satellite.utils.sat_utils import calc_elevation
+
+
+if __name__ == "__main__":
+
+ # Input parameters
+ local_dir = Path(__file__).parent.resolve()
+ param_file = local_dir / ".." / "input" / "parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml"
+ params = ParametersMssD2d()
+ params.load_parameters_from_file(param_file)
+ orbit_params = params.orbits[0]
+ print("Orbit parameters:")
+ print(orbit_params)
+
+ # Ground station location
+ GROUND_STA_LAT = -15.7801
+ GROUND_STA_LON = -42.9292
+ MIN_ELEV_ANGLE_DEG = 5.0 # minimum elevation angle for visibility
+
+ # Time duration in days for the linear time simulation
+ TIME_DURATION_HOURS = 72
+
+ # Random samples
+ N_DROPS = 50000
+
+ # Random seed for reproducibility
+ SEED = 6
+
+ # Instantiate the OrbitModel
+ orbit = OrbitModel(
+ Nsp=orbit_params.sats_per_plane,
+ Np=orbit_params.n_planes,
+ phasing=orbit_params.phasing_deg,
+ long_asc=orbit_params.long_asc_deg,
+ omega=orbit_params.omega_deg,
+ delta=orbit_params.inclination_deg,
+ hp=orbit_params.perigee_alt_km,
+ ha=orbit_params.apogee_alt_km,
+ Mo=orbit_params.initial_mean_anomaly
+ )
+
+ # Show visible satellites from ground-station
+ total_periods = int(TIME_DURATION_HOURS * 3600 / orbit.orbital_period_sec)
+ print(f"Total periods: {total_periods}")
+ pos_vec = orbit.get_satellite_positions_time_interval(initial_time_secs=0,
+ interval_secs=10,
+ n_periods=total_periods)
+ sat_altitude_km = orbit.apogee_alt_km # altitude of the satellites in kilometers
+ num_of_visible_sats_per_drop = []
+ elev_angles = calc_elevation(GROUND_STA_LAT, pos_vec['lat'], GROUND_STA_LON, pos_vec['lon'], sat_altitude_km)
+ elevation_angles_per_drop = elev_angles[np.where(np.array(elev_angles) > MIN_ELEV_ANGLE_DEG)]
+ num_of_visible_sats_per_drop = np.sum(np.array(elev_angles) > MIN_ELEV_ANGLE_DEG, axis=0)
+
+ # Show visible satellites from ground-station - random
+ num_of_visible_sats_per_drop_rand = []
+ elevation_angles_per_drop_rand = []
+ rng = np.random.RandomState(seed=SEED)
+ pos_vec = orbit.get_orbit_positions_random(rng=rng, n_samples=N_DROPS)
+ elev_angles = calc_elevation(GROUND_STA_LAT,
+ pos_vec['lat'],
+ GROUND_STA_LON, pos_vec['lon'],
+ sat_altitude_km)
+ elevation_angles_per_drop_rand = elev_angles[np.where(np.array(elev_angles) > MIN_ELEV_ANGLE_DEG)]
+ num_of_visible_sats_per_drop_rand = np.sum(np.array(elev_angles) > MIN_ELEV_ANGLE_DEG, axis=0)
+
+ # Free some memory
+ del elev_angles
+ del pos_vec
+
+ # plot histogram of visible satellites
+ fig = go.Figure(data=[go.Histogram(x=num_of_visible_sats_per_drop,
+ histnorm='probability', xbins=dict(start=-0.5, size=1))])
+ fig.update_layout(
+ title_text='Visible satellites per drop',
+ xaxis_title_text='Num of visible satellites',
+ yaxis_title_text='Probability',
+ bargap=0.2,
+ bargroupgap=0.1,
+ xaxis=dict(
+ tickmode='linear',
+ tick0=0,
+ dtick=1
+ )
+ )
+
+ fig.add_trace(go.Histogram(x=num_of_visible_sats_per_drop_rand,
+ histnorm='probability',
+ xbins=dict(start=-0.5, size=1)))
+
+ fig.data[0].name = 'Linear time'
+ fig.data[1].name = 'Random'
+ fig.update_layout(legend_title_text='Observation Type')
+ file_name = Path(__file__).parent / "visible_sats_per_drop.html"
+ fig.write_html(file=file_name, include_plotlyjs="cdn", auto_open=False)
+
+ # plot histogram of elevation angles
+ fig = go.Figure(data=[go.Histogram(x=np.array(elevation_angles_per_drop).flatten(),
+ histnorm='probability', xbins=dict(start=0, size=5))])
+ fig.update_layout(
+ title_text='Elevation angles',
+ xaxis_title_text='Elevation angle [deg]',
+ yaxis_title_text='Probability',
+ bargap=0.2,
+ bargroupgap=0.1
+ )
+ file_name = Path(__file__).parent / "elevation_angles.html"
+ fig.write_html(file=file_name, include_plotlyjs="cdn", auto_open=False)
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/parameter_gen_lat_variation.py b/sharc/campaigns/mss_d2d_to_imt/scripts/parameter_gen_lat_variation.py
new file mode 100644
index 000000000..79ad09ed9
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/parameter_gen_lat_variation.py
@@ -0,0 +1,42 @@
+"""Generates the parameters for the MSS D2D to IMT with varying latitude campaign.
+"""
+import numpy as np
+import yaml
+import os
+
+from sharc.parameters.parameters_base import tuple_constructor
+
+yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor)
+
+local_dir = os.path.dirname(os.path.abspath(__file__))
+parameter_file_name = os.path.join(local_dir, "../input/parameters_mss_d2d_to_imt_lat_variation_template.yaml")
+
+# load the base parameters from the yaml file
+with open(parameter_file_name, 'r') as file:
+ parameters = yaml.safe_load(file)
+
+# The scenario is to move the Earth station postion throught the Meridian from 0 to 60 degrees latitude.
+parameters['imt']['topology']['central_longitude'] = 0.0
+lats = np.arange(0, 70, 10)
+for direction in [('dl', 'DOWNLINK'), ('ul', 'UPLINK')]:
+ for lat in lats:
+ # Create a copy of the base parameters
+ params = parameters.copy()
+
+ # set the link direction
+ params['general']['imt_link'] = direction[1]
+
+ # Update the parameters with the new latitude
+ params['imt']['topology']['central_latitude'] = float(lat)
+ params['imt']['topology']['central_longitude'] = 0.0
+
+ # Set the right campaign prefix
+ params['general']['output_dir_prefix'] = 'output_mss_d2d_to_imt_lat_' + direction[0] + '_' + str(lat) + "_deg"
+ # Save the parameters to a new yaml file
+ parameter_file_name = "../input/parameters_mss_d2d_to_imt_lat_" + direction[0] + '_' + str(lat) + "_deg.yaml"
+ parameter_file_name = os.path.join(local_dir, parameter_file_name)
+ with open(parameter_file_name, 'w') as file:
+ yaml.dump(params, file, default_flow_style=False)
+
+ del params
+ print(f'Generated parameters for latitude {lat} degrees.')
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results.py b/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results.py
new file mode 100644
index 000000000..15a1d8ea9
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results.py
@@ -0,0 +1,111 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+from sharc.post_processor import PostProcessor
+import argparse
+
+# Command line argument parser
+parser = argparse.ArgumentParser(description="Generate and plot results.")
+parser.add_argument("--auto_open", action="store_true", default=False, help="Set this flag to open plots in a browser.")
+parser.add_argument("--scenario", type=int, choices=[0, 1], required=True,
+ help="Scenario parameter: 0 or 1. 0 for MSS-D2D to IMT-UL/DL,"
+ "1 for MSS-D2D to IMT-UL/DL with varying latitude.")
+args = parser.parse_args()
+scenario = args.scenario
+auto_open = args.auto_open
+
+local_dir = os.path.dirname(os.path.abspath(__file__))
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+if scenario == 0:
+ many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"),
+ filter_fn=lambda x: "_co_channel_system_A" in x,
+ only_latest=True)
+ post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="_mss_d2d_to_imt_ul_co_channel_system_A",
+ legend="MSS-D2D to IMT-UL"
+ )
+
+ post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains="_mss_d2d_to_imt_dl_co_channel_system_A",
+ legend="MSS-D2D to IMT-DL"
+ )
+elif scenario == 1:
+ many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"),
+ filter_fn=lambda x: "_lat_" in x,
+ only_latest=True)
+ for link in ["ul", "dl"]:
+ for i in range(0, 70, 10):
+ post_processor.add_plot_legend_pattern(
+ dir_name_contains="_lat_" + link + "_" + str(i) + "_deg",
+ legend="IMT-Link=" + link.upper() + " latitude=" + str(i) + "deg"
+ )
+else:
+ raise ValueError("Invalid scenario. Choose 0 or 1.")
+
+# ^: typing.List[Results]
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+plots = post_processor.generate_ccdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# Add a protection criteria line:
+protection_criteria = -6
+imt_dl_inr = post_processor.get_plot_by_results_attribute_name("imt_dl_inr", plot_type="ccdf")
+imt_dl_inr.add_vline(protection_criteria, line_dash="dash", annotation=dict(
+ text="Protection Criteria: " + str(protection_criteria) + " dB",
+ xref="x", yref="y",
+ x=protection_criteria + 0.5, y=0.8,
+ font=dict(size=12, color="red")
+))
+imt_dl_inr.update_layout(template="plotly_white")
+imt_ul_inr = post_processor.get_plot_by_results_attribute_name("imt_ul_inr", plot_type="ccdf")
+imt_ul_inr.add_vline(protection_criteria, line_dash="dash")
+
+# Combine INR plots into one:
+
+for trace in imt_ul_inr.data:
+ imt_dl_inr.add_trace(trace)
+
+# Update layout if needed
+imt_dl_inr.update_layout(title_text="CCDF Plot for IMT Downlink and Uplink INR",
+ xaxis_title="IMT INR [dB]",
+ yaxis_title="Cumulative Probability",
+ legend_title="Legend")
+
+file = os.path.join(campaign_base_dir, "output", "imt_dl_ul_inr.html")
+imt_dl_inr.write_html(file=file, include_plotlyjs="cdn", auto_open=auto_open)
+
+file = os.path.join(campaign_base_dir, "output", "imt_system_antenna_gain.html")
+imt_system_antenna_gain = post_processor.get_plot_by_results_attribute_name("imt_system_antenna_gain")
+imt_system_antenna_gain.write_html(file=file, include_plotlyjs="cdn", auto_open=auto_open)
+
+file = os.path.join(campaign_base_dir, "output", "system_imt_antenna_gain.html")
+system_imt_antenna_gain = post_processor.get_plot_by_results_attribute_name("system_imt_antenna_gain")
+system_imt_antenna_gain.write_html(file=file, include_plotlyjs="cdn", auto_open=auto_open)
+
+file = os.path.join(campaign_base_dir, "output", "sys_to_imt_coupling_loss.html")
+imt_system_path_loss = post_processor.get_plot_by_results_attribute_name("imt_system_path_loss")
+imt_system_path_loss.write_html(file=file, include_plotlyjs="cdn", auto_open=auto_open)
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results_lat_variation.py b/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results_lat_variation.py
new file mode 100644
index 000000000..c1c7e26eb
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results_lat_variation.py
@@ -0,0 +1,59 @@
+import os
+from pathlib import Path
+from sharc.results import Results
+from sharc.post_processor import PostProcessor
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+for i in range(0, 70, 10):
+ post_processor.add_plot_legend_pattern(
+ dir_name_contains="_lat_" + str(i) + "_deg",
+ legend="latitude=" + str(i) + "deg")
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True)
+# ^: typing.List[Results]
+
+post_processor.add_results(many_results)
+
+plots = post_processor.generate_ccdf_plots_from_results(
+ many_results
+)
+
+post_processor.add_plots(plots)
+
+# Add a protection criteria line:
+protection_criteria = -6
+post_processor\
+ .get_plot_by_results_attribute_name("imt_dl_inr", plot_type='ccdf')\
+ .add_vline(protection_criteria, line_dash="dash")
+
+# Show a single plot:
+post_processor\
+ .get_plot_by_results_attribute_name("imt_system_antenna_gain", plot_type='ccdf')\
+ .show()
+
+post_processor\
+ .get_plot_by_results_attribute_name("system_imt_antenna_gain", plot_type='ccdf')\
+ .show()
+
+post_processor\
+ .get_plot_by_results_attribute_name("sys_to_imt_coupling_loss", plot_type='ccdf')\
+ .show()
+
+post_processor\
+ .get_plot_by_results_attribute_name("imt_system_path_loss", plot_type='ccdf')\
+ .show()
+
+post_processor\
+ .get_plot_by_results_attribute_name("imt_dl_inr", plot_type='ccdf')\
+ .show()
+
+for result in many_results:
+ # This generates the mean, median, variance, etc
+ stats = PostProcessor.generate_statistics(
+ result=result
+ ).write_to_results_dir()
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations.py b/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations.py
new file mode 100644
index 000000000..5c2f2f703
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations.py
@@ -0,0 +1,43 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign_re
+import argparse
+import subprocess
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "mss_d2d_to_imt"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+# run_campaign_re(name_campaign, r'^parameters_mss_d2d_to_imt_co_channel_system_A.yaml')
+# run_campaign_re(name_campaign, r'^parameters_mss_d2d_to_imt_(dl,ul)_co_channel_system_A.yaml')
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Run SHARC MSS-D2D simulations.")
+ parser.add_argument(
+ "--scenario",
+ type=int,
+ choices=range(0, 2),
+ required=True,
+ help="""Specify the campaign scenario as a number (0 or 1).
+ 0 - MSS-D2D to IMT-UL/DL
+ 1 - MSS-D2D to IMT-UL/DL with varying latitude."""
+ )
+
+ # Parse the arguments
+ args = parser.parse_args()
+
+ # Update the campaign regex with the provided scenario
+ scenario = args.scenario
+ print(f"Running scenario {scenario}...")
+ if scenario == 0:
+ regex_pattern = r'^parameters_mss_d2d_to_imt_(dl|ul)_co_channel_system_A.yaml'
+ elif scenario == 1:
+ print("Generating parameters for varying latitude campaign...")
+ subprocess.run(["python", "parameter_gen_lat_variation.py"],
+ check=True)
+ regex_pattern = r'^parameters_mss_d2d_to_imt_lat_.*_deg.yaml'
+
+ # Run the campaign with the updated regex pattern
+ print("Executing campaign with regex pattern:", regex_pattern)
+ run_campaign_re(name_campaign, regex_pattern)
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations_lat_variation.py b/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations_lat_variation.py
new file mode 100644
index 000000000..21f6b17e7
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations_lat_variation.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign_re
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "mss_d2d_to_imt"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign_re(name_campaign, r'^parameters_mss_d2d_to_imt_lat_.*_deg.yaml')
diff --git a/sharc/campaigns/mss_d2d_to_imt/scripts/taylor_diagram_for_mss_d2d.py b/sharc/campaigns/mss_d2d_to_imt/scripts/taylor_diagram_for_mss_d2d.py
new file mode 100644
index 000000000..3c0d5a469
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt/scripts/taylor_diagram_for_mss_d2d.py
@@ -0,0 +1,55 @@
+# Generate a Taylor diagram for the MSS D2D to IMT sharing scenario with parameters from
+# Annex 4 to Working Party 4C Chairโs Report
+# WORKING DOCUMENT ON SHARING AND COMPATIBILITY STUDIES IN RELATION TO WRC-27 AGENDA ITEM 1.13
+# Section 4.1.4
+
+import numpy as np
+import plotly.graph_objects as go
+
+from sharc.antenna.antenna_s1528 import AntennaS1528Taylor
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+
+# Antenna parameters
+g_max = 34.1 # dBi
+l_r = l_t = 1.6 # meters
+slr = 20 # dB
+n_side_lobes = 2 # number of side lobes
+freq = 2e3 # MHz
+
+antenna_params = ParametersAntennaS1528(
+ antenna_gain=g_max,
+ frequency=freq, # in MHz
+ bandwidth=5, # in MHz
+ slr=slr,
+ n_side_lobes=n_side_lobes,
+ l_r=l_r,
+ l_t=l_t,
+)
+
+# Create an instance of AntennaS1528Taylor
+antenna = AntennaS1528Taylor(antenna_params)
+
+# Define phi angles from 0 to 60 degrees for plotting
+theta_angles = np.linspace(0, 90, 901)
+
+# Calculate gains for each phi angle at a fixed theta angle (e.g., theta=0)
+gain_rolloff_7 = antenna.calculate_gain(off_axis_angle_vec=theta_angles,
+ theta_vec=np.zeros_like(theta_angles))
+
+# Create a plotly figure
+fig = go.Figure()
+
+# Add a trace for the antenna gain
+fig.add_trace(go.Scatter(x=theta_angles, y=gain_rolloff_7 - g_max, mode='lines', name='Antenna Gain'))
+# Limit the y-axis from 0 to 35 dBi
+fig.update_yaxes(range=[-20 - g_max, 2])
+fig.update_xaxes(range=[0, 90])
+# Set the title and labels
+fig.update_layout(
+ title='Normalized SystemA Antenna Pattern',
+ xaxis_title='Theta (degrees)',
+ yaxis_title='Gain (dBi)'
+)
+
+# Show the plot
+fig.show()
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/input/.gitignore b/sharc/campaigns/mss_d2d_to_imt_cross_border/input/.gitignore
new file mode 100644
index 000000000..d6b7ef32c
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/input/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/readme.md b/sharc/campaigns/mss_d2d_to_imt_cross_border/readme.md
new file mode 100644
index 000000000..c3b7989fd
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/readme.md
@@ -0,0 +1,3 @@
+A cross border study with IMT TN single BS and UE at Paraguay's side of Friendship Bridge and MSS DC system over Brazil.
+
+UE is victim
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/base_input.yaml b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/base_input.yaml
new file mode 100644
index 000000000..8bf13a864
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/base_input.yaml
@@ -0,0 +1,551 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 10000
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: UPLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, MSS_SS, MSS_D2D
+ system: MSS_D2D
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: true
+ enable_adjacent_channel: false
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: FALSE
+ ###########################################################################
+ # output destination folder - this is relative SHARC/sharc directory
+ output_dir: campaigns/mss_d2d_to_imt_cross_border/output_base_ul/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix: output_mss_d2d_to_imt_cross_border_base_ul
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 35
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: TRUE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 2160.0
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 20.0
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.180
+ # IMT resource block bandwidth [MHz]
+ adjacent_ch_reception: ACS
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.0
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.1
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # The latitude position is is_spherical is set to true
+ central_latitude: -25.5549751
+ ###########################################################################
+ # The longitude position is is_spherical is set to true
+ central_longitude: -54.5746686
+ ##########################
+ central_altitude: 200
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: SINGLE_BS
+ ###########################################################################
+ # Macrocell Topology parameters. Relevant when imt.topology.type == "MACROCELL"
+ macrocell:
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 1500.0
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: false
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 1
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 100
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 1
+ ###########################################################################
+ # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT"
+ hotspot:
+ ###########################################################################
+ # Inter-site distance in hotspot network topology [m]
+ intersite_distance: 321
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: 99.9
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: 1.2
+ ###########################################################################
+ # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR"
+ indoor:
+ ###########################################################################
+ # Basic path loss model for indoor topology. Possible values:
+ # "FSPL" (free-space path loss),
+ # "INH_OFFICE" (3GPP Indoor Hotspot - Office)
+ basic_path_loss: FSPL
+ ###########################################################################
+ # Number of rows of buildings in the simulation scenario
+ n_rows: 3
+ ###########################################################################
+ # Number of colums of buildings in the simulation scenario
+ n_colums: 2
+ ###########################################################################
+ # Number of buildings containing IMT stations. Options:
+ # 'ALL': all buildings contain IMT stations.
+ # Number of buildings.
+ num_imt_buildings: 2
+ ###########################################################################
+ # Street width (building separation) [m]
+ street_width: 30.1
+ ###########################################################################
+ # Intersite distance [m]
+ intersite_distance: 40.1
+ ###########################################################################
+ # Number of cells per floor
+ num_cells: 3
+ ###########################################################################
+ # Number of floors per building
+ num_floors: 1
+ ###########################################################################
+ # Percentage of indoor UE's [0, 1]
+ ue_indoor_percent: .95
+ ###########################################################################
+ # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
+ building_class: THERMALLY_EFFICIENT
+ ###########################################################################
+ # NTN Topology Parameters
+ ntn:
+ ###########################################################################
+ # NTN cell radius or intersite distance in network topology [m]
+ # @important: You can set only one of cell_radius or intersite_distance
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ # NOTE: note that intersite distance has a different geometric meaning in ntn
+ cell_radius: 123
+ # intersite_distance: 155884
+ ###########################################################################
+ # NTN space station azimuth [degree]
+ bs_azimuth: 45
+ ###########################################################################
+ # NTN space station elevation [degree]
+ bs_elevation: 45
+ ###########################################################################
+ # number of sectors [degree]
+ num_sectors: 19
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: SINGLE_ELEMENT
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: 1.0
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 28.0
+ ###########################################################################
+ # Base station height [m]
+ height: 20.0
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 5.0
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 2.0
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-60., 60.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: M2101
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 6
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 6.4
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 2.1
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 1
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 0.0
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: UNIFORM_IN_CELL
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance: RAYLEIGH
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: UNIFORM
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -92.2
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 0.8
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 23
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 9
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: FIXED
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: -3
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 1
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 1
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.8
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+#### System Parameters
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # Adjacent channel emissions type
+ # Possible values are "ACLR", "SPECTRAL_MASK" and "OFF"
+ adjacent_ch_emissions: SPECTRAL_MASK
+ # chosen spectral Mask
+ spectral_mask: 3GPP E-UTRA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2167.5
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 39475.0
+ # Satellite power density in dBW/Hz
+ tx_power_density: -54.2
+ # Number of sectors
+ num_sectors: 19
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ # Maximum Antenna gain in dBi
+ antenna_gain: 34.1
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondarylobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ sat_is_active_if:
+ # for a satellite to be active, it needs to respect ALL conditions
+ conditions:
+ - LAT_LONG_INSIDE_COUNTRY
+ - MINIMUM_ELEVATION_FROM_ES
+ minimum_elevation_from_es: 5
+ lat_long_inside_country:
+ # You may specify another shp file for country borders reference
+ # country_shapes_filename: sharc/topology/countries/ne_110m_admin_0_countries.shp
+ country_names:
+ - Brazil
+ # - Argentina
+ # margin from inside of border [km]
+ # if positive, makes border smaller by x km
+ # if negative, makes border bigger by x km
+ margin_from_border: 0
+ # margin_from_border: 157.9
+ # margin_from_border: 213.4
+ # margin_from_border: 268.9
+ # margin_from_border: 324.4
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: SatelliteSimple
+ # ###########################################################################
+ # # P619 parameters
+ # param_p619:
+ # ###########################################################################
+ # # altitude of ES system [m]
+ # earth_station_alt_m: 1200
+ # ###########################################################################
+ # # latitude of ES system [m]
+ # earth_station_lat_deg: -15.7801
+ # ###########################################################################
+ # # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # # (positive if space-station is to the East of earth-station)
+ # earth_station_long_diff_deg: 0.0
+ # ###########################################################################
+ # # year season: SUMMER of WINTER
+ # season: SUMMER
+ # Orbit parameters
+ orbits:
+ # Number of planes
+ - n_planes: 28
+ # Inclination in degrees
+ inclination_deg: 53.0
+ # Perigee in km
+ perigee_alt_km: 525.0
+ # Apogee in km
+ apogee_alt_km: 525.0
+ # Number of satellites per plane
+ sats_per_plane: 128
+ # Longitude of the first ascending node in degrees
+ long_asc_deg: 0.0
+ # Phasing in degrees
+ phasing_deg: 1.5
\ No newline at end of file
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/generate_parameters_from_reference.py b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/generate_parameters_from_reference.py
new file mode 100644
index 000000000..07bfcd106
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/generate_parameters_from_reference.py
@@ -0,0 +1,124 @@
+"""Generates scenarios based on main parameters
+"""
+import numpy as np
+import yaml
+import os
+from copy import deepcopy
+
+from sharc.parameters.parameters_base import tuple_constructor
+
+yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor)
+
+local_dir = os.path.dirname(os.path.abspath(__file__))
+input_dir = os.path.join(local_dir, "../input")
+
+ul_parameter_file_name = os.path.join(local_dir, "./base_input.yaml")
+
+# load the base parameters from the yaml file
+with open(ul_parameter_file_name, 'r') as file:
+ ul_parameters = yaml.safe_load(file)
+
+dl_parameters = deepcopy(ul_parameters)
+dl_parameters['general']['output_dir'] = ul_parameters['general']['output_dir'].replace("_ul", "_dl")
+dl_parameters['general']['output_dir_prefix'] = ul_parameters['general']['output_dir_prefix'].replace("_ul", "_dl")
+dl_parameters['general']['imt_link'] = "DOWNLINK"
+
+country_border = 4 * ul_parameters["mss_d2d"]["cell_radius"] / 1e3
+print("country_border", country_border)
+
+# doesn't matter from which, both will give same result
+output_dir_pattern = ul_parameters['general']['output_dir'].replace("_base_ul", "_")
+output_prefix_pattern = ul_parameters['general']['output_dir_prefix'].replace("_base_ul", "_")
+
+for dist in [
+ 0,
+ country_border,
+ country_border + 111 / 2,
+ country_border + 111,
+ country_border + 3 * 111 / 2,
+ country_border + 2 * 111
+]:
+ ul_parameters["mss_d2d"]["sat_is_active_if"]["lat_long_inside_country"]["margin_from_border"] = dist
+ specific = f"{dist}km_base_ul"
+ ul_parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace("", specific)
+
+ dl_parameters["mss_d2d"]["sat_is_active_if"]["lat_long_inside_country"]["margin_from_border"] = dist
+ specific = f"{dist}km_base_dl"
+ dl_parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace("", specific)
+
+ ul_parameter_file_name = os.path.join(input_dir, f"./parameters_mss_d2d_to_imt_cross_border_{dist}km_base_ul.yaml")
+ dl_parameter_file_name = os.path.join(input_dir, f"./parameters_mss_d2d_to_imt_cross_border_{dist}km_base_dl.yaml")
+
+ with open(
+ dl_parameter_file_name,
+ 'w'
+ ) as file:
+ yaml.dump(dl_parameters, file, default_flow_style=False)
+ with open(
+ ul_parameter_file_name,
+ 'w'
+ ) as file:
+ yaml.dump(ul_parameters, file, default_flow_style=False)
+
+ for link in ["ul", "dl"]:
+ if link == "ul":
+ parameters = deepcopy(ul_parameters)
+ if link == "dl":
+ parameters = deepcopy(dl_parameters)
+
+ parameters['mss_d2d']['num_sectors'] = 19
+ # 1 out of 19 beams are active
+ parameters['mss_d2d']['beams_load_factor'] = 0.05263157894
+
+ specific = f"{dist}km_activate_random_beam_5p_{link}"
+ parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace("", specific)
+
+ with open(
+ os.path.join(input_dir, f"./parameters_mss_d2d_to_imt_cross_border_{specific}.yaml"),
+ 'w'
+ ) as file:
+ yaml.dump(parameters, file, default_flow_style=False)
+
+ parameters['mss_d2d']['num_sectors'] = 19
+ parameters['mss_d2d']['beams_load_factor'] = 0.3
+
+ specific = f"{dist}km_activate_random_beam_30p_{link}"
+ parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace("", specific)
+
+ with open(
+ os.path.join(input_dir, f"./parameters_mss_d2d_to_imt_cross_border_{specific}.yaml"),
+ 'w'
+ ) as file:
+ yaml.dump(parameters, file, default_flow_style=False)
+
+ parameters['mss_d2d']['num_sectors'] = 1
+ parameters['mss_d2d']['beams_load_factor'] = 1
+
+ parameters['mss_d2d']['center_beam_positioning'] = {}
+
+ parameters['mss_d2d']['center_beam_positioning']['type'] = "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE"
+
+ # for uniform area distribution
+ parameters['mss_d2d']['center_beam_positioning']['angle_from_subsatellite_phi'] = {
+ 'type': "~U(MIN,MAX)",
+ 'distribution': {
+ 'min': -180.,
+ 'max': 180.,
+ }
+ }
+ parameters['mss_d2d']['center_beam_positioning']['distance_from_subsatellite'] = {
+ 'type': "~SQRT(U(0,1))*MAX",
+ 'distribution': {
+ 'min': 0,
+ 'max': parameters['mss_d2d']["cell_radius"] * 4,
+ }
+ }
+
+ specific = f"{dist}km_random_pointing_1beam_{link}"
+ parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace("", specific)
+
+ with open(
+ os.path.join(input_dir, f"./parameters_mss_d2d_to_imt_cross_border_{specific}.yaml"),
+ 'w'
+ ) as file:
+ yaml.dump(parameters, file, default_flow_style=False)
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_3d_param_file.py b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_3d_param_file.py
new file mode 100644
index 000000000..903abeefd
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_3d_param_file.py
@@ -0,0 +1,314 @@
+# Generates a 3D plot of the Earth with the satellites positions
+# https://geopandas.org/en/stable/docs/user_guide/io.html
+import geopandas as gpd
+import numpy as np
+import plotly.graph_objects as go
+from pathlib import Path
+
+from sharc.support.sharc_geom import GeometryConverter
+from sharc.parameters.parameters import Parameters
+from sharc.topology.topology_factory import TopologyFactory
+from sharc.station_factory import StationFactory
+
+
+def plot_back(fig, geoconv):
+ """back half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ lat_vals = np.linspace(-90, 90, 50) # 50 latitude points from -90 to 90 degrees.
+ lon_vals = np.linspace(0, 180, 50) # 50 longitude points for the front half.
+ lon, lat = np.meshgrid(lon_vals, lat_vals) # lon and lat will be 2D arrays.
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=x, y=y, z=z,
+ colorscale=[[0, clor], [1, clor]], # Uniform color scale for a solid color.
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1)
+ )
+
+
+def plot_front(fig, geoconv):
+ """front half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ lat_vals = np.linspace(-90, 90, 50) # 50 latitude points from -90 to 90 degrees.
+ lon_vals = np.linspace(-180, 0, 50) # 50 longitude points for the front half.
+ lon, lat = np.meshgrid(lon_vals, lat_vals) # lon and lat will be 2D arrays.
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=x, y=y, z=z,
+ colorscale=[[0, clor], [1, clor]], # Uniform color scale for a solid color.
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1)
+ )
+
+
+def plot_polygon(poly, geoconv):
+
+ xy_coords = poly.exterior.coords.xy
+ lon = np.array(xy_coords[0])
+ lat = np.array(xy_coords[1])
+
+ # lon = lon * np.pi / 180
+ # lat = lat * np.pi / 180
+
+ # R = EARTH_RADIUS_KM
+ x, y, z = geoconv.convert_lla_to_transformed_cartesian(lat, lon, 0)
+
+ return x, y, z
+
+
+def plot_mult_polygon(mult_poly, geoconv):
+ if mult_poly.geom_type == 'Polygon':
+ return [plot_polygon(mult_poly, geoconv)]
+ elif mult_poly.geom_type == 'MultiPolygon':
+ return [plot_polygon(poly, geoconv) for poly in mult_poly.geoms]
+
+
+def plot_globe_with_borders(opaque_globe: bool, geoconv):
+ # Read the shapefile. Creates a DataFrame object
+ project_root = Path(__file__).resolve().parents[4]
+ countries_borders_shp_file = project_root / "sharc/data/countries/ne_110m_admin_0_countries.shp"
+ gdf = gpd.read_file(countries_borders_shp_file)
+ fig = go.Figure()
+ # fig.update_layout(
+ # scene=dict(
+ # aspectmode="data",
+ # xaxis=dict(showbackground=False),
+ # yaxis=dict(showbackground=False),
+ # zaxis=dict(showbackground=False)
+ # ),
+ # margin=dict(l=0, r=0, b=0, t=0)
+ # )
+ if opaque_globe:
+ plot_front(fig, geoconv)
+ plot_back(fig, geoconv)
+ x_all, y_all, z_all = [], [], []
+
+ for i in gdf.index:
+ # print(gdf.loc[i].NAME) # Call a specific attribute
+
+ polys = gdf.loc[i].geometry # Polygons or MultiPolygons
+
+ if polys.geom_type == 'Polygon':
+ x, y, z = plot_polygon(polys, geoconv)
+ x_all.extend(x)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y)
+ y_all.extend([None])
+ z_all.extend(z)
+ z_all.extend([None])
+
+ elif polys.geom_type == 'MultiPolygon':
+
+ for poly in polys.geoms:
+ x, y, z = plot_polygon(poly, geoconv)
+ x_all.extend(x)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y)
+ y_all.extend([None])
+ z_all.extend(z)
+ z_all.extend([None])
+
+ fig.add_trace(go.Scatter3d(x=x_all, y=y_all, z=z_all, mode='lines',
+ line=dict(color='rgb(0, 0, 0)'), showlegend=False))
+
+ return fig
+
+
+if __name__ == "__main__":
+ geoconv = GeometryConverter()
+ SELECTED_SNAPSHOT_NUMBER = 0
+ OPAQUE_GLOBE = True
+ print(f"Plotting drop {SELECTED_SNAPSHOT_NUMBER}")
+ # even when using the same drop number as in a simulation,
+ # since the random generators are not in the same state, there isn't
+ # a direct relationship between a drop in this plot and a drop in the
+ # simulation loop
+ # NOTE: if you want to plot the actual simulation scenarios for debugging,
+ # you should do so inside the simulation loop
+ print(" (not the same drop number as in simulation)")
+
+ script_dir = Path(__file__).parent
+ param_file = script_dir / "base_input.yaml"
+ # param_file = script_dir / "../input/parameters_mss_d2d_to_imt_cross_border_0km_random_pointing_1beam_dl.yaml"
+ param_file = param_file.resolve()
+ print("File at:")
+ print(f" '{param_file}'")
+
+ parameters = Parameters()
+ parameters.set_file_name(param_file)
+ parameters.read_params()
+
+ geoconv.set_reference(
+ parameters.imt.topology.central_latitude,
+ parameters.imt.topology.central_longitude,
+ parameters.imt.topology.central_altitude,
+ )
+ print(
+ "imt at (lat, lon, alt) = ",
+ (geoconv.ref_lat, geoconv.ref_long, geoconv.ref_alt),
+ )
+
+ import random
+ random.seed(parameters.general.seed)
+
+ secondary_seeds = [None] * parameters.general.num_snapshots
+
+ max_seed = 2**32 - 1
+
+ for index in range(parameters.general.num_snapshots):
+ secondary_seeds[index] = random.randint(1, max_seed)
+
+ seed = secondary_seeds[SELECTED_SNAPSHOT_NUMBER]
+
+ topology = TopologyFactory.createTopology(parameters, geoconv)
+
+ random_number_gen = np.random.RandomState(seed)
+
+ # In case of hotspots, base stations coordinates have to be calculated
+ # on every snapshot. Anyway, let topology decide whether to calculate
+ # or not
+ topology.calculate_coordinates(random_number_gen)
+
+ # Create the base stations (remember that it takes into account the
+ # network load factor)
+ bs = StationFactory.generate_imt_base_stations(
+ parameters.imt,
+ parameters.imt.bs.antenna.array,
+ topology, random_number_gen,
+ )
+
+ # Create the other system (FSS, HAPS, etc...)
+ system = StationFactory.generate_system(
+ parameters, topology, random_number_gen,
+ geoconv
+ )
+
+ # Create IMT user equipments
+ ue = StationFactory.generate_imt_ue(
+ parameters.imt,
+ parameters.imt.ue.antenna.array,
+ topology, random_number_gen,
+ )
+
+ # Plot the globe with satellite positions
+ fig = plot_globe_with_borders(OPAQUE_GLOBE, geoconv)
+
+ polygons_lim = plot_mult_polygon(
+ parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.filter_polygon,
+ geoconv
+ )
+ from functools import reduce
+
+ lim_x, lim_y, lim_z = reduce(
+ lambda acc, it: (list(it[0]) + [None] + acc[0], list(it[1]) + [None] + acc[1], list(it[2]) + [None] + acc[2]),
+ polygons_lim,
+ ([], [], [])
+ )
+
+ fig.add_trace(go.Scatter3d(
+ x=lim_x,
+ y=lim_y,
+ z=lim_z,
+ mode='lines',
+ line=dict(color='rgb(0, 0, 255)'),
+ showlegend=False
+ ))
+
+ # Plot all satellites (red markers)
+ fig.add_trace(go.Scatter3d(
+ x=system.x,
+ y=system.y,
+ z=system.z,
+ mode='markers',
+ marker=dict(size=2, color='red', opacity=0.5),
+ showlegend=False
+ ))
+
+ # Plot visible satellites (green markers)
+ # print(visible_positions['x'][visible_positions['x'] > 0])
+ # print("vis_elevation", vis_elevation)
+ fig.add_trace(go.Scatter3d(
+ x=system.x[system.active],
+ y=system.y[system.active],
+ z=system.z[system.active],
+ mode='markers',
+ marker=dict(size=3, color='green', opacity=0.8),
+ showlegend=False
+ ))
+
+ fig.add_trace(go.Scatter3d(
+ x=ue.x,
+ y=ue.y,
+ z=ue.z,
+ mode='markers',
+ marker=dict(size=4, color='blue', opacity=1.0),
+ showlegend=False
+ ))
+
+ fig.add_trace(go.Scatter3d(
+ x=bs.x,
+ y=bs.y,
+ z=bs.z,
+ mode='markers',
+ marker=dict(size=4, color='black', opacity=1.0),
+ showlegend=False
+ ))
+
+ # Display the plot
+ range = 3e6
+ fig.update_layout(
+ scene=dict(
+ zaxis=dict(
+ range=(-range, range)
+ ),
+ yaxis=dict(
+ range=(-range, range)
+ ),
+ xaxis=dict(
+ range=(-range, range)
+ ),
+ camera=dict(
+ center=dict(x=0, y=0, z=-geoconv.get_translation() / (2 * range)), # Look at Earth's center
+ # eye=eye, # Camera position
+ # center=dict(x=0, y=0, z=0), # Look at Earth's center
+ # up=dict(x=0, y=0, z=1) # Ensure the up direction is correct
+ )
+ )
+ )
+
+ fig.show()
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_results.py b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_results.py
new file mode 100644
index 000000000..9539672b1
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_results.py
@@ -0,0 +1,102 @@
+"""Script to process and plot results for MSS D2D to IMT cross-border campaign."""
+
+import os
+from pathlib import Path
+from sharc.results import Results
+# import plotly.graph_objects as go
+from sharc.post_processor import PostProcessor
+
+post_processor = PostProcessor()
+
+# Add a legend to results in folder that match the pattern
+# This could easily come from a config file
+
+prefixes = ["0km", "157.9km", "213.4km", "268.9km", "324.4km", "379.9km", "border"]
+for prefix in prefixes:
+ if prefix == "border":
+ km = "0km"
+ else:
+ km = prefix
+ post_processor\
+ .add_plot_legend_pattern(
+ dir_name_contains=f"{prefix}_base",
+ legend=f"19 sectors ({km})"
+ ).add_plot_legend_pattern(
+ dir_name_contains=f"{prefix}_activate_random_beam_5p",
+ legend=f"19 sectors, load=1/19 ({km})"
+ ).add_plot_legend_pattern(
+ dir_name_contains=f"{prefix}_activate_random_beam_30p",
+ legend=f"19 sectors, load=30% ({km})"
+ ).add_plot_legend_pattern(
+ dir_name_contains=f"{prefix}_random_pointing_1beam",
+ legend=f"1 sector random pointn ({km})"
+ )
+
+campaign_base_dir = str((Path(__file__) / ".." / "..").resolve())
+
+results_dl = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output_base_dl"), only_latest=True)
+results_ul = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output_base_ul"), only_latest=True)
+# ^: typing.List[Results]
+all_results = [*results_ul, *results_dl]
+
+post_processor.add_results(all_results)
+
+styles = ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"]
+
+
+def linestyle_getter(result: Results):
+ """
+ Returns a line style string based on the prefix found in the result's output directory.
+ """
+ for i in range(len(prefixes)):
+ if prefixes[i] in result.output_directory:
+ return styles[i]
+ return "solid"
+
+
+post_processor.add_results_linestyle_getter(linestyle_getter)
+
+plots = post_processor.generate_cdf_plots_from_results(
+ all_results
+)
+
+post_processor.add_plots(plots)
+
+# Add a protection criteria line:
+protection_criteria = -6
+post_processor\
+ .get_plot_by_results_attribute_name("imt_dl_inr")\
+ .add_vline(protection_criteria, line_dash="dash")
+
+post_processor\
+ .get_plot_by_results_attribute_name("imt_ul_inr")\
+ .add_vline(protection_criteria, line_dash="dash")
+
+# Add a protection criteria line:
+pfd_protection_criteria = -109
+post_processor\
+ .get_plot_by_results_attribute_name("imt_dl_pfd_external_aggregated")\
+ .add_vline(pfd_protection_criteria, line_dash="dash")
+
+
+attributes_to_plot = [
+ # "imt_system_antenna_gain",
+ # "system_imt_antenna_gain",
+ # "sys_to_imt_coupling_loss",
+ # "imt_system_path_loss",
+ "imt_dl_pfd_external",
+ "imt_dl_pfd_external_aggregated",
+ "imt_dl_inr",
+ "imt_ul_inr",
+]
+
+for attr in attributes_to_plot:
+ post_processor.get_plot_by_results_attribute_name(attr).show()
+
+# Ensure the "htmls" directory exists relative to the script directory
+# htmls_dir = Path(__file__).parent / "htmls"
+# htmls_dir.mkdir(exist_ok=True)
+# for attr in attributes_to_plot:
+# post_processor\
+# .get_plot_by_results_attribute_name(attr)\
+# .write_html(htmls_dir / f"{attr}.html")
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_multi_thread.py b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_multi_thread.py
new file mode 100644
index 000000000..1957bdd39
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_multi_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns_mut_thread import run_campaign
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "mss_d2d_to_imt_cross_border"
+
+# Run the campaigns
+# This function will execute the campaign with the given name.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign(name_campaign)
diff --git a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_single_thread.py b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_single_thread.py
new file mode 100644
index 000000000..1f65900eb
--- /dev/null
+++ b/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_single_thread.py
@@ -0,0 +1,10 @@
+from sharc.run_multiple_campaigns import run_campaign_re
+
+# Set the campaign name
+# The name of the campaign to run. This should match the name of the campaign directory.
+name_campaign = "mss_d2d_to_imt_cross_border"
+
+# Run the campaign in single-thread mode
+# This function will execute the campaign with the given name in a single-threaded manner.
+# It will look for the campaign directory under the specified name and start the necessary processes.
+run_campaign_re(name_campaign, r'^parameters_mss_d2d_to_imt_co_channel_system_A.yaml')
diff --git a/sharc/controller.py b/sharc/controller.py
index 064daf9cf..e1d37d64e 100644
--- a/sharc/controller.py
+++ b/sharc/controller.py
@@ -5,39 +5,40 @@
@author: edgar
"""
-from sharc.support.enumerations import Action
+from sharc.support.enumerations import Action
from sharc.model import Model
from thread_simulation import ThreadSimulation
+
class Controller:
"""
- This is the Controller class of the simplified MVC model that is
- implemented in this application. This class should define application
+ This is the Controller class of the simplified MVC model that is
+ implemented in this application. This class should define application
behavior, map user actions to model updates and select view for response.
"""
-
+
def __init__(self):
pass
-
+
def set_model(self, model: Model):
self.model = model
def get_model(self):
return self.model
-
+
def action(self, *args, **kwargs):
"""
- Receives the user action that is captured by view and maps it to the
+ Receives the user action that is captured by view and maps it to the
appropriate action. Currently, the only supported actions are the ones
- that start and stop simulation.
-
+ that start and stop simulation.
+
Parameters
----------
Action: this non-keyworded argument indicates the action to be taken
"""
action = kwargs["action"]
-
+
if action is Action.START_SIMULATION:
self.model.set_param_file(kwargs["param_file"])
self.simulation_thread = ThreadSimulation(self.model)
@@ -46,8 +47,7 @@ def action(self, *args, **kwargs):
self.model.set_param_file(kwargs["param_file"])
self.simulation_thread = ThreadSimulation(self.model)
# call run method directly, without starting a new thread
- self.simulation_thread.run()
+ self.simulation_thread.run()
if action is Action.STOP_SIMULATION:
if self.simulation_thread.is_alive():
self.simulation_thread.stop()
-
\ No newline at end of file
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.VERSION.txt b/sharc/data/countries/ne_110m_admin_0_countries.VERSION.txt
new file mode 100644
index 000000000..ac14c3dfa
--- /dev/null
+++ b/sharc/data/countries/ne_110m_admin_0_countries.VERSION.txt
@@ -0,0 +1 @@
+5.1.1
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.cpg b/sharc/data/countries/ne_110m_admin_0_countries.cpg
new file mode 100644
index 000000000..3ad133c04
--- /dev/null
+++ b/sharc/data/countries/ne_110m_admin_0_countries.cpg
@@ -0,0 +1 @@
+UTF-8
\ No newline at end of file
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.dbf b/sharc/data/countries/ne_110m_admin_0_countries.dbf
new file mode 100644
index 000000000..e0acd0664
Binary files /dev/null and b/sharc/data/countries/ne_110m_admin_0_countries.dbf differ
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.prj b/sharc/data/countries/ne_110m_admin_0_countries.prj
new file mode 100644
index 000000000..b13a71791
--- /dev/null
+++ b/sharc/data/countries/ne_110m_admin_0_countries.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]]
\ No newline at end of file
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.shp b/sharc/data/countries/ne_110m_admin_0_countries.shp
new file mode 100644
index 000000000..9318e45c7
Binary files /dev/null and b/sharc/data/countries/ne_110m_admin_0_countries.shp differ
diff --git a/sharc/data/countries/ne_110m_admin_0_countries.shx b/sharc/data/countries/ne_110m_admin_0_countries.shx
new file mode 100644
index 000000000..c3728e0dd
Binary files /dev/null and b/sharc/data/countries/ne_110m_admin_0_countries.shx differ
diff --git a/sharc/gui/__init__.py b/sharc/gui/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/gui/__init__.py
+++ b/sharc/gui/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/gui/thread_safe_scrolled_text.py b/sharc/gui/thread_safe_scrolled_text.py
index 4ca95521c..305ac431e 100644
--- a/sharc/gui/thread_safe_scrolled_text.py
+++ b/sharc/gui/thread_safe_scrolled_text.py
@@ -8,6 +8,7 @@
import tkinter.scrolledtext
import queue
+
class ThreadSafeScrolledText(tkinter.scrolledtext.ScrolledText):
"""
This is a subclass of ScrolledText that uses the Queue object and make the
@@ -15,7 +16,7 @@ class ThreadSafeScrolledText(tkinter.scrolledtext.ScrolledText):
interface code should be run in the main thread; other threads will write
to the Queue object.
"""
-
+
def __init__(self, master, **options):
"""
Creates the Queue object and starts the update method.
@@ -23,13 +24,13 @@ def __init__(self, master, **options):
tkinter.Text.__init__(self, master, **options)
self.__queue = queue.Queue()
self.__update()
-
+
def write(self, line: str):
"""
Puts in the queue the line to be displayed.
"""
self.__queue.put(line)
-
+
def __update(self):
"""
Periodically checks if queue contains something to be printed. If queue
@@ -38,13 +39,12 @@ def __update(self):
try:
while 1:
line = self.__queue.get_nowait()
- self.config(state = tkinter.NORMAL)
+ self.config(state=tkinter.NORMAL)
self.insert(tkinter.END, str(line))
self.see(tkinter.END)
- self.config(state = tkinter.DISABLED)
+ self.config(state=tkinter.DISABLED)
self.update_idletasks()
except queue.Empty:
pass
# interval is 100 ms
self.after(100, self.__update)
-
\ No newline at end of file
diff --git a/sharc/gui/view.py b/sharc/gui/view.py
index 5de707a21..9534d719f 100644
--- a/sharc/gui/view.py
+++ b/sharc/gui/view.py
@@ -20,6 +20,7 @@
import tkinter.filedialog
import tkinter.scrolledtext
+
class View(tkinter.Tk, Observer):
"""
Implements the graphical user interface. This is a subclass of Observer and
@@ -42,44 +43,56 @@ def initialize(self):
self.__app_icon = tkinter.PhotoImage(file="img/app_icon.gif")
self.tk.call('wm', 'iconphoto', self._w, self.__app_icon)
- self.__frame = tkinter.Frame(self, bg = '#CCCCCC')
+ self.__frame = tkinter.Frame(self, bg='#CCCCCC')
self.__frame.pack(fill='both', expand='yes')
- self.__scrolledtext = ThreadSafeScrolledText(self.__frame,
- wrap=tkinter.WORD, width=80, height=25, bd=5)
+ self.__scrolledtext = ThreadSafeScrolledText(
+ self.__frame,
+ wrap=tkinter.WORD, width=80, height=25, bd=5,
+ )
self.__scrolledtext.grid(column=0, row=1, columnspan=7, sticky='EW')
- self.__start_image = tkinter.PhotoImage(file = "img/start_icon.gif")
- self.__start_button = tkinter.Button(self.__frame, text="START",
+ self.__start_image = tkinter.PhotoImage(file="img/start_icon.gif")
+ self.__start_button = tkinter.Button(
+ self.__frame, text="START",
image=self.__start_image, compound=tkinter.LEFT,
- state=tkinter.NORMAL, command=self.__on_start_button_click)
+ state=tkinter.NORMAL, command=self.__on_start_button_click,
+ )
self.__start_button.bind("", self.__on_start_button_click)
self.__start_button.grid(column=1, row=0, sticky='EW')
self.__stop_image = tkinter.PhotoImage(file="img/stop_icon.gif")
- self.__stop_button = tkinter.Button(self.__frame, text="STOP ",
+ self.__stop_button = tkinter.Button(
+ self.__frame, text="STOP ",
image=self.__stop_image, compound=tkinter.LEFT,
- state=tkinter.DISABLED, command=self.__on_stop_button_click)
+ state=tkinter.DISABLED, command=self.__on_stop_button_click,
+ )
self.__stop_button.bind("", self.__on_stop_button_click)
self.__stop_button.grid(column=2, row=0, sticky='EW')
- self.__results_image = tkinter.PhotoImage(file = "img/results_icon.gif")
- self.__results_button = tkinter.Button(self.__frame, text="RESULTS",
+ self.__results_image = tkinter.PhotoImage(file="img/results_icon.gif")
+ self.__results_button = tkinter.Button(
+ self.__frame, text="RESULTS",
image=self.__results_image, compound=tkinter.LEFT,
- state=tkinter.DISABLED, command=self.__on_results_button_click)
+ state=tkinter.DISABLED, command=self.__on_results_button_click,
+ )
self.__results_button.bind("", self.__on_results_button_click)
self.__results_button.grid(column=3, row=0, sticky='EW')
self.__clear_image = tkinter.PhotoImage(file="img/clear_icon.gif")
- self.__clear_button = tkinter.Button(self.__frame, text="CLEAR",
+ self.__clear_button = tkinter.Button(
+ self.__frame, text="CLEAR",
image=self.__clear_image, compound=tkinter.LEFT,
- state=tkinter.NORMAL, command=self.__on_clear_button_click)
+ state=tkinter.NORMAL, command=self.__on_clear_button_click,
+ )
self.__clear_button.grid(column=4, row=0, sticky='EW')
self.__copy_image = tkinter.PhotoImage(file="img/copy_icon.gif")
- self.__copy_button = tkinter.Button(self.__frame, text="COPY",
+ self.__copy_button = tkinter.Button(
+ self.__frame, text="COPY",
image=self.__copy_image, compound=tkinter.LEFT,
- state=tkinter.NORMAL, command=self.__on_copy_button_click)
+ state=tkinter.NORMAL, command=self.__on_copy_button_click,
+ )
self.__copy_button.grid(column=5, row=0, sticky='EW')
self.grid_columnconfigure(0, weight=1)
@@ -94,24 +107,29 @@ def __on_start_button_click(self, *args):
"""
This method is called when start button is clicked
"""
- default_file = os.path.join(os.getcwd(), "input", "parameters.ini")
+ default_file = os.path.join(os.getcwd(), "input", "parameters.yaml")
default_dir = os.path.join(os.getcwd(), "input")
- param_file = tkinter.filedialog.askopenfilename(title = "Select parameters file",
- initialdir = default_dir,
- initialfile = default_file,
- filetypes = (("Simulation parameters", "*.ini"),
- ("All files", "*.*") ))
+ param_file = tkinter.filedialog.askopenfilename(
+ title="Select parameters file",
+ initialdir=default_dir,
+ initialfile=default_file,
+ filetypes=(
+ ("Simulation parameters", "*.yaml"),
+ ("All files", "*.*"),
+ ),
+ )
if param_file:
- self.__controller.action(action = Action.START_SIMULATION,
- param_file = param_file)
-
+ self.__controller.action(
+ action=Action.START_SIMULATION,
+ param_file=param_file,
+ )
def __on_stop_button_click(self, *args):
"""
This method is called when stop button is clicked
"""
self.__insert_text(__name__, "STOPPED BY USER, FINALIZING SIMULATION")
- self.__controller.action(action = Action.STOP_SIMULATION)
+ self.__controller.action(action=Action.STOP_SIMULATION)
self.__set_state(State.STOPPING)
def __on_results_button_click(self, *args):
@@ -129,40 +147,42 @@ def __on_clear_button_click(self):
"""
This method is called when clear button is clicked
"""
- self.__scrolledtext.config(state = tkinter.NORMAL)
+ self.__scrolledtext.config(state=tkinter.NORMAL)
self.__scrolledtext.delete(1.0, tkinter.END)
- self.__scrolledtext.config(state = tkinter.DISABLED)
+ self.__scrolledtext.config(state=tkinter.DISABLED)
def __on_copy_button_click(self):
"""
This method is called when copy button is clicked
"""
self.clipboard_clear()
- self.__scrolledtext.config(state = tkinter.NORMAL)
+ self.__scrolledtext.config(state=tkinter.NORMAL)
self.clipboard_append(self.__scrolledtext.get(1.0, tkinter.END))
- self.__scrolledtext.config(state = tkinter.DISABLED)
+ self.__scrolledtext.config(state=tkinter.DISABLED)
self.__popup("Copied to clipboard.")
def __plot_results(self, results: Results):
file_extension = ".png"
transparent_figure = False
-
+
for plot in results.plot_list:
- plt.figure(figsize=(8,7), facecolor='w', edgecolor='k')
- plt.plot(plot.x, plot.y, color='#990000', linewidth=2)
+ plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k')
+ plt.plot(plot.x, plot.y, color='#990000', linewidth=2)
plt.title(plot.title)
plt.xlabel(plot.x_label)
plt.ylabel(plot.y_label)
- if not plot.x_lim is None:
+ if plot.x_lim is not None:
plt.xlim(plot.x_lim)
- if not plot.y_lim is None:
- plt.ylim(plot.y_lim)
- #plt.grid()
+ if plot.y_lim is not None:
+ plt.ylim(plot.y_lim)
+ # plt.grid()
plt.tight_layout()
- plt.savefig(os.path.join("output", plot.file_name + file_extension),
- transparent=transparent_figure)
+ plt.savefig(
+ os.path.join("output", plot.file_name + file_extension),
+ transparent=transparent_figure,
+ )
- #plt.show()
+ # plt.show()
self.__popup("Plots successfully created! Check output directory.")
def __insert_text(self, source: str, text: str):
@@ -236,7 +256,7 @@ def notify_observer(self, *args, **kwargs):
"""
if "state" in kwargs:
self.__set_state(kwargs["state"])
- #self.insert_text( __name__, "\n" )
+ # self.insert_text( __name__, "\n" )
if "message" in kwargs:
self.__insert_text(kwargs["source"], kwargs["message"])
if "results" in kwargs:
@@ -262,14 +282,17 @@ def __popup(self, message: str):
empty_top_label = tkinter.Label(top_level, height=1, bg='#FFFFFF')
empty_top_label.pack()
- label = tkinter.Label(top_level, text=message, height=0, width=50,
- bg='#FFFFFF')
+ label = tkinter.Label(
+ top_level, text=message, height=0, width=50,
+ bg='#FFFFFF',
+ )
label.pack()
- button = tkinter.Button(top_level, text=" OK ",
- command=top_level.destroy)
+ button = tkinter.Button(
+ top_level, text=" OK ",
+ command=top_level.destroy,
+ )
button.pack()
empty_botton_label = tkinter.Label(top_level, height=1, bg='#FFFFFF')
empty_botton_label.pack()
-
diff --git a/sharc/gui/view_cli.py b/sharc/gui/view_cli.py
index 5627aadbe..992efe5b9 100644
--- a/sharc/gui/view_cli.py
+++ b/sharc/gui/view_cli.py
@@ -21,12 +21,12 @@ def __init__(self, parent=None):
super().__init__()
self.parent = parent
-
def initialize(self, param_file):
- self.controller.action(action = Action.START_SIMULATION_SINGLE_THREAD,
- param_file = param_file)
-
-
+ self.controller.action(
+ action=Action.START_SIMULATION_SINGLE_THREAD,
+ param_file=param_file,
+ )
+
def set_controller(self, controller: Controller):
"""
Keeps the reference to the controller
@@ -37,7 +37,6 @@ def set_controller(self, controller: Controller):
"""
self.controller = controller
-
def notify_observer(self, *args, **kwargs):
"""
Implements the method from Observer class. See documentation on the
@@ -51,7 +50,6 @@ def notify_observer(self, *args, **kwargs):
if "message" in kwargs:
self.insert_text(kwargs["source"], kwargs["message"])
-
def insert_text(self, source: str, text: str):
"""
This method can be called to display a message on the console. The same
@@ -66,4 +64,3 @@ def insert_text(self, source: str, text: str):
"""
logger = logging.getLogger(source)
logger.info(text)
-
\ No newline at end of file
diff --git a/sharc/how_to_run_campaigns.md b/sharc/how_to_run_campaigns.md
new file mode 100644
index 000000000..03b870eaa
--- /dev/null
+++ b/sharc/how_to_run_campaigns.md
@@ -0,0 +1,42 @@
+
+
+## Setup the campaigns folder
+
+1. Create a folder for your campaign:
+ - Example: `imt_hibs_ras_2600_MHz`
+
+2. Inside the campaign folder, create the following subfolders:
+ - `input` -> This folder is for defining the simulation parameters. For example, look in `campaigns/imt_hibs_ras_2600_MHz/input`.
+ - `output` -> SHARC will use this folder to save the results and plots.
+ - `scripts` -> This folder is used to implement scripts for post-processing.
+
+## Configure your simulation
+
+1. Create a parameter file:
+ - Example: `campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml`.
+
+2. Set the configuration for your study in the parameter file.
+
+3. Set the output folder in the parameter file:
+ ```yaml
+ ###########################################################################
+ # output destination folder - this is relative to the SHARC/sharc directory
+ output_dir = campaigns/imt_hibs_ras_2600_MHz/output/
+ ###########################################################################
+ # output folder prefix
+ output_dir_prefix = output_imt_hibs_ras_2600_MHz_0km
+ ```
+4. You can create multiple simulation parameters:
+ - Check the folder `sharc/campaigns/imt_hibs_ras_2600_MHz/input` for examples.
+
+## Run simulations and view the results
+
+1. **Run simulations:**
+ - In the `scripts` folder, you can create a file to start the simulation. Check the example: `campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py`.
+
+ - If you want to run a single-threaded simulation, check the file: `campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py`.
+
+2. **Generate plots:**
+ - You can create a file to read the data and generate the plots. SHARC has a function called `plot_cdf` to make plotting easy. Check the example: `campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py`.
+"""
+
diff --git a/sharc/img/__init__.py b/sharc/img/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/img/__init__.py
+++ b/sharc/img/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/input/detect_all_ini2yaml.py b/sharc/input/detect_all_ini2yaml.py
new file mode 100644
index 000000000..2c1d762f7
--- /dev/null
+++ b/sharc/input/detect_all_ini2yaml.py
@@ -0,0 +1,96 @@
+"""
+_summary_
+ Script to convert all .ini files found in the current directory and its subdirectories to .yaml,
+ saving them in the same directory.
+ Usage: `python3 script.py`
+ Caveats: currently, it does not parse .ini lists.
+"""
+
+import re
+import os
+
+num_spaces_ident = 4
+
+
+def convert_ini_to_yaml(input_file, output_file):
+
+ print("Reading from file: ", input_file)
+ print("Writing to file: ", output_file)
+
+ input_file_content = open(input_file, "r").readlines()
+
+ with open(output_file, 'w') as output_file:
+ current_ident: int = 0 # Identation to be used in yaml file
+ current_section: str = "" # Section of ini file
+ current_attr_name: str = "" # Attribute name of ini file
+ current_attr_comments: list[str] = []
+ for line in input_file_content:
+ if (line.isspace() or not line):
+ continue
+ line.strip()
+ if (line.startswith("#")):
+ current_attr_comments.append(line)
+ continue
+ elif (line.startswith("[")):
+ # Line is section
+ # go back 1 identation
+ current_ident = max(current_ident - num_spaces_ident, 0)
+ section_name = re.findall(r'\[(.+)\]', line)[0]
+ section_name = section_name.lower()
+ current_section = section_name
+ for comment in current_attr_comments:
+ output_file.write(' ' * current_ident + comment)
+ current_attr_comments = []
+ output_file.write(
+ ' ' * current_ident +
+ f"{current_section}:\n",
+ )
+ current_ident += num_spaces_ident
+ else:
+ # line is attribute
+ try:
+ current_attr_name = re.findall(r"(.+) *=(.+)", line)[0][0]
+ current_attr_value = re.findall(r"(.+) *=(.+)", line)[0][1]
+ if (current_attr_value == 'TRUE' or current_attr_value == 'True'):
+ current_attr_value = 'true'
+ if (current_attr_value == 'FALSE' or current_attr_value == 'False'):
+ current_attr_value = 'false'
+ for comment in current_attr_comments:
+ output_file.write(' ' * current_ident + comment)
+ current_attr_comments = []
+ output_file.write(
+ ' ' * current_ident + f"{current_attr_name} :{current_attr_value}\n",
+ )
+ except IndexError:
+ print(input_file, 'did not match the expected format. Skipping...')
+
+ print(f"Conversion complete: {output_file.name}")
+
+
+if __name__ == "__main__":
+
+ root_dir = os.getcwd() # Use the current working directory as the root
+ # Add your environment folder names here
+ exclude_dirs = {'env', 'venv', '.venv'}
+
+ ini_files = []
+ for root, dirs, files in os.walk(root_dir):
+
+ # Modify dirs in-place to exclude specific directories
+ dirs[:] = [d for d in dirs if d not in exclude_dirs]
+
+ for file in files:
+ if file.endswith('.ini'):
+ ini_files.append(os.path.join(root, file))
+
+ if not ini_files:
+ print("No .ini files found in the current directory or its subdirectories.")
+ exit(1)
+
+ print("Detected .ini files:")
+ for ini_file in ini_files:
+ print(ini_file)
+
+ for ini_file in ini_files:
+ output_file_name = os.path.splitext(ini_file)[0] + '.yaml'
+ convert_ini_to_yaml(ini_file, output_file_name)
diff --git a/sharc/input/ini2yaml_scraper.py b/sharc/input/ini2yaml_scraper.py
new file mode 100644
index 000000000..577ba630e
--- /dev/null
+++ b/sharc/input/ini2yaml_scraper.py
@@ -0,0 +1,78 @@
+"""
+_summary_
+ Script to be run when you need to convert between old .ini parameters and new .yaml parameters.
+ Usage: `python3 -i input-file.ini -o output-file.yaml`
+ Caveats: currently, it does not parse .ini lists.
+"""
+
+if __name__ == "__main__":
+
+ import re
+ from argparse import ArgumentParser
+
+ num_spaces_ident = 4
+
+ parser = ArgumentParser()
+ parser.add_argument(
+ "-i", "--input", dest="input_file",
+ help="Input file. Should be .ini. Absolute Path", required=True,
+ )
+ parser.add_argument(
+ "-o", "--output", dest="output_file",
+ help="Output file. Should be .yaml. Absolute Path", required=True,
+ )
+
+ args = parser.parse_args()
+
+ print("Reading from file: ", args.input_file)
+ print("Writing to file: ", args.output_file)
+
+ input_file_content = open(args.input_file, "r").readlines()
+
+ # if os.path.exists(args.output_file):
+ # response = input(f"File {args.output_file} already exists, are you sure that you want to override it? Y/n")
+ # if(response == "n" or response == "N"):
+ # print("**** Operation Cancelled ****")
+ # exit(1)
+ with open(args.output_file, 'w') as output_file:
+ current_ident: int = 0 # Identation to be used in yaml file
+ current_section: str = "" # Section of ini file
+ current_attr_name: str = "" # Attribute name of ini file
+ current_attr_comments: list[str] = []
+ for line in input_file_content:
+ if (line.isspace() or not line):
+ continue
+ line.strip()
+ if (line.startswith("#")):
+ current_attr_comments.append(line)
+ continue
+ elif (line.startswith("[")):
+ # Line is section
+ # go back 1 identation
+ current_ident = max(current_ident - num_spaces_ident, 0)
+ section_name = re.findall(r'\[(.+)\]', line)[0]
+ section_name = section_name.lower()
+ current_section = section_name
+ for comment in current_attr_comments:
+ output_file.write(' ' * current_ident + comment)
+ current_attr_comments = []
+ output_file.write(
+ ' ' * current_ident +
+ f"{current_section}:\n",
+ )
+ current_ident += num_spaces_ident
+ else:
+ # line is attribute
+ current_attr_name = re.findall(r"(.+) *=(.+)", line)[0][0]
+ current_attr_value = re.findall(r"(.+) *=(.+)", line)[0][1]
+ if (current_attr_value == 'TRUE' or current_attr_value == 'True'):
+ current_attr_value = 'true'
+ if (current_attr_value == 'FALSE' or current_attr_value == 'False'):
+ current_attr_value = 'false'
+ for comment in current_attr_comments:
+ output_file.write(' ' * current_ident + comment)
+ current_attr_comments = []
+ output_file.write(
+ ' ' * current_ident +
+ f"{current_attr_name} :{current_attr_value}\n",
+ )
diff --git a/sharc/input/input_folder.md b/sharc/input/input_folder.md
new file mode 100644
index 000000000..9afd7178e
--- /dev/null
+++ b/sharc/input/input_folder.md
@@ -0,0 +1,3 @@
+# Input folder
+This folder holds reference parameter files used for root level campaigns or parameters used as reference
+for campaign level simulations.
\ No newline at end of file
diff --git a/sharc/input/parameters.ini b/sharc/input/parameters.ini
deleted file mode 100644
index 531d3affa..000000000
--- a/sharc/input/parameters.ini
+++ /dev/null
@@ -1,824 +0,0 @@
-[GENERAL]
-###########################################################################
-# Number of simulation snapshots
-num_snapshots = 10000
-###########################################################################
-# IMT link that will be simulated (DOWNLINK or UPLINK)
-imt_link = UPLINK
-###########################################################################
-# The chosen system for sharing study
-# EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS
-system = EESS_PASSIVE
-###########################################################################
-# Compatibility scenario (co-channel and/or adjacent channel interference)
-enable_cochannel = FALSE
-enable_adjacent_channel = TRUE
-###########################################################################
-# Seed for random number generator
-seed = 101
-###########################################################################
-# if FALSE, then a new output directory is created
-overwrite_output = TRUE
-
-[IMT]
-###########################################################################
-# Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
-# "INDOOR"
-topology = HOTSPOT
-###########################################################################
-# Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies
-wrap_around = FALSE
-###########################################################################
-# Number of clusters in macro cell topology
-num_clusters = 1
-###########################################################################
-# Inter-site distance in macrocell network topology [m]
-intersite_distance = 500
-###########################################################################
-# Minimum 2D separation distance from BS to UE [m]
-minimum_separation_distance_bs_ue = 0
-###########################################################################
-# Defines if IMT service is the interferer or interfered-with service
-# TRUE : IMT suffers interference
-# FALSE : IMT generates interference
-interfered_with = FALSE
-###########################################################################
-# IMT center frequency [MHz]
-frequency = 24350
-###########################################################################
-# IMT bandwidth [MHz]
-bandwidth = 200
-###########################################################################
-# IMT resource block bandwidth [MHz]
-rb_bandwidth = 0.180
-###########################################################################
-# IMT spectrum emission mask. Options are:
-# "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
-# "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
-# TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
-spectral_mask = IMT-2020
-###########################################################################
-# level of spurious emissions [dBm/MHz]
-spurious_emissions = -13
-###########################################################################
-# Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
-# means that 10% of the total bandwidth will be used as guard band: 5% in
-# the lower
-guard_band_ratio = 0.1
-###########################################################################
-# The load probability (or activity factor) models the statistical
-# variation of the network load by defining the number of fully loaded
-# base stations that are simultaneously transmitting
-bs_load_probability = .2
-###########################################################################
-# Conducted power per antenna element [dBm/bandwidth]
-bs_conducted_power = 10
-###########################################################################
-# Base station height [m]
-bs_height = 6
-###########################################################################
-# Base station noise figure [dB]
-bs_noise_figure = 10
-###########################################################################
-# User equipment noise temperature [K]
-bs_noise_temperature = 290
-###########################################################################
-# Base station array ohmic loss [dB]
-bs_ohmic_loss = 3
-###########################################################################
-# Uplink attenuation factor used in link-to-system mapping
-ul_attenuation_factor = 0.4
-###########################################################################
-# Uplink minimum SINR of the code set [dB]
-ul_sinr_min = -10
-###########################################################################
-# Uplink maximum SINR of the code set [dB]
-ul_sinr_max = 22
-###########################################################################
-# Number of UEs that are allocated to each cell within handover margin.
-# Remember that in macrocell network each base station has 3 cells (sectors)
-ue_k = 3
-###########################################################################
-# Multiplication factor that is used to ensure that the sufficient number
-# of UE's will distributed throughout ths system area such that the number
-# of K users is allocated to each cell. Normally, this values varies
-# between 2 and 10 according to the user drop method
-ue_k_m = 1
-###########################################################################
-# Percentage of indoor UE's [%]
-ue_indoor_percent = 5
-###########################################################################
-# Regarding the distribution of active UE's over the cell area, this
-# parameter states how the UEs will be distributed
-# Possible values: UNIFORM : UEs will be uniformly distributed within the
-# whole simulation area. Not applicable to
-# hotspots.
-# ANGLE_AND_DISTANCE : UEs will be distributed following
-# given distributions for angle and
-# distance. In this case, these must be
-# defined later.
-ue_distribution_type = ANGLE_AND_DISTANCE
-###########################################################################
-# Regarding the distribution of active UE's over the cell area, this
-# parameter models the distance between UE's and BS.
-# Possible values: RAYLEIGH, UNIFORM
-ue_distribution_distance = RAYLEIGH
-###########################################################################
-# Regarding the distribution of active UE's over the cell area, this
-# parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
-# Possible values: NORMAL, UNIFORM
-ue_distribution_azimuth = NORMAL
-###########################################################################
-# Power control algorithm
-# ue_tx_power_control = "ON",power control On
-# ue_tx_power_control = "OFF",power control Off
-ue_tx_power_control = ON
-###########################################################################
-# Power per RB used as target value [dBm]
-ue_p_o_pusch = -95
-###########################################################################
-# Alfa is the balancing factor for UEs with bad channel
-# and UEs with good channel
-ue_alpha = 1
-###########################################################################
-# Maximum UE transmit power [dBm]
-ue_p_cmax = 22
-###########################################################################
-# UE power dynamic range [dB]
-# The minimum transmit power of a UE is (ue_p_cmax - ue_dynamic_range)
-ue_power_dynamic_range = 63
-###########################################################################
-# UE height [m]
-ue_height = 1.5
-###########################################################################
-# User equipment noise figure [dB]
-ue_noise_figure = 10
-###########################################################################
-# User equipment feed loss [dB]
-ue_ohmic_loss = 3
-###########################################################################
-# User equipment body loss [dB]
-ue_body_loss = 4
-###########################################################################
-# Downlink attenuation factor used in link-to-system mapping
-dl_attenuation_factor = 0.6
-###########################################################################
-# Downlink minimum SINR of the code set [dB]
-dl_sinr_min = -10
-###########################################################################
-# Downlink maximum SINR of the code set [dB]
-dl_sinr_max = 30
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "CI" (close-in FS reference distance)
-# "UMa" (Urban Macro - 3GPP)
-# "UMi" (Urban Micro - 3GPP)
-# "TVRO-URBAN"
-# "TVRO-SUBURBAN"
-# "ABG" (Alpha-Beta-Gamma)
-channel_model = UMi
-###########################################################################
-# Adjustment factor for LoS probability in UMi path loss model.
-# Original value: 18 (3GPP)
-los_adjustment_factor = 18
-###########################################################################
-# If shadowing should be applied or not
-shadowing = TRUE
-###########################################################################
-# System receive noise temperature [K]
-noise_temperature = 290
-BOLTZMANN_CONSTANT = 1.38064852e-23
-
-[IMT_ANTENNA]
-###########################################################################
-# Defines the antenna model to be used in compatibility studies between
-# IMT and other services in adjacent band
-# Possible values: SINGLE_ELEMENT, BEAMFORMING
-adjacent_antenna_model = SINGLE_ELEMENT
-###########################################################################
-# If normalization of M2101 should be applied for BS
-bs_normalization = FALSE
-###########################################################################
-# If normalization of M2101 should be applied for UE
-ue_normalization = FALSE
-###########################################################################
-# File to be used in the BS beamforming normalization
-# Normalization files can be generated with the
-# antenna/beamforming_normalization/normalize_script.py script
-bs_normalization_file = antenna/beamforming_normalization/bs_norm.npz
-###########################################################################
-# File to be used in the UE beamforming normalization
-# Normalization files can be generated with the
-# antenna/beamforming_normalization/normalize_script.py script
-ue_normalization_file = antenna/beamforming_normalization/ue_norm.npz
-###########################################################################
-# Radiation pattern of each antenna element
-# Possible values: "M2101", "F1336", "FIXED"
-bs_element_pattern = M2101
-ue_element_pattern = M2101
-###########################################################################
-# Minimum array gain for the beamforming antenna [dBi]
-bs_minimum_array_gain = -200
-ue_minimum_array_gain = -200
-###########################################################################
-# mechanical downtilt [degrees]
-# NOTE: consider defining it to 90 degrees in case of indoor simulations
-bs_downtilt = 6
-###########################################################################
-# BS/UE maximum transmit/receive element gain [dBi]
-# default: bs_element_max_g = 5, for M.2101
-# = 15, for M.2292
-# default: ue_element_max_g = 5, for M.2101
-# = -3, for M.2292
-bs_element_max_g = 5
-ue_element_max_g = 5
-###########################################################################
-# BS/UE horizontal 3dB beamwidth of single element [degrees]
-bs_element_phi_3db = 65
-ue_element_phi_3db = 90
-###########################################################################
-# BS/UE vertical 3dB beamwidth of single element [degrees]
-# For F1336: if equal to 0, then beamwidth is calculated automaticaly
-bs_element_theta_3db = 65
-ue_element_theta_3db = 90
-###########################################################################
-# BS/UE number of rows in antenna array
-bs_n_rows = 8
-ue_n_rows = 4
-###########################################################################
-# BS/UE number of columns in antenna array
-bs_n_columns = 8
-ue_n_columns = 4
-###########################################################################
-# BS/UE array horizontal element spacing (d/lambda)
-bs_element_horiz_spacing = 0.5
-ue_element_horiz_spacing = 0.5
-###########################################################################
-# BS/UE array vertical element spacing (d/lambda)
-bs_element_vert_spacing = 0.5
-ue_element_vert_spacing = 0.5
-###########################################################################
-# BS/UE front to back ratio of single element [dB]
-bs_element_am = 30
-ue_element_am = 25
-###########################################################################
-# BS/UE single element vertical sidelobe attenuation [dB]
-bs_element_sla_v = 30
-ue_element_sla_v = 25
-###########################################################################
-# Multiplication factor k that is used to adjust the single-element pattern.
-# According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
-# side lobes when beamforming is assumed in adjacent channel.
-# Original value: 12 (Rec. ITU-R M.2101)
-bs_multiplication_factor = 12
-ue_multiplication_factor = 12
-
-[HOTSPOT]
-###########################################################################
-# Number of hotspots per macro cell (sector)
-num_hotspots_per_cell = 1
-###########################################################################
-# Maximum 2D distance between hotspot and UE [m]
-# This is the hotspot radius
-max_dist_hotspot_ue = 100
-###########################################################################
-# Minimum 2D distance between macro cell base station and hotspot [m]
-min_dist_bs_hotspot = 0
-
-[INDOOR]
-###########################################################################
-# Basic path loss model for indoor topology. Possible values:
-# "FSPL" (free-space path loss),
-# "INH_OFFICE" (3GPP Indoor Hotspot - Office)
-basic_path_loss = "INH_OFFICE"
-###########################################################################
-# Number of rows of buildings in the simulation scenario
-n_rows = 3
-###########################################################################
-# Number of colums of buildings in the simulation scenario
-n_colums = 2
-###########################################################################
-# Number of buildings containing IMT stations. Options:
-# 'ALL': all buildings contain IMT stations.
-# Number of buildings.
-num_imt_buildings = ALL
-###########################################################################
-# Street width (building separation) [m]
-street_width = 30
-###########################################################################
-# Intersite distance [m]
-intersite_distance = 40
-###########################################################################
-# Number of cells per floor
-num_cells = 3
-###########################################################################
-# Number of floors per building
-num_floors = 1
-###########################################################################
-# Percentage of indoor UE's [0, 1]
-ue_indoor_percent = .95
-###########################################################################
-# Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
-building_class = TRADITIONAL
-
-[FSS_SS]
-###########################################################################
-# satellite center frequency [MHz]
-frequency = 43000
-###########################################################################
-# satellite bandwidth [MHz]
-bandwidth = 200
-###########################################################################
-# satellite altitude [m] and latitude [deg]
-altitude = 35780000
-lat_deg = 0
-###########################################################################
-# Elevation angle [deg]
-elevation = 270
-###########################################################################
-# Azimuth angle [deg]
-azimuth = 0
-###########################################################################
-# Peak transmit power spectral density (clear sky) [dBW/Hz]
-tx_power_density = -5
-###########################################################################
-# System receive noise temperature [K]
-noise_temperature = 950
-###########################################################################
-# adjacent channel selectivity (dB)
-adjacent_ch_selectivity = 0
-###########################################################################
-# Satellite peak receive antenna gain [dBi]
-antenna_gain = 46.6
-###########################################################################
-# Antenna pattern of the FSS space station
-# Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI"
-antenna_pattern = FSS_SS
-# IMT parameters relevant to the satellite system
-# altitude of IMT system (in meters)
-# latitude of IMT system (in degrees)
-# difference between longitudes of IMT and satellite system
-# (positive if space-station is to the East of earth-station)
-imt_altitude = 0
-imt_lat_deg = 0
-imt_long_diff_deg = 0
-season = SUMMER
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "SatelliteSimple" (FSPL + 4 + clutter loss)
-# "P619"
-channel_model = P619
-###########################################################################
-# The required near-in-side-lobe level (dB) relative to peak gain
-# according to ITU-R S.672-4
-antenna_l_s = -20
-###########################################################################
-# 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
-antenna_3_dB = 0.65
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-
-[FSS_ES]
-###########################################################################
-# type of FSS-ES location:
-# FIXED - position must be given
-# CELL - random within central cell
-# NETWORK - random within whole network
-# UNIFORM_DIST - uniform distance from cluster centre,
-# between min_dist_to_bs and max_dist_to_bs
-location = UNIFORM_DIST
-###########################################################################
-# x-y coordinates [m] (only if FIXED location is chosen)
-x = 10000
-y = 0
-###########################################################################
-# minimum distance from BSs [m]
-min_dist_to_bs = 10
-###########################################################################
-# maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen)
-max_dist_to_bs = 600
-###########################################################################
-# antenna height [m]
-height = 6
-###########################################################################
-# Elevation angle [deg], minimum and maximum, values
-elevation_min = 48
-elevation_max = 80
-###########################################################################
-# Azimuth angle [deg]
-# either a specific angle or string 'RANDOM'
-azimuth = RANDOM
-###########################################################################
-# center frequency [MHz]
-frequency = 43000
-###########################################################################
-# bandwidth [MHz]
-bandwidth = 6
-###########################################################################
-# System receive noise temperature [K]
-noise_temperature = 950
-###########################################################################
-# adjacent channel selectivity (dB)
-adjacent_ch_selectivity = 0
-###########################################################################
-# Peak transmit power spectral density (clear sky) [dBW/Hz]
-tx_power_density = -68.3
-###########################################################################
-# antenna peak gain [dBi]
-antenna_gain = 32
-###########################################################################
-# Antenna pattern of the FSS Earth station
-# Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI",
-# "Modified ITU-R S.465"
-antenna_pattern = Modified ITU-R S.465
-###########################################################################
-# Diameter of antenna [m]
-diameter = 1.8
-###########################################################################
-# Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model
-antenna_envelope_gain = 0
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "TerrestrialSimple" (FSPL + clutter loss)
-# "P452"
-# "TVRO-URBAN"
-# "TVRO-SUBURBAN"
-# "HDFSS"
-channel_model = P452
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-###########################################################################
-# P452 parameters
-###########################################################################
-# Total air pressure in hPa
-atmospheric_pressure = 935
-###########################################################################
-# Temperature in Kelvin
-air_temperature = 300
-###########################################################################
-#Sea-level surface refractivity (use the map)
-N0 = 352.58
-###########################################################################
-#Average radio-refractive (use the map)
-delta_N = 43.127
-###########################################################################
-#percentage p. Float (0 to 100) or RANDOM
-percentage_p = 0.2
-###########################################################################
-#Distance over land from the transmit and receive antennas to the coast (km)
-Dct = 70
-###########################################################################
-#Distance over land from the transmit and receive antennas to the coast (km)
-Dcr = 70
-###########################################################################
-##Effective height of interfering antenna (m)
-Hte = 20
-###########################################################################
-#Effective height of interfered-with antenna (m)
-Hre = 3
-###########################################################################
-##Latitude of transmitter
-tx_lat = -23.55028
-###########################################################################
-#Latitude of receiver
-rx_lat = -23.17889
-###########################################################################
-#Antenna polarization
-polarization = horizontal
-###########################################################################
-#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
-clutter_loss = TRUE
-###########################################################################
-# HDFSS propagation parameters
-###########################################################################
-# HDFSS position relative to building it is on. Possible values are
-# ROOFTOP and BUILDINGSIDE
-es_position = ROOFTOP
-###########################################################################
-# Enable shadowing loss
-shadow_enabled = TRUE
-###########################################################################
-# Enable building entry loss
-building_loss_enabled = TRUE
-###########################################################################
-# Enable interference from IMT stations at the same building as the HDFSS
-same_building_enabled = FALSE
-###########################################################################
-# Enable diffraction loss
-diffraction_enabled = TRUE
-###########################################################################
-# Building entry loss type applied between BSs and HDFSS ES. Options are:
-# P2109_RANDOM: random probability at P.2109 model, considering elevation
-# P2109_FIXED: fixed probability at P.2109 model, considering elevation.
-# Probability must be specified in bs_building_entry_loss_prob.
-# FIXED_VALUE: fixed value per BS. Value must be specified in
-# bs_building_entry_loss_value.
-bs_building_entry_loss_type = P2109_FIXED
-###########################################################################
-# Probability of building entry loss not exceeded if
-# bs_building_entry_loss_type = P2109_FIXED
-bs_building_entry_loss_prob = 0.75
-###########################################################################
-# Value in dB of building entry loss if
-# bs_building_entry_loss_type = FIXED_VALUE
-bs_building_entry_loss_value = 35
-
-[FS]
-###########################################################################
-# x-y coordinates [m]
-x = 1000
-y = 0
-###########################################################################
-# antenna height [m]
-height = 15
-###########################################################################
-# Elevation angle [deg]
-elevation = -10
-###########################################################################
-# Azimuth angle [deg]
-azimuth = 180
-###########################################################################
-# center frequency [MHz]
-frequency = 27250
-###########################################################################
-# bandwidth [MHz]
-bandwidth = 112
-###########################################################################
-# System receive noise temperature [K]
-noise_temperature = 290
-###########################################################################
-# adjacent channel selectivity (dB)
-adjacent_ch_selectivity = 20
-###########################################################################
-# Peak transmit power spectral density (clear sky) [dBW/Hz]
-tx_power_density = -68.3
-###########################################################################
-# antenna peak gain [dBi]
-antenna_gain = 36.9
-###########################################################################
-# Antenna pattern of the fixed wireless service
-# Possible values: "ITU-R F.699", "OMNI"
-antenna_pattern = ITU-R F.699
-###########################################################################
-# Diameter of antenna [m]
-diameter = 0.3
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "TerrestrialSimple" (FSPL + clutter loss)
-channel_model = FSPL
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-
-
-[HAPS]
-###########################################################################
-# HAPS center frequency [MHz]
-frequency = 27250
-###########################################################################
-# HAPS bandwidth [MHz]
-bandwidth = 200
-###########################################################################
-# HAPS altitude [m] and latitude [deg]
-altitude = 20000
-lat_deg = 0
-###########################################################################
-# Elevation angle [deg]
-elevation = 270
-###########################################################################
-# Azimuth angle [deg]
-azimuth = 0
-###########################################################################
-# EIRP spectral density [dBW/MHz]
-eirp_density = 4.4
-###########################################################################
-# HAPS peak antenna gain [dBi]
-antenna_gain = 28.1
-###########################################################################
-# Adjacent channel selectivity [dB]
-acs = 30
-###########################################################################
-# Antenna pattern of the HAPS (airbone) station
-# Possible values: "ITU-R F.1891", "OMNI"
-antenna_pattern = ITU-R F.1891
-# IMT parameters relevant to the HAPS system
-# altitude of IMT system (in meters)
-# latitude of IMT system (in degrees)
-# difference between longitudes of IMT and satellite system
-# (positive if space-station is to the East of earth-station)
-imt_altitude = 0
-imt_lat_deg = 0
-imt_long_diff_deg = 0
-season = SUMMER
-
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "SatelliteSimple" (FSPL + 4 + clutter loss)
-# "P619"
-channel_model = P619
-
-###########################################################################
-# Near side-lobe level (dB) relative to the peak gain required by the system
-# design, and has a maximum value of โ25 dB
-antenna_l_n = -25
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-
-[RNS]
-###########################################################################
-# x-y coordinates [m]
-x = 660
-y = -370
-###########################################################################
-# altitude [m]
-altitude = 150
-###########################################################################
-# center frequency [MHz]
-frequency = 32000
-###########################################################################
-# bandwidth [MHz]
-bandwidth = 60
-###########################################################################
-# System receive noise temperature [K]
-noise_temperature = 1154
-###########################################################################
-# Peak transmit power spectral density (clear sky) [dBW/Hz]
-tx_power_density = -70.79
-###########################################################################
-# antenna peak gain [dBi]
-antenna_gain = 30
-###########################################################################
-# Adjacent channel selectivity [dB]
-acs = 30
-###########################################################################
-# Antenna pattern of the fixed wireless service
-# Possible values: "ITU-R M.1466", "OMNI"
-antenna_pattern = ITU-R M.1466
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "SatelliteSimple" (FSPL + 4 dB + clutter loss)
-# "P619"
-channel_model = P619
-###########################################################################
-# Specific parameters for P619
-season = SUMMER
-imt_altitude = 0
-imt_lat_deg = 0
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-
-
-[RAS]
-###########################################################################
-# x-y coordinates [m]
-x = 81000
-y = 0
-###########################################################################
-# antenna height [m]
-height = 15
-###########################################################################
-# Elevation angle [deg]
-elevation = 45
-###########################################################################
-# Azimuth angle [deg]
-azimuth = -90
-###########################################################################
-# center frequency [MHz]
-frequency = 43000
-###########################################################################
-# bandwidth [MHz]
-bandwidth = 1000
-###########################################################################
-# Antenna noise temperature [K]
-antenna_noise_temperature = 25
-###########################################################################
-# Receiver noise temperature [K]
-receiver_noise_temperature = 65
-###########################################################################
-# adjacent channel selectivity (dB)
-adjacent_ch_selectivity = 20
-###########################################################################
-# Antenna efficiency
-antenna_efficiency = 1
-###########################################################################
-# Antenna pattern of the FSS Earth station
-# Possible values: "ITU-R SA.509", "OMNI"
-antenna_pattern = ITU-R SA.509
-###########################################################################
-# Antenna gain for "OMNI" pattern
-antenna_gain = 0
-###########################################################################
-# Diameter of antenna [m]
-diameter = 15
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
-SPEED_OF_LIGHT = 299792458
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "TerrestrialSimple" (FSPL + clutter loss)
-# "P452"
-channel_model = P452
-###########################################################################
-# P452 parameters
-###########################################################################
-# Total air pressure in hPa
-atmospheric_pressure = 935
-###########################################################################
-# Temperature in Kelvin
-air_temperature = 300
-###########################################################################
-#Sea-level surface refractivity (use the map)
-N0 = 352.58
-###########################################################################
-#Average radio-refractive (use the map)
-delta_N = 43.127
-###########################################################################
-#percentage p. Float (0 to 100) or RANDOM
-percentage_p = 0.2
-###########################################################################
-#Distance over land from the transmit and receive antennas to the coast (km)
-Dct = 70
-###########################################################################
-#Distance over land from the transmit and receive antennas to the coast (km)
-Dcr = 70
-###########################################################################
-##Effective height of interfering antenna (m)
-Hte = 20
-###########################################################################
-#Effective height of interfered-with antenna (m)
-Hre = 3
-###########################################################################
-##Latitude of transmitter
-tx_lat = -23.55028
-###########################################################################
-#Latitude of receiver
-rx_lat = -23.17889
-###########################################################################
-#Antenna polarization
-polarization = horizontal
-###########################################################################
-#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
-clutter_loss = TRUE
-
-[EESS_PASSIVE]
-###########################################################################
-# sensor center frequency [MHz]
-frequency = 23900
-###########################################################################
-# sensor bandwidth [MHz]
-bandwidth = 200
-###########################################################################
-# Off-nadir pointing angle [deg]
-nadir_angle = 46.6
-###########################################################################
-# sensor altitude [m]
-altitude = 828000
-###########################################################################
-# Antenna pattern of the sensor
-# Possible values: "ITU-R RS.1813"
-# "ITU-R RS.1861 9a"
-# "ITU-R RS.1861 9b"
-# "ITU-R RS.1861 9c"
-# "OMNI"
-antenna_pattern = ITU-R RS.1813
-# Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
-antenna_efficiency = 0.6
-# Antenna diameter for ITU-R RS.1813 [m]
-antenna_diameter = 2.2
-###########################################################################
-# receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
-antenna_gain = 52
-###########################################################################
-# Channel parameters
-# channel model, possible values are "FSPL" (free-space path loss),
-# "P619"
-channel_model = FSPL
-# Relevant IMT parameters which apply for ITU-R P.619
-# altitude of IMT system (in meters)
-# latitude of IMT system (in degrees)
-# season of the year: "SUMMER", "WINTER"
-imt_altitude = 20
-imt_lat_deg = -22.9
-season = SUMMER
-###########################################################################
-# Constants
-BOLTZMANN_CONSTANT = 1.38064852e-23
-EARTH_RADIUS = 6371000
diff --git a/sharc/input/parameters.yaml b/sharc/input/parameters.yaml
new file mode 100644
index 000000000..15af8b19e
--- /dev/null
+++ b/sharc/input/parameters.yaml
@@ -0,0 +1,935 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots: 100
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link: DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # SINGLE_EARTH_STATION, SINGLE_SPACE_STATION
+ system: SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel: FALSE
+ enable_adjacent_channel: TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed: 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output: TRUE
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue: 1.3
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE: IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with: FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency: 24360
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth: 200.5
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth: 0.181
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask: 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions: -13.1
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio: 0.14
+ ###########################################################################
+ # Adjacent Interference model used when IMT is victim. Possible values are ACIR or SPECTRAL_MASK.
+ # When using ACIR the interfence power from the other system is calculted by using the ACLR and ACS.
+ # Whith the SPECTRAL_MASK the the interference power from the other system is calculated from it's spectral mask.
+ adjacent_interf_model: ACIR
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ ###########################################################################
+ # This parameter is only used when other system has satellite that needs
+ # a reference point
+ # The latitude position of center of topology [deg]
+ central_latitude: -15.7801 # Brasรญlia
+ ###########################################################################
+ # This parameter is only used when other system has satellite that needs
+ # a reference point
+ # The longitude position of center of topology [deg]
+ central_longitude: -47.9292 # Brasรญlia
+ ###########################################################################
+ # This parameter is only used when other system has satellite that needs
+ # a reference point
+ # The altitude of center of topology [m]
+ central_altitude: 1200 # Brasรญlia
+
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: INDOOR
+ ###########################################################################
+ # Macrocell Topology parameters. Relevant when imt.topology.type == "MACROCELL"
+ macrocell:
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 543
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 543
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 2
+
+ ###########################################################################
+ # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT"
+ hotspot:
+ ###########################################################################
+ # Inter-site distance in hotspot network topology [m]
+ intersite_distance: 321
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: 99.9
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: 1.2
+ ###########################################################################
+ # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR"
+ indoor:
+ ###########################################################################
+ # Basic path loss model for indoor topology. Possible values:
+ # "FSPL" (free-space path loss),
+ # "INH_OFFICE" (3GPP Indoor Hotspot - Office)
+ basic_path_loss: FSPL
+ ###########################################################################
+ # Number of rows of buildings in the simulation scenario
+ n_rows: 3
+ ###########################################################################
+ # Number of colums of buildings in the simulation scenario
+ n_colums: 2
+ ###########################################################################
+ # Number of buildings containing IMT stations. Options:
+ # 'ALL': all buildings contain IMT stations.
+ # Number of buildings.
+ num_imt_buildings: 2
+ ###########################################################################
+ # Street width (building separation) [m]
+ street_width: 30.1
+ ###########################################################################
+ # Intersite distance [m]
+ intersite_distance: 40.1
+ ###########################################################################
+ # Number of cells per floor
+ num_cells: 3
+ ###########################################################################
+ # Number of floors per building
+ num_floors: 1
+ ###########################################################################
+ # Percentage of indoor UE's [0, 1]
+ ue_indoor_percent: .95
+ ###########################################################################
+ # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
+ building_class: THERMALLY_EFFICIENT
+ ###########################################################################
+ # NTN Topology Parameters
+ ntn:
+ ###########################################################################
+ # NTN cell radius or intersite distance in network topology [m]
+ # @important: You can set only one of cell_radius or intersite_distance
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ # NOTE: note that intersite distance has a different geometric meaning in ntn
+ cell_radius: 123
+ # intersite_distance: 155884
+ ###########################################################################
+ # NTN space station azimuth [degree]
+ bs_azimuth: 45
+ ###########################################################################
+ # NTN space station elevation [degree]
+ bs_elevation: 45
+ ###########################################################################
+ # number of sectors [degree]
+ num_sectors: 19
+ ###########################################################################
+ # Conducted power per element [dBm/bandwidth]
+ bs_conducted_power: 37
+ ###########################################################################
+ # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2
+ bs_backoff_power: 3
+ ###########################################################################
+ # NTN Antenna configuration
+ bs_n_rows_layer1 : 2
+ bs_n_columns_layer1: 2
+ bs_n_rows_layer2 : 4
+ bs_n_columns_layer2: 2
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model: BEAMFORMING
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability: .2
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power: 11.1
+ ###########################################################################
+ # Base station height [m]
+ height: 6.1
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure: 10.1
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss: 3.1
+ # Base Station Antenna parameters:
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization: FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-60., 60.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [90., 100.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt: 6
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Subarray for IMT as defined in R23-WP5D-C-0413, Annex 4.2
+ # Single column sub array
+ subarray:
+ # NOTE: if subarray is enabled, element definition will mostly come from
+ # the above definitions
+ is_enabled: false
+ # Rows per subarray
+ n_rows: 3
+ # Sub array element spacing (d/lambda).
+ element_vert_spacing: 0.7
+ # Sub array eletrical downtilt [deg]
+ eletrical_downtilt: 3.0
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k: 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m: 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent: 5
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type: ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM, SQRT(UNIFORM)
+ distribution_distance: UNIFORM
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth: NORMAL
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control: ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch: -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha: 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax: 22
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range: 63
+ ###########################################################################
+ # UE height [m]
+ height: 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure: 10
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss: 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss: 4
+ ###########################################################################
+ # Adjacent Channel Selectivity in dB
+ adjacent_ch_selectivity: 33
+ ###########################################################################
+ antenna:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization: FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file: antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern: F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain: -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g: 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db: 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db: 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows: 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns: 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing: 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing: 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am: 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v: 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor: 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max: 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor: 0.6
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min: -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max: 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model: UMa
+ season: SUMMER
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor: 18
+ ###########################################################################
+ # If shadowing should be applied or not.
+ # Used in propagation UMi, UMa, ABG, when topology == indoor and any
+ shadowing: FALSE
+ ###########################################################################
+ # receiver noise temperature [K]
+ noise_temperature: 290
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0
+ # High for clutter. According to 2018 it can be "low", "mid" or "high"
+ mean_clutter_height: "high"
+ # Percentage of stations below rooftop (To apply clutter)
+ below_rooftop: 100
+
+single_earth_station:
+ ###########################################################################
+ # Sensor center frequency [MHz]
+ frequency: 1
+ ###########################################################################
+ # Sensor bandwidth [MHz]
+ bandwidth: 1
+ ###########################################################################
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: 0.0
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 1
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: -65
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 1.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 1
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ ###########################################################################
+ # Type of azimuth. May be "UNIFORM_DIST", "FIXED"
+ type: FIXED
+ ###########################################################################
+ # Value of azimuth when type == "FIXED" [deg]
+ fixed: 0
+ ###########################################################################
+ # Limits of random uniform distribution when type == "UNIFORM_DIST"
+ uniform_dist:
+ ###########################################################################
+ # uniform distribution lower bound [deg]
+ min: -180
+ ###########################################################################
+ # uniform distribution upper bound [deg]
+ max: 180
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ # Type of elevation. May be "UNIFORM_DIST", "FIXED"
+ type: FIXED
+ # Value of elevation when type == "FIXED" [deg]
+ fixed: 60
+ # Limits of random uniform distribution when type == "UNIFORM_DIST"
+ uniform_dist:
+ # uniform distribution lower bound [deg]
+ min: 30
+ # uniform distribution upper bound [deg]
+ max: 65
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ # choose way to set location. May be "CELL", "NETWORK", "UNIFORM_DIST" or "FIXED"
+ # location.type == CELL means that the ES will be distributed randomly,
+ # but always in the central cell
+ # location.type == NETWORK means that the ES will be distributed randomly,
+ # in a random cell
+ # location.type == UNIFORM_DIST means that the ES will be distributed randomly,
+ # in a ring shaped section with delimited outer and inner radius
+ # location.type == FIXED means that the ES will be always at SAME fixed location
+ type: CELL
+ ###########################################################################
+ # Value of position (x,y) when type == "FIXED" [(m, m)]
+ fixed:
+ x: 10
+ y: 100
+ cell:
+ ###########################################################################
+ # Minimum distance to IMT BS when location.type == CELL [m]
+ min_dist_to_bs: 100
+ network:
+ ###########################################################################
+ # Minimum distance to IMT BS when location.type == NETWORK [m]
+ min_dist_to_bs: 150
+ uniform_dist:
+ ###########################################################################
+ # Ring inner radius [m]
+ min_dist_to_center: 101
+ ###########################################################################
+ # Ring outer radius [m]
+ max_dist_to_center: 102
+ antenna:
+ ###########################################################################
+ # Choose the antenna pattern. Can be one of:
+ # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855",
+ # "ITU-R Reg. RR. Appendice 7 Annex 3"
+ pattern: ITU-R S.465
+ ###########################################################################
+ # Earth station peak receive antenna gain [dBi]
+ gain: 28
+ itu_r_f_699:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_465:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_580:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_1855:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_reg_rr_a7_3:
+ ###########################################################################
+ # Antenna diameter [m]
+ # if no diameter is passed, a diameter will be assumed according to document
+ diameter: 1.1
+ itu_r_s_465_modified:
+ ###########################################################################
+ # Antenna envelope gain [dBi]
+ envelope_gain: -4
+ ###########################################################################
+ # Selected channel model
+ # Possible values are "P619", "P452", "TerrestrialSimple"
+ channel_model: "P619"
+
+ ###########################################################################
+ # Parameters for P619 channel model
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 10
+ # High for clutter. According to 2018 it can be "low", "mid" or "high"
+ mean_clutter_height: "high"
+ # Percentage of stations below rooftop (To apply clutter)
+ below_rooftop: 100
+ ###########################################################################
+ # season - season of the year.
+ # Only actually useful for P619, but we can't put it inside param_p619 without changing more code
+ season: SUMMER
+
+ ###########################################################################
+ # Parameters for P452 channel model
+ param_p452:
+ ###########################################################################
+ # Total air pressure [hPa]
+ atmospheric_pressure: 1
+ ###########################################################################
+ # Temperature [K]
+ air_temperature: 2
+ ###########################################################################
+ # Sea-level surface refractivity (use the map)
+ N0: 3
+ ###########################################################################
+ # Average radio-refractive (use the map)
+ delta_N: 4
+ ###########################################################################
+ # percentage p. Float (0 to 100) or RANDOM
+ percentage_p: 5
+ ###########################################################################
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dct: 6
+ ###########################################################################
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dcr: 7
+ ###########################################################################
+ # Effective height of interfering antenna (m)
+ Hte: 8
+ ###########################################################################
+ # Effective height of interfered-with antenna (m)
+ Hre: 9
+ ###########################################################################
+ # Latitude of transmitter
+ tx_lat: 10
+ ###########################################################################
+ # Latitude of receiver
+ rx_lat: 11
+ ###########################################################################
+ # Antenna polarization. Possible values are "horizontal", "vertical"
+ polarization: horizontal
+ ###########################################################################
+ # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
+ clutter_loss: TRUE
+ # Determine if clutter is applied to "one-end" or "both-ends"
+ clutter_type: "one-end"
+
+ # HDFSS propagation parameters
+ param_hdfss:
+ ###########################################################################
+ # HDFSS position relative to building it is on. Possible values are
+ # ROOFTOP and BUILDINGSIDE
+ es_position: BUILDINGSIDE
+ ###########################################################################
+ # Enable shadowing loss
+ shadow_enabled: FALSE
+ ###########################################################################
+ # Enable building entry loss
+ building_loss_enabled: FALSE
+ ###########################################################################
+ # Enable interference from IMT stations at the same building as the HDFSS
+ same_building_enabled: TRUE
+ ###########################################################################
+ # Enable diffraction loss
+ diffraction_enabled: FALSE
+ ###########################################################################
+ # Building entry loss type applied between BSs and HDFSS ES. Options are:
+ # P2109_RANDOM: random probability at P.2109 model, considering elevation
+ # P2109_FIXED: fixed probability at P.2109 model, considering elevation.
+ # Probability must be specified in bs_building_entry_loss_prob.
+ # FIXED_VALUE: fixed value per BS. Value must be specified in
+ # bs_building_entry_loss_value.
+ bs_building_entry_loss_type: FIXED_VALUE
+ ###########################################################################
+ # Probability of building entry loss not exceeded if
+ # bs_building_entry_loss_type = P2109_FIXED
+ bs_building_entry_loss_prob: 0.19
+ ###########################################################################
+ # Value in dB of building entry loss if
+ # bs_building_entry_loss_type = FIXED_VALUE
+ bs_building_entry_loss_value: 35
+
+ ###########################################################################
+ # HDFSS position relative to building it is on. Possible values are
+ # ROOFTOP and BUILDINGSIDE
+ es_position: BUILDINGSIDE
+ ###########################################################################
+ # Enable shadowing loss
+ shadow_enabled: TRUE
+ ###########################################################################
+ # Enable building entry loss
+ building_loss_enabled: TRUE
+ ###########################################################################
+ # Enable interference from IMT stations at the same building as the HDFSS
+ same_building_enabled: FALSE
+ ###########################################################################
+ # Enable diffraction loss
+ diffraction_enabled: FALSE
+ ###########################################################################
+ # Building entry loss type applied between BSs and HDFSS ES. Options are:
+ # P2109_RANDOM: random probability at P.2109 model, considering elevation
+ # P2109_FIXED: fixed probability at P.2109 model, considering elevation.
+ # Probability must be specified in bs_building_entry_loss_prob.
+ # FIXED_VALUE: fixed value per BS. Value must be specified in
+ # bs_building_entry_loss_value.
+ bs_building_entry_loss_type: P2109_FIXED
+ ###########################################################################
+ # Probability of building entry loss not exceeded if
+ # bs_building_entry_loss_type = P2109_FIXED
+ bs_building_entry_loss_prob: 0.75
+ ###########################################################################
+ # Value in dB of building entry loss if
+ # bs_building_entry_loss_type = FIXED_VALUE
+ bs_building_entry_loss_value: 35
+single_space_station:
+ ###########################################################################
+ # Sensor center frequency [MHz]
+ frequency: 1
+ ###########################################################################
+ # Sensor bandwidth [MHz]
+ bandwidth: 1
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 1
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: 1
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 1
+ geometry:
+ ###########################################################################
+ # Satellite altitude [meters]
+ altitude: 1
+ ###########################################################################
+ # earth station altitude [meters]
+ es_altitude: 1
+ ###########################################################################
+ # earth station lat long [deg]
+ es_lat_deg: 1
+ ###########################################################################
+ # earth station lat long [deg]
+ es_long_deg: 1
+ ###########################################################################
+ # Antenna azimuth angle [degrees]
+ azimuth:
+ ###########################################################################
+ # Type of azimuth. May be "FIXED" or "POINTING_AT_IMT"
+ type: POINTING_AT_IMT
+ ###########################################################################
+ # Arbitrary value of azimuth when type == "FIXED" [deg]
+ fixed: 0
+ ###########################################################################
+ # Antenna elevation angle [degrees]
+ elevation:
+ ###########################################################################
+ # Type of elevation. May be "FIXED" or "POINTING_AT_IMT"
+ type: POINTING_AT_IMT
+ ###########################################################################
+ # Arbitrary value of elevation when type == "FIXED" [deg]
+ fixed: -90
+ ###########################################################################
+ # Station location:
+ location:
+ ###########################################################################
+ # type may be "FIXED"
+ # location.type == FIXED means that the SS will be always at SAME fixed location
+ type: FIXED
+ ###########################################################################
+ # Value of position (lat, long) when type == "FIXED" [(deg, deg)]
+ fixed:
+ lat_deg: 0
+ long_deg: 0
+ antenna:
+ ###########################################################################
+ # Choose the antenna pattern. Can be one of:
+ # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855",
+ # "ITU-R Reg. RR. Appendice 7 Annex 3"
+ pattern: ITU-R S.465
+ ###########################################################################
+ # Earth station peak receive antenna gain [dBi]
+ gain: 28
+ itu_r_f_699:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1
+ itu_r_s_465:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1
+ itu_r_s_580:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1
+ itu_r_s_1855:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1
+ itu_r_s_465_modified:
+ ###########################################################################
+ # Antenna envelope gain [dBi]
+ envelope_gain: 1
+ itu_reg_rr_a7_3:
+ ###########################################################################
+ # Antenna diameter [m]
+ # if no diameter is passed, a diameter will be assumed according to document
+ diameter: 1
+
+ ###########################################################################
+ # Selected channel model. May be "FSPL" or "P619"
+ channel_model: "P619"
+
+ ###########################################################################
+ # season - season of the year.
+ # Useful for P.619
+ season: SUMMER
+
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2170.0
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 19000
+ # Satellite power density in dBW/Hz
+ tx_power_density: -30
+ # Satellite Tx max Gain in dBi
+ antenna_gain: 30.0
+ # Number of sectors
+ num_sectors: 19
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ # Radius of the antenna's circular aperture in meters
+ antenna_diamter: 1.0
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ antenna_l_s: -6.75
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB_bw: 4.4127
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: P619
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 0
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 0
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.75
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
+ # High for clutter. According to 2018 it can be "low", "mid" or "high"
+ mean_clutter_height: "high"
+ # Percentage of stations below rooftop (To apply clutter)
+ below_rooftop: 100
+ # Orbit parameters
+ orbit:
+ # Number of planes
+ n_planes: 20
+ # Inclination in degrees
+ inclination_deg: 54.5
+ # Perigee in km
+ perigee_alt_km: 525.0
+ # Apogee in km
+ apogee_alt_km: 525.0
+ # Number of satellites per plane
+ sats_per_plane: 32
+ # Longitude of the first ascending node in degrees
+ long_asc_deg: 18.0
+ # Phasing in degrees
+ phasing_deg: 3.9
diff --git a/sharc/main.py b/sharc/main.py
index 5e610222c..299a6105c 100644
--- a/sharc/main.py
+++ b/sharc/main.py
@@ -5,28 +5,28 @@
@author: edgar
"""
+from sharc.support.logging import Logging
+from sharc.controller import Controller
+from sharc.gui.view import View
+from sharc.model import Model
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
-from sharc.model import Model
-from sharc.gui.view import View
-from sharc.controller import Controller
-from sharc.support.logging import Logging
+
def main():
Logging.setup_logging()
-
+
model = Model()
view = View()
controller = Controller()
-
+
view.set_controller(controller)
controller.set_model(model)
model.add_observer(view)
-
+
view.mainloop()
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/sharc/main_cli.py b/sharc/main_cli.py
index cfe9241b1..86c99afae 100644
--- a/sharc/main_cli.py
+++ b/sharc/main_cli.py
@@ -5,19 +5,19 @@
@author: edgar
"""
-import sys, getopt
+from sharc.support.logging import Logging
+from sharc.controller import Controller
+from sharc.gui.view_cli import ViewCli
+from sharc.model import Model
+import sys
+import getopt
import os
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
-
-from sharc.model import Model
-from sharc.gui.view_cli import ViewCli
-from sharc.controller import Controller
-from sharc.support.logging import Logging
def main(argv):
print("Welcome to SHARC!\n")
-
+
param_file = ''
try:
@@ -27,7 +27,7 @@ def main(argv):
sys.exit(2)
if not opts:
- param_file = os.path.join(os.getcwd(), "input", "parameters.ini")
+ param_file = os.path.join(os.getcwd(), "input", "parameters.yaml")
else:
for opt, arg in opts:
if opt == "-h":
@@ -35,20 +35,19 @@ def main(argv):
sys.exit()
elif opt == "-p":
param_file = param_file = os.path.join(os.getcwd(), arg)
-
+
Logging.setup_logging()
-
+
model = Model()
view_cli = ViewCli()
controller = Controller()
-
+
view_cli.set_controller(controller)
controller.set_model(model)
model.add_observer(view_cli)
-
- view_cli.initialize(param_file)
+ view_cli.initialize(param_file)
if __name__ == "__main__":
- main(sys.argv[1:])
\ No newline at end of file
+ main(sys.argv[1:])
diff --git a/sharc/mask/spectral_mask.py b/sharc/mask/spectral_mask.py
index 3e74a055b..f1355ab2c 100644
--- a/sharc/mask/spectral_mask.py
+++ b/sharc/mask/spectral_mask.py
@@ -8,18 +8,24 @@
from abc import ABC, abstractmethod
import numpy as np
+
class SpectralMask(ABC):
+ def __init__(self) -> None:
+ self.mask_dbm = None
+ self.freq_lim = None
+ self.p_tx = None
+
@abstractmethod
- def set_mask(self, p_tx = 0):
+ def set_mask(self, p_tx=0):
pass
- def power_calc(self,center_f: float, band: float):
+ def power_calc(self, center_f: float, band: float):
"""
- Calculates out-of-band power in the given band. It does that by
+ Calculates out-of-band power in the given band. It does that by
dividing the band into the rectangular sections defined by the spectral
mask and adding up the area of all the rectangles.
-
+
Parameters:
center_f (float): center frequency of band in which out-of-band
power is to be calculated
@@ -27,17 +33,21 @@ def power_calc(self,center_f: float, band: float):
be calculated
"""
# Limit delta f: edges of band
- df_min = center_f - band/2
- df_max = center_f + band/2
+ df_min = center_f - band / 2
+ df_max = center_f + band / 2
# Power in mW
- power_oob = 0 # Out-of-band power
+ power_oob = 0 # Out-of-band power
# Included delta f values: values of spectral mask delta f break limist
# which are contained within the band
- inc_df = np.where(np.logical_and(self.freq_lim > df_min,
- self.freq_lim < df_max))[0]
-
+ inc_df = np.where(
+ np.logical_and(
+ self.freq_lim > df_min,
+ self.freq_lim < df_max,
+ ),
+ )[0]
+
# If no break limits are within band: the band does not need to be
# divided into rectangles
if len(inc_df) == 0:
@@ -45,33 +55,34 @@ def power_calc(self,center_f: float, band: float):
# Define what is the power emission level at that frequency
msk = self.mask_dbm[np.where(self.freq_lim >= df_max)]
# If df_max is below smallest break limit
- if len(msk) == 0: msk = np.array([self.mask_dbm[-1]])
+ if len(msk) == 0:
+ msk = np.array([self.mask_dbm[-1]])
# Turn it into scalar
pwr_lvl = msk[0]
- if pwr_lvl != self.p_tx: power_oob += band*np.power(10,(pwr_lvl/10))
+ if pwr_lvl != self.p_tx:
+ power_oob += band * np.power(10, (pwr_lvl / 10))
# If one or more break limitas are within band
else:
-
+
# Lower and upper power emission levels
pwr_lvl_1 = self.mask_dbm[inc_df[0]]
pwr_lvl_2 = self.mask_dbm[inc_df[-1] + 1]
# Upper and lower rectangles
if pwr_lvl_1 != self.p_tx:
- power_oob += (self.freq_lim[inc_df[0]] - df_min)*\
- np.power(10,(pwr_lvl_1/10))
+ power_oob += (self.freq_lim[inc_df[0]] - df_min) *\
+ np.power(10, (pwr_lvl_1 / 10))
if pwr_lvl_2 != self.p_tx:
- power_oob += (df_max - self.freq_lim[inc_df[-1]])*\
- np.power(10,(pwr_lvl_2/10))
+ power_oob += (df_max - self.freq_lim[inc_df[-1]]) *\
+ np.power(10, (pwr_lvl_2 / 10))
# Middle rectangles
for df in inc_df[0:-1]:
pwr_lvl = self.mask_dbm[df + 1]
if pwr_lvl != self.p_tx:
- power_oob += (self.freq_lim[df + 1] - self.freq_lim[df])*\
- np.power(10,(pwr_lvl/10))
-
- return 10*np.log10(power_oob)
+ power_oob += (self.freq_lim[df + 1] - self.freq_lim[df]) *\
+ np.power(10, (pwr_lvl / 10))
+ return 10 * np.log10(power_oob)
diff --git a/sharc/mask/spectral_mask_3gpp.py b/sharc/mask/spectral_mask_3gpp.py
index c03d55527..c52d09502 100644
--- a/sharc/mask/spectral_mask_3gpp.py
+++ b/sharc/mask/spectral_mask_3gpp.py
@@ -11,22 +11,25 @@
import numpy as np
import sys
+
class SpectralMask3Gpp(SpectralMask):
- def __init__(self,
- sta_type: StationType,
- freq_mhz: float,
- band_mhz: float,
- spurious_emissions: float,
- scenario = "OUTDOOR"):
+ def __init__(
+ self,
+ sta_type: StationType,
+ freq_mhz: float,
+ band_mhz: float,
+ spurious_emissions: float,
+ scenario="OUTDOOR",
+ ):
"""
- Implements spectral emission mask from 3GPP 36.104 Table 6.6.3.1-6 for
+ Implements spectral emission mask from 3GPP 36.104 Table 6.6.3.1-6 for
Wide Area BS operating with 5, 10, 15 or 20 MHz channel bandwidth.
-
- Also implements spectral emission mask from 3GPP 36.101 Table 6.6.2.1.1-1
+
+ Also implements spectral emission mask from 3GPP 36.101 Table 6.6.2.1.1-1
for UE operating with 5, 10, 15 or 20 MHz channel bandwidth.
-
- In order to characterize Cat-A or Cat-B base stations, it is necessary
+
+ In order to characterize Cat-A or Cat-B base stations, it is necessary
to adjust the input parameter that defines the spurious emission level:
Cat-A: -13 dBm/MHz
Cat-B: -30 dBm/MHz
@@ -35,11 +38,11 @@ def __init__(self,
message = "ERROR\nInvalid station type: " + str(sta_type)
sys.stderr.write(message)
sys.exit(1)
-
- if band_mhz not in [ 5, 10, 15, 20 ]:
+
+ if band_mhz not in [5, 10, 15, 20]:
message = "ERROR\nInvalid bandwidth for 3GPP mask: " + band_mhz
sys.stderr.write(message)
- sys.exit(1)
+ sys.exit(1)
# Attributes
self.spurious_emissions = spurious_emissions
@@ -47,28 +50,31 @@ def __init__(self,
self.scenario = scenario
self.band_mhz = band_mhz
self.freq_mhz = freq_mhz
-
+
delta_f_lim = self.get_frequency_limits(self.sta_type, self.band_mhz)
- #delta_f_lim_flipped = np.flip(self.delta_f_lim,0)
+ # delta_f_lim_flipped = np.flip(self.delta_f_lim,0)
delta_f_lim_flipped = delta_f_lim[::-1]
-
- self.freq_lim = np.concatenate(((self.freq_mhz - self.band_mhz/2) - delta_f_lim_flipped,
- (self.freq_mhz + self.band_mhz/2) + delta_f_lim))
-
-
- def get_frequency_limits(self,
- sta_type : StationType,
- bandwidth : float) -> np.array:
+
+ self.freq_lim = np.concatenate((
+ (self.freq_mhz - self.band_mhz / 2) - delta_f_lim_flipped,
+ (self.freq_mhz + self.band_mhz / 2) + delta_f_lim,
+ ))
+
+ def get_frequency_limits(
+ self,
+ sta_type: StationType,
+ bandwidth: float,
+ ) -> np.array:
"""
Calculates the frequency limits of the spectrum emission masks. This
implementation is valid only for bandwidths equal to 5, 10, 15 or 20 MHz.
"""
-
+
if sta_type is StationType.IMT_BS:
# Mask delta f breaking limits [MHz]
delta_f_lim = np.arange(0, 5.1, .1)
delta_f_lim = np.append(delta_f_lim, 10)
- else:
+ else:
delta_f_lim = np.array([0, 1, 5])
if bandwidth == 5:
delta_f_lim = np.append(delta_f_lim, np.array([6, 10]))
@@ -79,28 +85,34 @@ def get_frequency_limits(self,
else:
delta_f_lim = np.append(delta_f_lim, np.array([20, 25]))
return delta_f_lim
-
-
- def set_mask(self, power = 0):
- emission_limits = self.get_emission_limits(self.sta_type,
- self.band_mhz,
- self.spurious_emissions)
- self.p_tx = power - 10 * np.log10(self.band_mhz)
- #emission_limits = np.flip(emission_limits, 0)
- emission_limits_flipped = emission_limits[::-1]
- self.mask_dbm = np.concatenate((emission_limits_flipped,
- np.array([self.p_tx]),
- emission_limits))
+ def set_mask(self, p_tx=0):
+ emission_limits = self.get_emission_limits(
+ self.sta_type,
+ self.band_mhz,
+ self.spurious_emissions,
+ )
+ self.p_tx = p_tx - 10 * np.log10(self.band_mhz)
+ # emission_limits = np.flip(emission_limits, 0)
+ emission_limits_flipped = emission_limits[::-1]
+ self.mask_dbm = np.concatenate((
+ emission_limits_flipped,
+ np.array([self.p_tx]),
+ emission_limits,
+ ))
- def get_emission_limits(self,
- sta_type : StationType,
- bandwidth : float,
- spurious_emissions : float) -> np.array:
+ def get_emission_limits(
+ self,
+ sta_type: StationType,
+ bandwidth: float,
+ spurious_emissions: float,
+ ) -> np.array:
if sta_type is StationType.IMT_BS:
# emission limits in dBm/MHz
emission_limits = 3 - 7 / 5 * (np.arange(.05, 5, .1) - .05)
- emission_limits = np.append(emission_limits, np.array([-4, spurious_emissions]))
+ emission_limits = np.append(
+ emission_limits, np.array([-4, spurious_emissions]),
+ )
else:
if bandwidth == 5:
limit_r1 = np.array([-15])
@@ -110,6 +122,8 @@ def get_emission_limits(self,
limit_r1 = np.array([-20])
else:
limit_r1 = np.array([-21])
- emission_limits = np.append(limit_r1 + 10*np.log10(1/0.03),
- np.array([-10, -13, -25, spurious_emissions]))
- return emission_limits
\ No newline at end of file
+ emission_limits = np.append(
+ limit_r1 + 10 * np.log10(1 / 0.03),
+ np.array([-10, -13, -25, spurious_emissions]),
+ )
+ return emission_limits
diff --git a/sharc/mask/spectral_mask_imt.py b/sharc/mask/spectral_mask_imt.py
index 71c807dca..86bd6872e 100644
--- a/sharc/mask/spectral_mask_imt.py
+++ b/sharc/mask/spectral_mask_imt.py
@@ -11,17 +11,20 @@
import numpy as np
import matplotlib.pyplot as plt
+
class SpectralMaskImt(SpectralMask):
"""
- Implements spectral masks for IMT-2020 according to Document 5-1/36-E.
- The masks are in the document's tables 1 to 8.
-
+ Implements spectral masks for IMT-2020 according to Document 5-1/36-E.
+ The masks are in the document's tables 1 to 8.
+ Implements alternative spectral masks for IMT-2020 for cases Document 5-1/36-E does not implement
+ according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0.
+ Uses alternative when: outdoor BS's with freq < 24.25GHz
+
Attributes:
spurious_emissions (float): level of power emissions at spurious
- domain [dBm/MHz].
- delta_f_lin (np.array): mask delta f breaking limits in MHz. Delta f
- values for which the spectral mask changes value. Hard coded as
- [0, 20, 400]. In this context, delta f is the frequency distance to
+ domain [dBm/MHz].
+ delta_f_lin (np.array): mask delta f breaking limits in MHz. Delta f
+ values for which the spectral mask changes value. In this context, delta f is the frequency distance to
the transmission's edge frequencies
freq_lim (no.array): frequency values for which the spectral mask
changes emission value
@@ -32,115 +35,267 @@ class SpectralMaskImt(SpectralMask):
scenario (str): INDOOR or OUTDOOR scenario
p_tx (float): station's transmit power in dBm/MHz
mask_dbm (np.array): spectral mask emission values in dBm
+ alternative_mask_used (bool): represents whether the alternative mask should be used or not
+ ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE (int): A hardcoded value that specifies how many samples of a diagonal
+ line may be taken. Is needed because oob_power is calculated expecting rectangles, so we approximate
+ the diagonal with 'SAMPLESIZE' rectangles
"""
- def __init__(self,
- sta_type: StationType,
- freq_mhz: float,
- band_mhz: float,
- spurious_emissions : float,
- scenario = "OUTDOOR"):
+ ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE: int = 40
+
+ def __init__(
+ self,
+ sta_type: StationType,
+ freq_mhz: float,
+ band_mhz: float,
+ spurious_emissions: float,
+ scenario="OUTDOOR",
+ ):
"""
Class constructor.
-
+
Parameters:
sta_type (StationType): type of station to which consider the spectral
mask. Possible values are StationType.IMT_BS and StationType.
IMT_UE
freq_mhz (float): center frequency of station in MHz
band_mhs (float): transmitting bandwidth of station in MHz
- spurious_emissions (float): level of spurious emissions [dBm/MHz].
+ spurious_emissions (float): level of spurious emissions [dBm/MHz].
scenario (str): INDOOR or OUTDOOR scenario
"""
# Spurious domain limits [dBm/MHz]
self.spurious_emissions = spurious_emissions
+
+ # conditions to use alternative mask
+ self.alternative_mask_used = freq_mhz < 24250 \
+ and scenario != "INDOOR" \
+ and sta_type == StationType.IMT_BS \
+ and spurious_emissions in [-13, -30]
+
# Mask delta f breaking limits [MHz]
- self.delta_f_lim = np.array([0, 20, 400])
-
+ if self.alternative_mask_used:
+ self.delta_f_lim = self.get_alternative_mask_delta_f_lim(
+ freq_mhz, band_mhz,
+ )
+ else:
+ # use value from 5-1/36-E
+ self.delta_f_lim = np.array([0, 20, 400])
+
# Attributes
self.sta_type = sta_type
self.scenario = scenario
self.band_mhz = band_mhz
self.freq_mhz = freq_mhz
-
- self.freq_lim = np.concatenate(((freq_mhz - band_mhz/2)-self.delta_f_lim[::-1],
- (freq_mhz + band_mhz/2)+self.delta_f_lim))
-
-
- def set_mask(self, power = 0):
+
+ self.freq_lim = np.concatenate((
+ (freq_mhz - band_mhz / 2) - self.delta_f_lim[::-1],
+ (freq_mhz + band_mhz / 2) + self.delta_f_lim,
+ ))
+
+ def set_mask(self, p_tx=0):
"""
- Sets the spectral mask (mask_dbm attribute) based on station type,
+ Sets the spectral mask (mask_dbm attribute) based on station type,
operating frequency and transmit power.
-
+
Parameters:
- power (float): station transmit power. Default = 0
+ p_tx (float): station transmit power. Default = 0
"""
- self.p_tx = power - 10*np.log10(self.band_mhz)
-
- # Set new transmit power value
+ if self.alternative_mask_used:
+ self.mask_dbm = self.get_alternative_mask_mask_dbm(p_tx)
+ return
+
+ self.p_tx = p_tx - 10 * np.log10(self.band_mhz)
+
+ # Set new transmit power value
if self.sta_type is StationType.IMT_UE:
# Table 8
mask_dbm = np.array([-5, -13, self.spurious_emissions])
-
- elif self.sta_type is StationType.IMT_BS and self.scenario is "INDOOR":
+
+ elif self.sta_type is StationType.IMT_BS and self.scenario == "INDOOR":
# Table 1
mask_dbm = np.array([-5, -13, self.spurious_emissions])
-
+
else:
-
+
if (self.freq_mhz > 24250 and self.freq_mhz < 33400):
- if power >= 34.5:
+ if p_tx >= 34.5:
# Table 2
mask_dbm = np.array([-5, -13, self.spurious_emissions])
else:
# Table 3
- mask_dbm = np.array([-5, np.max((power-47.5,-20)),
- self.spurious_emissions])
+ mask_dbm = np.array([
+ -5, np.max((p_tx - 47.5, -20)),
+ self.spurious_emissions,
+ ])
elif (self.freq_mhz > 37000 and self.freq_mhz < 52600):
- if power >= 32.5:
+ if p_tx >= 32.5:
# Table 4
mask_dbm = np.array([-5, -13, self.spurious_emissions])
else:
# Table 5
- mask_dbm = np.array([-5, np.max((power-45.5,-20)),
- self.spurious_emissions])
+ mask_dbm = np.array([
+ -5, np.max((p_tx - 45.5, -20)),
+ self.spurious_emissions,
+ ])
elif (self.freq_mhz > 66000 and self.freq_mhz < 86000):
- if power >= 30.5:
+ if p_tx >= 30.5:
# Table 6
mask_dbm = np.array([-5, -13, self.spurious_emissions])
else:
# Table 7
- mask_dbm = np.array([-5, np.max((power-43.5,-20)),
- self.spurious_emissions])
+ mask_dbm = np.array([
+ -5, np.max((p_tx - 43.5, -20)),
+ self.spurious_emissions,
+ ])
else:
- # Dummy spectral mask, for testing purposes only
- mask_dbm = np.array([-10, -20, -50])
-
- self.mask_dbm = np.concatenate((mask_dbm[::-1],np.array([self.p_tx]),
- mask_dbm))
-
+ # this will only be reached when spurious emission has been manually set to something invalid and
+ # alternative mask should be used
+ raise ValueError(
+ "SpectralMaskIMT cannot be used with current parameters. \
+ You may have set spurious emission to a value not in [-13,-30]",
+ )
+
+ self.mask_dbm = np.concatenate((
+ mask_dbm[::-1], np.array([self.p_tx]),
+ mask_dbm,
+ ))
+
+ def get_alternative_mask_delta_f_lim(self, freq_mhz: float, band_mhz: float) -> np.array:
+ """
+ Implements spectral masks for IMT-2020 outdoor BS's when freq < 26GHz,
+ according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0.
+ Reference tables are:
+ - Table 1 in ITU-R SM.1541-6
+ - Table 2 in ITU-R SM.1539-1
+ - Table 6.6.4.2.1-2 in ETSI TS 138 104 V16.6.0
+ - Table 6.6.4.2.2.1-2 in ETSI TS 138 104 V16.6.0
+ - Table 6.6.5.2.1-1 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table)
+ - Table 6.6.5.2.1-2 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table)
+ """
+ # ITU-R SM.1539-1 Table 2
+ if (freq_mhz > 0.009 and freq_mhz <= 0.15):
+ B_L = 0.00025
+ B_U = 0.01
+ elif (freq_mhz > 0.15 and freq_mhz <= 30):
+ B_L = 0.004
+ B_U = 0.1
+ elif (freq_mhz > 30 and freq_mhz <= 1000):
+ B_L = 0.025
+ B_U = 10
+ elif (freq_mhz > 1000 and freq_mhz <= 3000):
+ B_L = 0.1
+ B_U = 50
+ elif (freq_mhz > 3000 and freq_mhz <= 10000):
+ B_L = 0.1
+ B_U = 100
+ elif (freq_mhz > 10000 and freq_mhz <= 15000):
+ B_L = 0.3
+ B_U = 250
+ elif (freq_mhz > 15000 and freq_mhz <= 26000):
+ B_L = 0.5
+ B_U = 500
+ else:
+ raise ValueError(f"Invalid frequency value {freq_mhz} for mask ITU-R SM.1539-1")
+
+ # ITU-R SM.1541-6 Table 1 (same as using only ITU-R SM.1539-1 Table 2, but with less hardcoded values)
+ B_L_separation = 2.5 * B_L
+ B_U_separation = 1.5 * band_mhz + B_U
+ B_N_separation = 2.5 * band_mhz
+
+ if band_mhz < B_L:
+ delta_f_spurious = B_L_separation
+ elif band_mhz > B_U:
+ delta_f_spurious = B_U_separation
+ else:
+ delta_f_spurious = B_N_separation
+
+ diagonal = np.array([
+ i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE for i in range(
+ self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE,
+ )
+ ])
+
+ # band/2 is subtracted from delta_f_spurious beacuse that specific interval is from frequency center
+ rest_of_oob_and_spurious = np.array(
+ [5, 10.0, delta_f_spurious - band_mhz / 2],
+ )
+
+ return np.concatenate((diagonal, rest_of_oob_and_spurious))
+
+ def get_alternative_mask_mask_dbm(self, power: float = 0) -> np.array:
+ """
+ Implements spectral masks for IMT-2020 outdoor BS's when freq < 26GHz,
+ according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0.
+ Reference tables are:
+ - Table 1 in ITU-R SM.1541-6
+ - Table 2 in ITU-R SM.1539-1
+ - Table 6.6.4.2.1-2 in ETSI TS 138 104 V16.6.0
+ - Table 6.6.4.2.2.1-2 in ETSI TS 138 104 V16.6.0
+ - Table 6.6.5.2.1-1 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table)
+ - Table 6.6.5.2.1-2 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table)
+ """
+ self.p_tx = power - 10 * np.log10(self.band_mhz)
+
+ if self.spurious_emissions == -13:
+ # use document ETSI TS 138 104 V16.6.0 Table 6.6.4.2.1-2
+ diagonal_samples = np.array([
+ -7 - 7 / 5 *
+ (i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE)
+ for i in range(self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE)
+ ])
+ rest_of_oob_and_spurious = np.array([
+ -14, -13, self.spurious_emissions,
+ ])
+ mask_dbm = np.concatenate(
+ (diagonal_samples, rest_of_oob_and_spurious),
+ )
+ elif self.spurious_emissions == -30:
+ # use document ETSI TS 138 104 V16.6.0 Table 6.6.4.2.2.1-2
+ diagonal_samples = np.array([
+ -7 - 7 / 5 *
+ (i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE)
+ for i in range(self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE)
+ ])
+ rest_of_oob_and_spurious = np.array(
+ [-14, -15, self.spurious_emissions],
+ )
+ mask_dbm = np.concatenate(
+ (diagonal_samples, rest_of_oob_and_spurious),
+ )
+ else:
+ raise ValueError(
+ "Alternative mask should only be used for spurious emissions -13 and -30",
+ )
+
+ return np.concatenate((
+ mask_dbm[::-1], np.array([self.p_tx]),
+ mask_dbm,
+ ))
+
+
if __name__ == '__main__':
# Initialize variables
sta_type = StationType.IMT_BS
- p_tx = 25.1
- freq = 43000
- band = 200
-
+ p_tx = 34.061799739838875
+ freq = 9000
+ band = 100
+ spurious_emissions_dbm_mhz = -30
+
# Create mask
- msk = SpectralMaskImt(sta_type,freq,band)
+ msk = SpectralMaskImt(sta_type, freq, band, spurious_emissions_dbm_mhz)
msk.set_mask(p_tx)
-
+
# Frequencies
- freqs = np.linspace(-600,600,num=1000)+freq
-
+ freqs = np.linspace(-600, 600, num=1000) + freq
+
# Mask values
- mask_val = np.ones_like(freqs)*msk.mask_dbm[0]
- for k in range(len(msk.freq_lim)-1,-1,-1):
+ mask_val = np.ones_like(freqs) * msk.mask_dbm[0]
+ for k in range(len(msk.freq_lim) - 1, -1, -1):
mask_val[np.where(freqs < msk.freq_lim[k])] = msk.mask_dbm[k]
-
+
# Plot
- plt.plot(freqs,mask_val)
- plt.xlim([freqs[0],freqs[-1]])
- plt.xlabel("$\Delta$f [MHz]")
+ plt.plot(freqs, mask_val)
+ plt.xlim([freqs[0], freqs[-1]])
+ plt.xlabel(r"$\Delta$f [MHz]")
plt.ylabel("Spectral Mask [dBc]")
plt.grid()
plt.show()
diff --git a/sharc/mask/spectral_mask_mss.py b/sharc/mask/spectral_mask_mss.py
new file mode 100644
index 000000000..1d6c4d58e
--- /dev/null
+++ b/sharc/mask/spectral_mask_mss.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+
+from sharc.support.enumerations import StationType
+from sharc.mask.spectral_mask import SpectralMask
+
+import numpy as np
+import math
+import matplotlib.pyplot as plt
+
+
+class SpectralMaskMSS(SpectralMask):
+ """
+ Implements spectral mask for all MSS Space Stations and some MSS Earth Stations
+ according to REC ITU-R SM.1541. This is a generic mask and should only be used in case
+ another isn't given.
+
+ Spurious boundary has a default of 200% of the bandwidth from band edge.
+
+ Attributes:
+ spurious_emissions (float): level of power emissions at spurious
+ domain [dBm/MHz].
+ delta_f_lin (np.array): mask delta f breaking limits in MHz. Delta f
+ values for which the spectral mask changes value. In this context, delta f is the frequency distance to
+ the transmission's edge frequencies
+ freq_lim (no.array): frequency values for which the spectral mask
+ changes emission value
+ freq_mhz (float): center frequency of station in MHz
+ band_mhs (float): transmitting bandwidth of station in MHz
+ p_tx (float): station's transmit power in dBm/MHz
+ mask_dbm (np.array): spectral mask emission values in dBm
+ """
+
+ ALREADY_WARNED_AGAINST_LONG_CALCULATIONS = False
+
+ def __init__(
+ self,
+ freq_mhz: float,
+ band_mhz: float,
+ spurious_emissions: float,
+ ):
+ """
+ Class constructor.
+
+ Parameters:
+ freq_mhz (float): center frequency of station in MHz
+ band_mhs (float): transmitting bandwidth of station in MHz
+ spurious_emissions (float): level of spurious emissions [dBm/MHz].
+ """
+ # Spurious domain limits [dBm/MHz]
+ self.spurious_emissions = spurious_emissions
+
+ if freq_mhz < 15000:
+ if band_mhz > 20 and not self.ALREADY_WARNED_AGAINST_LONG_CALCULATIONS:
+ self.ALREADY_WARNED_AGAINST_LONG_CALCULATIONS = True
+ print("WARNING: SpectralMaskMSS may take noticeably long to calculate. Consider changing its integral step.")
+ self.reference_bandwidth = 0.004
+ else:
+ self.reference_bandwidth = 1
+ self.spurious_boundary = 2 * band_mhz
+
+ self.delta_f_lim = np.linspace(
+ 0., self.spurious_boundary - self.reference_bandwidth,
+ math.ceil(self.spurious_boundary / self.reference_bandwidth)
+ )
+ self.delta_f_lim = np.concatenate((self.delta_f_lim, [self.spurious_boundary]))
+
+ # Attributes
+ self.band_mhz = band_mhz
+ self.freq_mhz = freq_mhz
+
+ self.freq_lim = np.concatenate((
+ (freq_mhz - band_mhz / 2) - self.delta_f_lim[::-1],
+ (freq_mhz + band_mhz / 2) + self.delta_f_lim,
+ ))
+
+ def set_mask(self, p_tx):
+ """
+ Set the spectral mask (mask_dbm attribute) based on station type, operating frequency and transmit power.
+
+ Parameters:
+ p_tx (float): station transmit power.
+ """
+ # dBm/MHz
+ # this should work for the document's dBsd definition
+ # when we have a uniform PSD in assigned band
+ self.p_tx = p_tx - 10 * np.log10(self.band_mhz) + 30
+
+ # attenuation mask
+ mask_dbsd = 40 * np.log10(
+ # both in MHz, percentage is just division
+ 100 * (self.delta_f_lim[:-1] / self.band_mhz) / 50 + 1
+ # 100 * ((self.delta_f_lim[:-1] + self.reference_bandwidth/2) / self.band_mhz) / 50 + 1
+ )
+
+ # functionally same as 0, but won't be ignored at spectral mask calculation
+ # TODO: something better than this...
+ mask_dbsd[mask_dbsd == 0.] = 1e-14
+
+ mask_dbm = self.p_tx - mask_dbsd
+
+ mask_dbm = np.concatenate((mask_dbm, [self.spurious_emissions]))
+
+ self.mask_dbm = np.concatenate((
+ mask_dbm[::-1],
+ np.array([self.p_tx]),
+ mask_dbm,
+ ))
+
+
+if __name__ == '__main__':
+ # Initialize variables
+ p_tx = 34.061799739838875
+ freq = 2100
+ band = 5
+ spurious_emissions_dbm_mhz = -30
+
+ # Create mask
+ msk = SpectralMaskMSS(freq, band, spurious_emissions_dbm_mhz)
+ msk.set_mask(p_tx)
+
+ # Frequencies
+ freqs = np.linspace(-15, 15, num=1000) + freq
+
+ # Mask values
+ mask_val = np.ones_like(freqs) * msk.mask_dbm[0]
+ for k in range(len(msk.freq_lim) - 1, -1, -1):
+ mask_val[np.where(freqs < msk.freq_lim[k])] = msk.mask_dbm[k]
+ # set as p_tx instead of 0 for plotting
+ if k == len(msk.delta_f_lim):
+ mask_val[np.where(freqs < msk.freq_lim[k])] = msk.p_tx
+
+ # Plot
+ # plt.plot(freqs, 10**(mask_val/10))
+ plt.plot(freqs, mask_val)
+ plt.xlim([freqs[0], freqs[-1]])
+ plt.xlabel(r"f [MHz]")
+ plt.ylabel("Spectral Mask [dBm]")
+ plt.grid()
+ plt.show()
+
+ oob_idxs = np.where((freqs > freq - msk.spurious_boundary) & (freqs < freq + msk.spurious_boundary))[0]
+
+ # Plot
+ plt.plot(freqs[oob_idxs], mask_val[oob_idxs])
+ # plt.plot(freqs[oob_idxs], 10 **(mask_val[oob_idxs]/10))
+ plt.xlim([freqs[oob_idxs][0], freqs[oob_idxs][-1]])
+ plt.xlabel(r"f [MHz]")
+ plt.ylabel("Spectral Mask before spurious region [mW]")
+ plt.grid()
+ plt.show()
diff --git a/sharc/model.py b/sharc/model.py
index 6789260b1..3c4936e6d 100644
--- a/sharc/model.py
+++ b/sharc/model.py
@@ -13,7 +13,7 @@
from sharc.parameters.parameters import Parameters
import random
-import sys
+
class Model(Observable):
"""
@@ -32,8 +32,10 @@ def add_observer(self, observer: Observer):
def set_param_file(self, param_file):
self.param_file = param_file
- self.notify_observers(source = __name__,
- message = "Loading file:\n" + self.param_file)
+ self.notify_observers(
+ source=__name__,
+ message="Loading file:\n" + self.param_file,
+ )
def initialize(self):
"""
@@ -45,21 +47,27 @@ def initialize(self):
self.parameters.read_params()
if self.parameters.general.imt_link == "DOWNLINK":
- self.simulation = SimulationDownlink(self.parameters, self.param_file)
+ self.simulation = SimulationDownlink(
+ self.parameters, self.param_file,
+ )
else:
- self.simulation = SimulationUplink(self.parameters, self.param_file)
+ self.simulation = SimulationUplink(
+ self.parameters, self.param_file,
+ )
self.simulation.add_observer_list(self.observers)
description = self.get_description()
- self.notify_observers(source=__name__,
- message=description + "\nSimulation is running...",
- state=State.RUNNING )
+ self.notify_observers(
+ source=__name__,
+ message=description + "\nSimulation is running...",
+ state=State.RUNNING,
+ )
self.current_snapshot = 0
self.simulation.initialize()
- random.seed( self.parameters.general.seed )
+ random.seed(self.parameters.general.seed)
self.secondary_seeds = [None] * self.parameters.general.num_snapshots
@@ -72,18 +80,18 @@ def get_description(self) -> str:
param_system = self.simulation.param_system
description = "\nIMT:\n" \
- + "\tinterfered with: {:s}\n".format(str(self.parameters.imt.interfered_with)) \
- + "\tdirection: {:s}\n".format(self.parameters.general.imt_link) \
- + "\tfrequency: {:.3f} GHz\n".format(self.parameters.imt.frequency*1e-3) \
- + "\tbandwidth: {:.0f} MHz\n".format(self.parameters.imt.bandwidth) \
- + "\tspurious emissions: {:.0f} dBm/MHz\n".format(self.parameters.imt.spurious_emissions) \
- + "\ttopology: {:s}\n".format(self.parameters.imt.topology) \
- + "\tpath loss model: {:s}\n".format(self.parameters.imt.channel_model) \
- + "{:s}:\n".format(self.parameters.general.system) \
- + "\tfrequency: {:.3f} GHz\n".format(param_system.frequency*1e-3) \
- + "\tbandwidth: {:.0f} MHz\n".format(param_system.bandwidth) \
- + "\tpath loss model: {:s}\n".format(param_system.channel_model) \
- + "\tantenna pattern: {:s}\n".format(param_system.antenna_pattern)
+ + "\tinterfered with: {:s}\n".format(str(self.parameters.imt.interfered_with)) \
+ + "\tdirection: {:s}\n".format(self.parameters.general.imt_link) \
+ + "\tfrequency: {:.3f} GHz\n".format(self.parameters.imt.frequency * 1e-3) \
+ + "\tbandwidth: {:.0f} MHz\n".format(self.parameters.imt.bandwidth) \
+ + "\tspurious emissions: {:.0f} dBm/MHz\n".format(self.parameters.imt.spurious_emissions) \
+ + "\ttopology: {:s}\n".format(self.parameters.imt.topology.type) \
+ + "\tpath loss model: {:s}\n".format(self.parameters.imt.channel_model) \
+ + "{:s}:\n".format(self.parameters.general.system) \
+ + "\tfrequency: {:.3f} GHz\n".format(param_system.frequency * 1e-3) \
+ + "\tbandwidth: {:.0f} MHz\n".format(param_system.bandwidth) \
+ + "\tpath loss model: {:s}\n".format(param_system.channel_model) \
+ + "\tantenna pattern: {:s}\n".format(param_system.antenna_pattern)
return description
@@ -96,12 +104,16 @@ def snapshot(self):
if not self.current_snapshot % 10:
write_to_file = True
- self.notify_observers(source=__name__,
- message="Snapshot #" + str(self.current_snapshot))
+ self.notify_observers(
+ source=__name__,
+ message="Snapshot #" + str(self.current_snapshot),
+ )
- self.simulation.snapshot(write_to_file=write_to_file,
- snapshot_number=self.current_snapshot,
- seed = self.secondary_seeds[self.current_snapshot - 1])
+ self.simulation.snapshot(
+ write_to_file=write_to_file,
+ snapshot_number=self.current_snapshot,
+ seed=self.secondary_seeds[self.current_snapshot - 1],
+ )
def is_finished(self) -> bool:
"""
@@ -122,8 +134,10 @@ def finalize(self):
Finalizes the simulation and performs all post-simulation tasks
"""
self.simulation.finalize(snapshot_number=self.current_snapshot)
- self.notify_observers(source=__name__,
- message="FINISHED!", state=State.FINISHED)
+ self.notify_observers(
+ source=__name__,
+ message="FINISHED!", state=State.FINISHED,
+ )
def set_elapsed_time(self, elapsed_time: str):
"""
@@ -134,6 +148,8 @@ def set_elapsed_time(self, elapsed_time: str):
----------
elapsed_time: Elapsed time.
"""
- self.notify_observers(source=__name__,
- message="Elapsed time: " + elapsed_time,
- state=State.FINISHED)
+ self.notify_observers(
+ source=__name__,
+ message="Elapsed time: " + elapsed_time,
+ state=State.FINISHED,
+ )
diff --git a/sharc/output/__init__.py b/sharc/output/__init__.py
deleted file mode 100644
index faaaf799c..000000000
--- a/sharc/output/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/parameters/__init__.py b/sharc/parameters/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/parameters/__init__.py
+++ b/sharc/parameters/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/parameters/antenna/parameters_antenna_s1528.py b/sharc/parameters/antenna/parameters_antenna_s1528.py
new file mode 100644
index 000000000..05e48f04d
--- /dev/null
+++ b/sharc/parameters/antenna/parameters_antenna_s1528.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersAntennaS1528(ParametersBase):
+ """Dataclass containing the Antenna Pattern S.1528 parameters for the simulator.
+ """
+ section_name: str = "S1528"
+ # satellite center frequency [MHz]
+ frequency: float = None
+ # channel bandwidth - used for Taylor antenna
+ bandwidth: float = None
+ # Peak antenna gain [dBi]
+ antenna_gain: float = None
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", "ITU-R-S.1528-Taylor"
+ antenna_pattern: str = "ITU-R-S.1528-LEO"
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ # according to ITU-R S.672-4
+ antenna_l_s: float = -20.0
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB_bw: float = 0.65
+
+ #####################################################################
+ # The following parameters are used for S.1528-Taylor antenna pattern
+
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: float = 20.0
+
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: int = 2
+
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: float = 1.6
+ l_t: float = 1.6
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check.
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+
+ self.validate("antenna_s1528")
+
+ def load_from_parameters(self, param: ParametersBase):
+ """Load from another parameter object
+
+ Parameters
+ ----------
+ param : ParametersBase
+ Parameters object containing ParametersAntennaS1528
+ """
+ self.antenna_gain = param.antenna_gain
+ self.frequency = param.frequency
+ self.antenna_gain = param.antenna_gain
+ self.antenna_pattern = param.antenna_pattern
+ self.antenna_l_s = param.antenna_l_s
+ self.antenna_3_dB_bw = param.antenna_3_dB_bw
+ self.slr = param.slr
+ self.n_side_lobes = param.n_side_lobes
+ return self
+
+ def set_external_parameters(self, **kwargs):
+ """
+ This method is used to "propagate" parameters from external context
+ to the values required by antenna S1528.
+ """
+ attr_list = [a for a in dir(self) if not a.startswith('__')]
+
+ for k, v in kwargs.items():
+ if k in attr_list:
+ setattr(self, k, v)
+ else:
+ raise ValueError(f"Parameter {k} is not a valid attribute of {self.__class__.__name__}")
+
+ self.validate("S.1528")
+
+ def validate(self, ctx: str):
+ # Now do the sanity check for some parameters
+ if None in [self.frequency, self.bandwidth]:
+ raise ValueError(
+ f"{ctx}.[frequency, bandwidth, antenna_gain] = {[self.frequency, self.bandwidth]}.\
+ They need to all be set!")
+
+ if self.antenna_pattern not in ["ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", "ITU-R-S.1528-Taylor"]:
+ raise ValueError(f"{ctx}: \
+ invalid value for parameter antenna_pattern - {self.antenna_pattern}. \
+ Possible values \
+ are \"ITU-R-S.1528-Section1.2\", \"ITU-R-S.1528-LEO\", \"ITU-R-S.1528-Taylor\"")
diff --git a/sharc/parameters/constants.py b/sharc/parameters/constants.py
new file mode 100644
index 000000000..12cf8def2
--- /dev/null
+++ b/sharc/parameters/constants.py
@@ -0,0 +1,6 @@
+"""Constants definitions used thourghput the simulator."""
+BOLTZMANN_CONSTANT = 1.38064852e-23
+EARTH_RADIUS = 6371000
+SPEED_OF_LIGHT = 299792458
+KEPLER_CONST = 398601.8 # Kepler's constant, in km^3/s^2
+EARTH_ROTATION_RATE = 2 * 3.14159265359 / (24 * 3600) # earth's average rotation rate, in rad/s
diff --git a/sharc/parameters/imt/parameters_antenna_imt.py b/sharc/parameters/imt/parameters_antenna_imt.py
new file mode 100644
index 000000000..74dd64bbd
--- /dev/null
+++ b/sharc/parameters/imt/parameters_antenna_imt.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Sat Apr 15 16:29:36 2017
+
+@author: Calil
+"""
+
+from sharc.support.named_tuples import AntennaPar
+from numpy import load
+import typing
+
+from dataclasses import dataclass, field
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersAntennaSubarrayImt(ParametersBase):
+ """
+ Parameters for subarray as defined in R23-WP5D-C-0413, Annex 4.2
+ """
+ # to use subarray, set this to true
+ is_enabled: bool = False
+
+ # Number of rows in subarray
+ n_rows: int = 3
+
+ # BS array element vertical spacing (d/lambda).
+ element_vert_spacing: float = 0.5
+ # element_vert_spacing: float = 0.5
+
+ # notice that electrical tilt == -1 * downtilt
+ # Sub array eletrical downtilt [deg]
+ eletrical_downtilt: float = 3.0
+
+
+@dataclass
+class ParametersAntennaImt(ParametersBase):
+ """
+ Defines the antenna model and related parameters to be used in compatibility
+ studies between IMT and other services in adjacent bands.
+ """
+ section_name: str = "imt_antenna"
+
+ # Normalization application flags for base station (BS) and user equipment (UE).
+ normalization: bool = False
+
+ # Normalization files for BS and UE beamforming.
+ normalization_file: str = "antenna/beamforming_normalization/norm.npz"
+
+ # Radiation pattern of each antenna element.
+ element_pattern: str = "M2101"
+
+ # Minimum array gain for the beamforming antenna [dBi].
+ minimum_array_gain: float = -200.0
+
+ # beamforming angle limitation [deg].
+ # PS: it isn't implemented for UEs
+ # and current implementation doesn't make sense for UEs
+ horizontal_beamsteering_range: tuple[float | int, float | int] = (-180., 180.)
+ vertical_beamsteering_range: tuple[float | int, float | int] = (0., 180.)
+
+ # Mechanical downtilt [degrees].
+ # PS: downtilt doesn't make sense on UE's
+ downtilt: float = 6.0
+
+ # BS/UE maximum transmit/receive element gain [dBi].
+ element_max_g: float = 5.0
+
+ # BS/UE horizontal 3dB beamwidth of single element [degrees].
+ element_phi_3db: float = 65.0
+
+ # BS/UE vertical 3dB beamwidth of single element [degrees].
+ element_theta_3db: float = 65.0
+
+ # BS/UE number of rows and columns in antenna array.
+ n_rows: int = 8
+ n_columns: int = 8
+
+ # BS/UE array element spacing (d/lambda).
+ element_horiz_spacing: float = 0.5
+ element_vert_spacing: float = 0.5
+
+ # BS/UE front to back ratio and single element vertical sidelobe attenuation [dB].
+ element_am: int = 30
+ element_sla_v: int = 30
+
+ # Multiplication factor k used to adjust the single-element pattern.
+ multiplication_factor: int = 12
+
+ adjacent_antenna_model: typing.Literal["BEAMFORMING", "SINGLE_ELEMENT"] = None
+
+ subarray: ParametersAntennaSubarrayImt = field(default_factory=ParametersAntennaSubarrayImt)
+
+ def load_subparameters(self, ctx: str, params: dict, quiet=True):
+ """
+ Loads the parameters when is placed as subparameter
+ """
+ super().load_subparameters(ctx, params, quiet)
+
+ def set_external_parameters(self, *, adjacent_antenna_model: typing.Literal["BEAMFORMING", "SINGLE_ELEMENT"]):
+ self.adjacent_antenna_model = adjacent_antenna_model
+
+ def validate(self, ctx: str):
+ # Additional sanity checks specific to antenna parameters can be implemented here
+
+ # Sanity check for adjacent_antenna_model
+ if self.adjacent_antenna_model not in ["SINGLE_ELEMENT", "BEAMFORMING"]:
+ raise ValueError("adjacent_antenna_model must be 'SINGLE_ELEMENT'")
+
+ # Sanity checks for normalization flags
+ if not isinstance(self.normalization, bool):
+ raise ValueError("normalization must be a boolean value")
+
+ # Sanity checks for element patterns
+ if self.element_pattern.upper() not in ["M2101", "F1336", "FIXED"]:
+ raise ValueError(
+ f"Invalid element_pattern value {self.element_pattern}",
+ )
+ if isinstance(self.horizontal_beamsteering_range, list):
+ self.horizontal_beamsteering_range = tuple(self.horizontal_beamsteering_range)
+
+ if not isinstance(self.horizontal_beamsteering_range, tuple):
+ raise ValueError(
+ f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
+ "It needs to be a tuple"
+ )
+ if len(self.horizontal_beamsteering_range) != 2\
+ or not all(map(
+ lambda x: isinstance(x, float) or isinstance(x, int), self.horizontal_beamsteering_range
+ )):
+ raise ValueError(
+ f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
+ "It needs to contain two numbers delimiting the range of beamsteering in degrees"
+ )
+ if self.horizontal_beamsteering_range[0] > self.horizontal_beamsteering_range[1]:
+ raise ValueError(
+ f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
+ "The second value must be bigger than the first"
+ )
+ if not all(map(
+ lambda x: x >= -180. and x <= 180., self.horizontal_beamsteering_range
+ )):
+ raise ValueError(
+ f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
+ "Horizontal beamsteering limit angles must be in the range [-180, 180]"
+ )
+
+ if isinstance(self.vertical_beamsteering_range, list):
+ self.vertical_beamsteering_range = tuple(self.vertical_beamsteering_range)
+ if not isinstance(self.vertical_beamsteering_range, tuple):
+ raise ValueError(
+ f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
+ "It needs to be a tuple"
+ )
+ if len(self.vertical_beamsteering_range) != 2\
+ or not all(map(
+ lambda x: isinstance(x, float) or isinstance(x, int), self.vertical_beamsteering_range
+ )):
+ raise ValueError(
+ f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
+ "It needs to contain two numbers delimiting the range of beamsteering in degrees"
+ )
+ if self.vertical_beamsteering_range[0] > self.vertical_beamsteering_range[1]:
+ raise ValueError(
+ f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
+ "The second value must be bigger than the first"
+ )
+ if not all(map(
+ lambda x: x >= 0. and x <= 180., self.vertical_beamsteering_range
+ )):
+ raise ValueError(
+ f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
+ "vertical beamsteering limit angles must be in the range [0, 180]"
+ )
+
+ def get_antenna_parameters(self) -> AntennaPar:
+ if self.normalization:
+ # Load data, save it in dict and close it
+ data = load(self.normalization_file)
+ data_dict = {key: data[key] for key in data}
+ self.normalization_data = data_dict
+ data.close()
+ else:
+ self.normalization_data = None
+ tpl = AntennaPar(
+ self.adjacent_antenna_model,
+ self.normalization,
+ self.normalization_data,
+ self.element_pattern,
+ self.element_max_g,
+ self.element_phi_3db,
+ self.element_theta_3db,
+ self.element_am,
+ self.element_sla_v,
+ self.n_rows,
+ self.n_columns,
+ self.element_horiz_spacing,
+ self.element_vert_spacing,
+ self.multiplication_factor,
+ self.minimum_array_gain,
+ self.downtilt,
+ )
+
+ return tpl
diff --git a/sharc/parameters/imt/parameters_hotspot.py b/sharc/parameters/imt/parameters_hotspot.py
new file mode 100644
index 000000000..481f18fe0
--- /dev/null
+++ b/sharc/parameters/imt/parameters_hotspot.py
@@ -0,0 +1,37 @@
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersHotspot(ParametersBase):
+ intersite_distance: int = None
+ # Enable wrap around
+ wrap_around: bool = False
+ # Number of clusters topology
+ num_clusters: int = 1
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell: float = 1.0
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue: float = 100.0
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot: float = 0.0
+
+ def validate(self, ctx):
+ if not isinstance(self.intersite_distance, int) and not isinstance(self.intersite_distance, float):
+ raise ValueError(f"{ctx}.intersite_distance should be a number")
+
+ if self.num_clusters not in [1, 7]:
+ raise ValueError(f"{ctx}.num_clusters should be either 1 or 7")
+
+ if not isinstance(self.num_hotspots_per_cell, int) or self.num_hotspots_per_cell < 0:
+ raise ValueError("num_hotspots_per_cell must be non-negative")
+
+ if (not isinstance(self.max_dist_hotspot_ue, float) and not isinstance(self.max_dist_hotspot_ue, int))\
+ or self.max_dist_hotspot_ue < 0:
+ raise ValueError("max_dist_hotspot_ue must be non-negative")
+
+ if (not isinstance(self.min_dist_bs_hotspot, float) and not isinstance(self.min_dist_bs_hotspot, int))\
+ or self.min_dist_bs_hotspot < 0:
+ raise ValueError("min_dist_bs_hotspot must be non-negative")
diff --git a/sharc/parameters/imt/parameters_imt.py b/sharc/parameters/imt/parameters_imt.py
new file mode 100644
index 000000000..fdb95a204
--- /dev/null
+++ b/sharc/parameters/imt/parameters_imt.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+"""Parameters definitions for IMT systems
+"""
+from dataclasses import dataclass, field
+import typing
+
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.parameters_antenna import ParametersAntenna
+from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology
+
+
+@dataclass
+class ParametersImt(ParametersBase):
+ """Dataclass containing the IMT system parameters
+ """
+ section_name: str = "imt"
+ # whether to enable recursive parameters setting on .yaml file
+ nested_parameters_enabled: bool = True
+
+ minimum_separation_distance_bs_ue: float = 0.0
+ interfered_with: bool = False
+ frequency: float = 24350.0
+ bandwidth: float = 200.0
+ rb_bandwidth: float = 0.180
+ spectral_mask: str = "IMT-2020"
+ spurious_emissions: float = -13.0
+ guard_band_ratio: float = 0.1
+ # Adjacent Interference filter reception used when IMT is victim. Possible values is ACS and OFF
+ adjacent_ch_reception: str = "OFF"
+
+ @dataclass
+ class ParametersBS(ParametersBase):
+ """Dataclass containing the IMT Base Station (BS) parameters."""
+
+ load_probability = 0.2
+ conducted_power = 10.0
+ height: float = 6.0
+ noise_figure: float = 10.0
+ ohmic_loss: float = 3.0
+ antenna: ParametersAntenna = field(default_factory=lambda: ParametersAntenna(
+ pattern="ARRAY", array=ParametersAntennaImt(downtilt=0.0)
+ ))
+ bs: ParametersBS = field(default_factory=ParametersBS)
+
+ topology: ParametersImtTopology = field(default_factory=ParametersImtTopology)
+
+ @dataclass
+ class ParametersUL(ParametersBase):
+ """Dataclass containing the IMT Uplink (UL) parameters."""
+
+ attenuation_factor: float = 0.4
+ sinr_min: float = -10.0
+ sinr_max: float = 22.0
+ uplink: ParametersUL = field(default_factory=ParametersUL)
+
+ # Antenna model for adjacent band studies.
+ adjacent_antenna_model: typing.Literal["SINGLE_ELEMENT", "BEAMFORMING"] = "SINGLE_ELEMENT"
+
+ @dataclass
+ class ParametersUE(ParametersBase):
+ """Dataclass containing the IMT User Equipment (UE) parameters."""
+
+ k: int = 3
+ k_m: int = 1
+ indoor_percent: int = 5.0
+ distribution_type: str = "ANGLE_AND_DISTANCE"
+ distribution_distance: str = "RAYLEIGH"
+ distribution_azimuth: str = "NORMAL"
+ azimuth_range: tuple = (-60, 60)
+ tx_power_control: bool = True
+ p_o_pusch: float = -95.0
+ alpha: float = 1.0
+ p_cmax: float = 22.0
+ power_dynamic_range: float = 63.0
+ height: float = 1.5
+ noise_figure: float = 10.0
+ ohmic_loss: float = 3.0
+ body_loss: float = 4.0
+ adjacent_ch_selectivity: float = 33 # Adjacent Channel Selectivity in dB
+ antenna: ParametersAntenna = field(default_factory=lambda: ParametersAntenna(
+ pattern="ARRAY"
+ ))
+
+ def validate(self, ctx: str):
+ """Validate the UE antenna beamsteering range parameters."""
+ if self.antenna.array.horizontal_beamsteering_range != (-180., 180.)\
+ or self.antenna.array.vertical_beamsteering_range != (0., 180.):
+ raise NotImplementedError(
+ "UE antenna beamsteering limit has not been implemented. Default values of\n"
+ "horizontal = (-180., 180.), vertical = (0., 180.) should not be changed"
+ )
+
+ ue: ParametersUE = field(default_factory=ParametersUE)
+
+ @dataclass
+ class ParamatersDL(ParametersBase):
+ attenuation_factor: float = 0.6
+ sinr_min: float = -10.0
+ sinr_max: float = 30.0
+
+ downlink: ParamatersDL = field(default_factory=ParamatersDL)
+
+ noise_temperature: float = 290.0
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # TODO: check if we wanna separate the channel model definition in its own nested attributes
+ channel_model: str = "UMi"
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # For now, the NTN footprint is centered over the BS nadir point, therefore
+ # the paramters imt_lag_deg and imt_long_diff_deg SHALL be zero.
+ # space_station_alt_m - altitude of IMT space station (meters)
+ # earth_station_alt_m - altitude of IMT earth stations (UEs) (in meters)
+ # earth_station_lat_deg - latitude of IMT earth stations (UEs) (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT space and earth stations
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ param_p619 = ParametersP619()
+ season: str = "SUMMER"
+
+ # TODO: create parameters for where this is needed
+ los_adjustment_factor: float = 18.0
+ shadowing: bool = True
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+
+ if self.spectral_mask not in ["IMT-2020", "3GPP E-UTRA"]:
+ raise ValueError(
+ f"""ParametersImt: Inavlid Spectral Mask Name {self.spectral_mask}""",
+ )
+ if self.adjacent_ch_reception not in ["ACS", "OFF"]:
+ raise ValueError(
+ f"""ParametersImt: Invalid Adjacent Channel Reception model {self.adjacent_ch_reception}""",
+ )
+
+ if self.channel_model not in ["FSPL", "CI", "UMa", "UMi", "TVRO-URBAN", "TVRO-SUBURBAN", "ABG", "P619"]:
+ raise ValueError(f"ParamtersImt: \
+ Invalid value for parameter channel_model - {self.channel_model}. \
+ Possible values are \"FSPL\",\"CI\", \"UMa\", \"UMi\", \"TVRO-URBAN\", \"TVRO-SUBURBAN\", \
+ \"ABG\", \"P619\".")
+
+ if self.topology.type == "NTN" and self.channel_model not in ["FSPL", "P619"]:
+ raise ValueError(
+ f"ParametersImt: Invalid channel model {self.channel_model} for topology NTN",
+ )
+
+ if self.season not in ["SUMMER", "WINTER"]:
+ raise ValueError(f"ParamtersImt: \
+ Invalid value for parameter season - {self.season}. \
+ Possible values are \"SUMMER\", \"WINTER\".")
+
+ if self.topology.type == "NTN":
+ self.is_space_to_earth = True
+ # self.param_p619.load_from_paramters(self)
+
+ self.frequency = float(self.frequency)
+
+ self.topology.ntn.set_external_parameters(
+ bs_height=self.bs.height
+ )
+
+ self.bs.antenna.set_external_parameters(
+ adjacent_antenna_model=self.adjacent_antenna_model,
+ frequency=self.frequency,
+ bandwidth=self.bandwidth,
+ )
+
+ self.ue.antenna.set_external_parameters(
+ adjacent_antenna_model=self.adjacent_antenna_model,
+ frequency=self.frequency,
+ bandwidth=self.bandwidth,
+ )
+
+ self.validate("imt")
diff --git a/sharc/parameters/imt/parameters_imt_mss_dc.py b/sharc/parameters/imt/parameters_imt_mss_dc.py
new file mode 100644
index 000000000..9149ffa97
--- /dev/null
+++ b/sharc/parameters/imt/parameters_imt_mss_dc.py
@@ -0,0 +1,303 @@
+# Parameters for the IMT MSS-DC topology.
+from dataclasses import dataclass, field
+import numpy as np
+import typing
+from pathlib import Path
+import geopandas as gpd
+import shapely as shp
+
+from sharc.support.sharc_geom import shrink_countries_by_km
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_orbit import ParametersOrbit
+
+SHARC_ROOT_DIR = (Path(__file__) / ".." / ".." / ".." / "..").resolve()
+
+
+@dataclass
+class ParametersSectorPositioning(ParametersBase):
+ """Dataclass for sector positioning parameters in the IMT MSS-DC topology."""
+
+ @dataclass
+ class ParametersSectorValue(ParametersBase):
+ @dataclass
+ class ParametersSectorValueDistribution(ParametersBase):
+ min: float = None
+ max: float = None
+
+ __ALLOWED_TYPES = [
+ "FIXED",
+ "~U(MIN,MAX)",
+ "~SQRT(U(0,1))*MAX",
+ ]
+ # # this distribution can be used when you wish to have a uniform area distribution
+ # # over the area of a cone base (circle)
+ # # uniform dist over area of circle is sqrt(U(0,1))*max_radius for radius dist
+ # "~ATAN(SQRT(U(0,1))*TAN(MAX))",
+ type: typing.Literal[
+ "FIXED",
+ "~U(MIN,MAX)",
+ "~SQRT(U(0,1))*MAX",
+ ] = "FIXED"
+
+ MIN_VALUE: float = None
+ MAX_VALUE: float = None
+
+ fixed: float = 0.0
+ distribution: ParametersSectorValueDistribution = field(default_factory=ParametersSectorValueDistribution)
+
+ def validate(self, ctx):
+ if self.type not in self.__ALLOWED_TYPES:
+ raise ValueError(
+ f"{ctx}.type = {self.type} is not one of the accepted values:\n{self.__ALLOWED_TYPES}"
+ )
+ match self.type:
+ case "FIXED":
+ if not (isinstance(self.fixed, float) or isinstance(self.fixed, int)):
+ raise ValueError(f"{ctx}.fixed must be a number")
+ if self.MIN_VALUE is not None:
+ if self.fixed < self.MIN_VALUE:
+ raise ValueError(f"{ctx}.fixed must be at least {self.MIN_VALUE}")
+ if self.MAX_VALUE is not None:
+ if self.fixed > self.MAX_VALUE:
+ raise ValueError(f"{ctx}.fixed must be at least {self.MAX_VALUE}")
+ case "~U(MIN,MAX)":
+ if not (isinstance(self.distribution.min, float) or isinstance(self.distribution.max, int)):
+ raise ValueError(f"{ctx}.distribution.min must be a number")
+
+ if not (isinstance(self.distribution.max, float) or isinstance(self.distribution.max, int)):
+ raise ValueError(f"{ctx}.distribution.max must be a number")
+
+ if self.distribution.max <= self.distribution.min:
+ raise ValueError(f"{ctx}.distribution.max must be bigger than {ctx}.distribution.max")
+
+ if self.MIN_VALUE is not None:
+ if self.distribution.min < self.MIN_VALUE:
+ raise ValueError(f"{ctx}.distribution.min must be at least {self.MIN_VALUE}")
+ if self.distribution.max < self.MIN_VALUE:
+ raise ValueError(f"{ctx}.distribution.max must be at least {self.MIN_VALUE}")
+
+ if self.MAX_VALUE is not None:
+ if self.distribution.min > self.MAX_VALUE:
+ raise ValueError(f"{ctx}.distribution.min must be at least {self.MAX_VALUE}")
+ if self.distribution.max > self.MAX_VALUE:
+ raise ValueError(f"{ctx}.distribution.max must be at least {self.MAX_VALUE}")
+ case _:
+ raise NotImplementedError(
+ f"No validation implemented for {ctx}.type = {self.type}"
+ )
+
+ __ALLOWED_TYPES = [
+ "ANGLE_FROM_SUBSATELLITE",
+ "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE",
+ ]
+
+ type: typing.Literal[
+ "ANGLE_FROM_SUBSATELLITE",
+ "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE",
+ ] = "ANGLE_FROM_SUBSATELLITE"
+
+ # theta is the off axis angle from satellite nadir
+ angle_from_subsatellite_theta: ParametersSectorValue = field(
+ default_factory=lambda: ParametersSectorPositioning.ParametersSectorValue()
+ )
+
+ # phi completes polar coordinates
+ # equivalent to "azimuth" from subsatellite in earth plane
+ angle_from_subsatellite_phi: ParametersSectorValue = field(
+ default_factory=lambda: ParametersSectorPositioning.ParametersSectorValue(MIN_VALUE=-180.0, MAX_VALUE=180.0)
+ )
+
+ # distance from subsatellite. Substitutes theta
+ distance_from_subsatellite: ParametersSectorValue = field(
+ default_factory=lambda: ParametersSectorPositioning.ParametersSectorValue(MIN_VALUE=0.0)
+ )
+
+ def validate(self, ctx):
+ if self.type not in self.__ALLOWED_TYPES:
+ raise ValueError(
+ f"{ctx}.type = {self.type} is not one of the accepted values:\n{self.__ALLOWED_TYPES}"
+ )
+ match self.type:
+ case "ANGLE_FROM_SUBSATELLITE":
+ self.angle_from_subsatellite_theta.validate(f"{ctx}.angle_from_subsatellite_theta")
+ self.angle_from_subsatellite_phi.validate(f"{ctx}.angle_from_subsatellite_phi")
+ case "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE":
+ self.angle_from_subsatellite_theta.validate(f"{ctx}.angle_from_subsatellite_theta")
+ case _:
+ raise NotImplementedError(
+ f"No validation implemented for {ctx}.type = {self.type}"
+ )
+
+
+@dataclass
+class ParametersSelectActiveSatellite(ParametersBase):
+ @dataclass
+ class ParametersLatLongInsideCountry(ParametersBase):
+ country_shapes_filename: Path = \
+ SHARC_ROOT_DIR / "sharc" / "data" / "countries" / "ne_110m_admin_0_countries.shp"
+
+ # may load automatically for different shapefiles
+ __ALLOWED_COUNTRY_NAMES = []
+
+ __ALLOWED_COORDINATE_REFERENCES = [
+ # it is a WGS84 based coordinate system that only contains (lat, long) information
+ # which should be more than enough to describe any country borders or desired shape
+ "EPSG:4326"
+ ]
+
+ country_names: list[str] = field(default_factory=lambda: list([""]))
+ # margin from inside of border [km]
+ # if positive, makes border smaller by x km
+ # if negative, makes border bigger by x km
+ margin_from_border: float = 0.0
+
+ # geometry after file processing
+ filter_polygon = None
+
+ already_validated = False
+
+ def validate(self, ctx):
+ if self.already_validated:
+ return
+ self.already_validated = True
+
+ # conditional is weird due to suboptimal way of working with nested array parameters
+ if len(self.country_names) == 1 and self.country_names[0] == "":
+ raise ValueError(f"You need to pass at least one country name to {ctx}.country_names")
+
+ f = gpd.read_file(self.country_shapes_filename, columns=["NAME"])
+ if f.geometry.crs not in self.__ALLOWED_COORDINATE_REFERENCES:
+ raise ValueError(
+ f"Shapefile at {ctx}.country_shapes_filename = {self.country_shapes_filename}\n"
+ f"does not use one of the allowed formats {self.__ALLOWED_COORDINATE_REFERENCES},"
+ "with points as (lat, long).\n"
+ "If for some reason you really want to use another projection for this parameter\n"
+ "Add the projection so that this error isn't triggered"
+ )
+ if "NAME" not in f:
+ raise ValueError(
+ f"Shapefile at {ctx}.country_shapes_filename = {self.country_shapes_filename}\n"
+ "does not contains a 'NAME' column, so it cannot be read"
+ )
+ self.__ALLOWED_COUNTRY_NAMES = list(f["NAME"])
+
+ for country_name in self.country_names:
+ if country_name not in self.__ALLOWED_COUNTRY_NAMES:
+ raise ValueError(
+ f"{ctx}.country_names has {country_name} but shapefile only contains data on\n"
+ f"{self.__ALLOWED_COUNTRY_NAMES}"
+ )
+
+ filtered_gdf = f[f["NAME"].isin(self.country_names)]
+
+ # shrink countries and unite
+ # them into a single MultiPolygon
+ self.filter_polygon = shp.ops.unary_union(shrink_countries_by_km(
+ filtered_gdf.geometry.values, self.margin_from_border
+ ))
+
+ __ALLOWED_CONDITIONS = [
+ "LAT_LONG_INSIDE_COUNTRY",
+ "MINIMUM_ELEVATION_FROM_ES",
+ "MAXIMUM_ELEVATION_FROM_ES",
+ ]
+
+ conditions: list[typing.Literal[
+ "LAT_LONG_INSIDE_COUNTRY",
+ "MINIMUM_ELEVATION_FROM_ES",
+ "MAXIMUM_ELEVATION_FROM_ES",
+ ]] = field(default_factory=lambda: list([""]))
+
+ minimum_elevation_from_es: float = None
+
+ maximum_elevation_from_es: float = None
+
+ lat_long_inside_country: ParametersLatLongInsideCountry = field(default_factory=ParametersLatLongInsideCountry)
+
+ def validate(self, ctx):
+ if "LAT_LONG_INSIDE_COUNTRY" in self.conditions:
+ self.lat_long_inside_country.validate(f"{ctx}.lat_long_inside_country")
+
+ if "MINIMUM_ELEVATION_FROM_ES" in self.conditions:
+ if not isinstance(self.minimum_elevation_from_es, float) and not isinstance(self.minimum_elevation_from_es, int):
+ raise ValueError(
+ f"{ctx}.minimum_elevation_from_es is not a number!"
+ )
+ if not (self.minimum_elevation_from_es >= 0 and self.minimum_elevation_from_es < 90):
+ raise ValueError(
+ f"{ctx}.minimum_elevation_from_es needs to be a number in interval [0, 90]"
+ )
+
+ if "MAXIMUM_ELEVATION_FROM_ES" in self.conditions:
+ if not isinstance(self.maximum_elevation_from_es, float) and not isinstance(self.maximum_elevation_from_es, int):
+ raise ValueError(
+ f"{ctx}.maximum_elevation_from_es is not a number!"
+ )
+ if not (self.maximum_elevation_from_es >= 0 and self.maximum_elevation_from_es < 90):
+ raise ValueError(
+ f"{ctx}.maximum_elevation_from_es needs to be a number in interval [0, 90]"
+ )
+ if "MINIMUM_ELEVATION_FROM_ES" in self.conditions:
+ if self.maximum_elevation_from_es < self.minimum_elevation_from_es:
+ raise ValueError(
+ f"{ctx}.maximum_elevation_from_es needs to be >= {ctx}.minimum_elevation_from_es"
+ )
+
+ if len(self.conditions) == 1 and self.conditions[0] == "":
+ self.conditions.pop()
+
+ if any(cond not in self.__ALLOWED_CONDITIONS for cond in self.conditions):
+ raise ValueError(
+ f"{ctx}.conditions = {self.conditions}\n"
+ f"However, only the following are allowed: {self.__ALLOWED_CONDITIONS}"
+ )
+
+ if len(set(self.conditions)) != len(self.conditions):
+ raise ValueError(
+ f"{ctx}.conditions = {self.conditions}\n"
+ "And it contains duplicate values!"
+ )
+
+
+@dataclass
+class ParametersImtMssDc(ParametersBase):
+ """Dataclass for the IMT MSS-DC topology parameters."""
+ section_name: str = "imt_mss_dc"
+
+ nested_parameters_enabled = True
+
+ # MSS_D2D system name
+ name: str = "SystemA"
+
+ # Orbit parameters
+ orbits: list[ParametersOrbit] = field(default_factory=lambda: [ParametersOrbit()])
+
+ # Number of beams
+ num_beams: int = 19
+
+ # Beam radius in meters
+ # The beam radius should be calculated based on the Antenna Pattern used for IMT Space Stations
+ beam_radius: float = 36516.0
+
+ sat_is_active_if: ParametersSelectActiveSatellite = field(default_factory=ParametersSelectActiveSatellite)
+
+ center_beam_positioning: ParametersSectorPositioning = field(default_factory=ParametersSectorPositioning)
+
+ def validate(self, ctx: str):
+ """
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ # Now do the sanity check for some parameters
+ if self.num_beams not in [1, 7, 19]:
+ raise ValueError(f"{ctx}.num_beams: Invalid number of sectors {self.num_sectors}")
+
+ if self.beam_radius <= 0:
+ raise ValueError(f"{ctx}.beam_radius: cell_radius must be greater than 0, but is {self.cell_radius}")
+ else:
+ self.cell_radius = self.beam_radius
+ self.intersite_distance = np.sqrt(3) * self.cell_radius
+
+ super().validate(ctx)
diff --git a/sharc/parameters/imt/parameters_imt_topology.py b/sharc/parameters/imt/parameters_imt_topology.py
new file mode 100644
index 000000000..618a318d2
--- /dev/null
+++ b/sharc/parameters/imt/parameters_imt_topology.py
@@ -0,0 +1,49 @@
+import typing
+from dataclasses import field, dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.imt.parameters_hotspot import ParametersHotspot
+from sharc.parameters.imt.parameters_indoor import ParametersIndoor
+from sharc.parameters.imt.parameters_macrocell import ParametersMacrocell
+from sharc.parameters.imt.parameters_ntn import ParametersNTN
+from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc
+from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS
+
+
+@dataclass
+class ParametersImtTopology(ParametersBase):
+ nested_parameters_enabled = True
+
+ type: typing.Literal[
+ "MACROCELL", "HOTSPOT", "INDOOR", "SINGLE_BS", "NTN", "MSS_DC"
+ ] = "MACROCELL"
+
+ # these parameters are needed in case the other system requires coordinate
+ # transformation
+ central_latitude: float = None
+ central_longitude: float = None
+ central_altitude: float = None
+
+ macrocell: ParametersMacrocell = field(default_factory=ParametersMacrocell)
+ hotspot: ParametersHotspot = field(default_factory=ParametersHotspot)
+ indoor: ParametersIndoor = field(default_factory=ParametersIndoor)
+ single_bs: ParametersSingleBS = field(default_factory=ParametersSingleBS)
+ ntn: ParametersNTN = field(default_factory=ParametersNTN)
+ mss_dc: ParametersImtMssDc = field(default_factory=ParametersImtMssDc)
+
+ def validate(self, ctx):
+ match self.type:
+ case "MACROCELL":
+ self.macrocell.validate(f"{ctx}.macrocell")
+ case "HOTSPOT":
+ self.hotspot.validate(f"{ctx}.hotspot")
+ case "INDOOR":
+ self.indoor.validate(f"{ctx}.indoor")
+ case "SINGLE_BS":
+ self.single_bs.validate(f"{ctx}.single_bs")
+ case "NTN":
+ self.ntn.validate(f"{ctx}.ntn")
+ case "MSS_DC":
+ self.mss_dc.validate(f"{ctx}.mss_dc")
+ case _:
+ raise NotImplementedError(f"{ctx}.type == '{self.type}' may not be implemented")
diff --git a/sharc/parameters/imt/parameters_indoor.py b/sharc/parameters/imt/parameters_indoor.py
new file mode 100644
index 000000000..2763ee458
--- /dev/null
+++ b/sharc/parameters/imt/parameters_indoor.py
@@ -0,0 +1,34 @@
+from dataclasses import dataclass
+import typing
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersIndoor(ParametersBase):
+ # Basic path loss model for indoor topology. Possible values:
+ # "FSPL" (free-space path loss),
+ # "INH_OFFICE" (3GPP Indoor Hotspot - Office)
+ basic_path_loss: typing.Literal["INH_OFFICE", "FSPL"] = "INH_OFFICE"
+ # Number of rows of buildings in the simulation scenario
+ n_rows: int = 3
+ # Number of colums of buildings in the simulation scenario
+ n_colums: int = 2
+ # Number of buildings containing IMT stations. Options:
+ # 'ALL': all buildings contain IMT stations.
+ # An integer representing the number of buildings.
+ num_imt_buildings: typing.Union[typing.Literal["ALL"], int] = "ALL"
+ # Street width (building separation) [m]
+ street_width: int = 30
+ # Intersite distance [m]
+ intersite_distance: int = 40
+ # Number of cells per floor
+ num_cells: int = 3
+ # Number of floors per building
+ num_floors: int = 1
+ # Percentage of indoor UE's [0, 1]
+ ue_indoor_percent: int = .95
+ # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
+ building_class: typing.Literal[
+ "TRADITIONAL", "THERMALLY_EFFICIENT"
+ ] = "TRADITIONAL"
diff --git a/sharc/parameters/imt/parameters_macrocell.py b/sharc/parameters/imt/parameters_macrocell.py
new file mode 100644
index 000000000..3345019c5
--- /dev/null
+++ b/sharc/parameters/imt/parameters_macrocell.py
@@ -0,0 +1,17 @@
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersMacrocell(ParametersBase):
+ intersite_distance: int = None
+ wrap_around: bool = False
+ num_clusters: int = 1
+
+ def validate(self, ctx):
+ if not isinstance(self.intersite_distance, int) and not isinstance(self.intersite_distance, float):
+ raise ValueError(f"{ctx}.intersite_distance should be a number")
+
+ if self.num_clusters not in [1, 7]:
+ raise ValueError(f"{ctx}.num_clusters should be either 1 or 7")
diff --git a/sharc/parameters/imt/parameters_ntn.py b/sharc/parameters/imt/parameters_ntn.py
new file mode 100644
index 000000000..91296077a
--- /dev/null
+++ b/sharc/parameters/imt/parameters_ntn.py
@@ -0,0 +1,105 @@
+# NOTE: some parameters are without use in implementation as of this commit
+# Some parameters should probably go to the BS geometry definition
+# instead of being ntn only. Need to ensure the parameters make sense
+# to other topologies as well, or validate that those parameters
+# can't be set if another topology is chosen
+
+from dataclasses import dataclass
+import numpy as np
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersNTN(ParametersBase):
+ """
+ Simulation parameters for NTN network topology.
+ """
+ section_name: str = "ntn"
+
+ # NTN Airborne Platform height (m)
+ bs_height: float = None
+
+ # NTN cell radius in network topology [m]
+ cell_radius: float = 90000
+
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ # @important: for NTN, intersite distance means something different than normally,
+ # since the BS's are placed at center of hexagons
+ intersite_distance: float = None
+
+ # BS azimuth
+ # TODO: Put this elsewhere (in a bs.geometry for example) if needed by another model
+ bs_azimuth: float = 45
+ # BS elevation
+ bs_elevation: float = 90
+
+ # Number of sectors
+ num_sectors: int = 7
+
+ # TODO: implement the below parameters in the simulator. They are currently not used
+ # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2
+ bs_backoff_power: int = 3
+
+ # NTN Antenna configuration
+ bs_n_rows_layer1: int = 2
+ bs_n_columns_layer1: int = 2
+ bs_n_rows_layer2: int = 4
+ bs_n_columns_layer2: int = 2
+
+ def load_subparameters(self, ctx: str, params: dict, quiet=True):
+ super().load_subparameters(ctx, params, quiet)
+
+ if self.cell_radius is not None and self.intersite_distance is not None:
+ raise ValueError(f"You cannot set both {ctx}.intersite_distance and {ctx}.cell_radius.")
+
+ if self.cell_radius is not None:
+ self.intersite_distance = self.cell_radius * np.sqrt(3)
+
+ if self.intersite_distance is not None:
+ self.cell_radius = self.intersite_distance / np.sqrt(3)
+
+ def set_external_parameters(self, *, bs_height: float):
+ """
+ This method is used to "propagate" parameters from external context
+ to the values required by ntn topology. It's not ideal, but it's done
+ this way until we decide on a better way to model context.
+ """
+ self.bs_height = bs_height
+
+ def validate(self, ctx: str):
+ # Now do the sanity check for some parameters
+ if self.num_sectors not in [1, 7, 19]:
+ raise ValueError(
+ f"ParametersNTN: Invalid number of sectors {self.num_sectors}",
+ )
+
+ if self.bs_height <= 0:
+ raise ValueError(
+ f"ParametersNTN: bs_height must be greater than 0, but is {self.bs_height}",
+ )
+
+ if self.cell_radius <= 0:
+ raise ValueError(
+ f"ParametersNTN: cell_radius must be greater than 0, but is {self.cell_radius}",
+ )
+
+ if self.intersite_distance <= 0:
+ raise ValueError(
+ f"ParametersNTN: intersite_distance must be greater than 0, but is {self.intersite_distance}",
+ )
+
+ if not isinstance(self.bs_backoff_power, int) or self.bs_backoff_power < 0:
+ raise ValueError(
+ f"ParametersNTN: bs_backoff_power must be a non-negative integer, but is {self.bs_backoff_power}",
+ )
+
+ if not np.all((0 <= self.bs_azimuth) & (self.bs_azimuth <= 360)):
+ raise ValueError(
+ "ParametersNTN: bs_azimuth values must be between 0 and 360 degrees",
+ )
+
+ if not np.all((0 <= self.bs_elevation) & (self.bs_elevation <= 90)):
+ raise ValueError(
+ "ParametersNTN: bs_elevation values must be between 0 and 90 degrees",
+ )
diff --git a/sharc/parameters/imt/parameters_single_bs.py b/sharc/parameters/imt/parameters_single_bs.py
new file mode 100644
index 000000000..16350f047
--- /dev/null
+++ b/sharc/parameters/imt/parameters_single_bs.py
@@ -0,0 +1,41 @@
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersSingleBS(ParametersBase):
+ intersite_distance: int = None
+ cell_radius: int = None
+ num_clusters: int = 1
+
+ def load_subparameters(self, ctx: str, params: dict, quiet=True):
+ super().load_subparameters(ctx, params, quiet)
+
+ if self.intersite_distance is not None and self.cell_radius is not None:
+ raise ValueError(f"{ctx}.intersite_distance and {ctx}.cell_radius should not be set at the same time")
+
+ if self.intersite_distance is not None:
+ self.cell_radius = self.intersite_distance * 2 / 3
+ if self.cell_radius is not None:
+ self.intersite_distance = self.cell_radius * 3 / 2
+
+ def validate(self, ctx):
+ if None in [self.intersite_distance, self.cell_radius]:
+ raise ValueError(f"{ctx}.intersite_distance and cell_radius should be set.\
+ One of them through the parameters, the other inferred")
+
+ if self.num_clusters not in [1, 2]:
+ raise ValueError(f"{ctx}.num_clusters should either be 1 or 2")
+
+
+if __name__ == "__main__":
+ # Run validation for input parameters
+ import os
+ import pprint
+
+ # Load default simulator parameters
+ yaml_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../input/parameters.yaml")
+ params = ParametersSingleBS()
+ params.load_parameters_from_file(yaml_file_path, quiet=False)
+ pprint.pprint(params)
diff --git a/sharc/parameters/parameters.py b/sharc/parameters/parameters.py
index 07275b273..9dc022cf3 100644
--- a/sharc/parameters/parameters.py
+++ b/sharc/parameters/parameters.py
@@ -5,20 +5,23 @@
@author: edgar
"""
-import configparser
+import sys
+import os
from sharc.parameters.parameters_general import ParametersGeneral
-from sharc.parameters.parameters_imt import ParametersImt
-from sharc.parameters.parameters_hotspot import ParametersHotspot
-from sharc.parameters.parameters_indoor import ParametersIndoor
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
-from sharc.parameters.parameters_eess_passive import ParametersEessPassive
+from sharc.parameters.imt.parameters_imt import ParametersImt
+from sharc.parameters.parameters_eess_ss import ParametersEessSS
from sharc.parameters.parameters_fs import ParametersFs
+from sharc.parameters.parameters_metsat_ss import ParametersMetSatSS
from sharc.parameters.parameters_fss_ss import ParametersFssSs
from sharc.parameters.parameters_fss_es import ParametersFssEs
from sharc.parameters.parameters_haps import ParametersHaps
from sharc.parameters.parameters_rns import ParametersRns
from sharc.parameters.parameters_ras import ParametersRas
+from sharc.parameters.parameters_single_earth_station import ParametersSingleEarthStation
+from sharc.parameters.parameters_mss_ss import ParametersMssSs
+from sharc.parameters.parameters_mss_d2d import ParametersMssD2d
+from sharc.parameters.parameters_single_space_station import ParametersSingleSpaceStation
class Parameters(object):
@@ -31,331 +34,109 @@ def __init__(self):
self.general = ParametersGeneral()
self.imt = ParametersImt()
- self.antenna_imt = ParametersAntennaImt()
- self.hotspot = ParametersHotspot()
- self.indoor = ParametersIndoor()
- self.eess_passive = ParametersEessPassive()
+ self.eess_ss = ParametersEessSS()
self.fs = ParametersFs()
self.fss_ss = ParametersFssSs()
self.fss_es = ParametersFssEs()
self.haps = ParametersHaps()
self.rns = ParametersRns()
self.ras = ParametersRas()
-
+ self.single_earth_station = ParametersSingleEarthStation()
+ self.single_space_station = ParametersSingleSpaceStation()
+ self.metsat_ss = ParametersMetSatSS()
+ self.mss_ss = ParametersMssSs()
+ self.mss_d2d = ParametersMssD2d()
def set_file_name(self, file_name: str):
- self.file_name = file_name
+ """sets the configuration file name
+ Parameters
+ ----------
+ file_name : str
+ configuration file path
+ """
+ self.file_name = file_name
def read_params(self):
- config = configparser.ConfigParser()
- config.read(self.file_name)
+ """Read the parameters from the config file
+ """
+ if not os.path.isfile(self.file_name):
+ err_msg = f"PARAMETER ERROR [{self.__class__.__name__}]: \
+ Could not find the configuration file {self.file_name}"
+ sys.stderr.write(err_msg)
+ sys.exit(1)
#######################################################################
# GENERAL
#######################################################################
- self.general.num_snapshots = config.getint("GENERAL", "num_snapshots")
- self.general.imt_link = config.get("GENERAL", "imt_link")
- self.general.system = config.get("GENERAL", "system")
- self.general.enable_cochannel = config.getboolean("GENERAL", "enable_cochannel")
- self.general.enable_adjacent_channel = config.getboolean("GENERAL", "enable_adjacent_channel")
- self.general.seed = config.getint("GENERAL", "seed")
- self.general.overwrite_output = config.getboolean("GENERAL", "overwrite_output")
-
+ self.general.load_parameters_from_file(self.file_name)
#######################################################################
# IMT
#######################################################################
- self.imt.topology = config.get("IMT", "topology")
- self.imt.wrap_around = config.getboolean("IMT", "wrap_around")
- self.imt.num_clusters = config.getint("IMT", "num_clusters")
- self.imt.intersite_distance = config.getfloat("IMT", "intersite_distance")
- self.imt.minimum_separation_distance_bs_ue = config.getfloat("IMT", "minimum_separation_distance_bs_ue")
- self.imt.interfered_with = config.getboolean("IMT", "interfered_with")
- self.imt.frequency = config.getfloat("IMT", "frequency")
- self.imt.bandwidth = config.getfloat("IMT", "bandwidth")
- self.imt.rb_bandwidth = config.getfloat("IMT", "rb_bandwidth")
- self.imt.spectral_mask = config.get("IMT", "spectral_mask")
- self.imt.spurious_emissions = config.getfloat("IMT", "spurious_emissions")
- self.imt.guard_band_ratio = config.getfloat("IMT", "guard_band_ratio")
- self.imt.bs_load_probability = config.getfloat("IMT", "bs_load_probability")
- self.imt.bs_conducted_power = config.getfloat("IMT", "bs_conducted_power")
- self.imt.bs_height = config.getfloat("IMT", "bs_height")
- self.imt.bs_noise_figure = config.getfloat("IMT", "bs_noise_figure")
- self.imt.bs_noise_temperature = config.getfloat("IMT", "bs_noise_temperature")
- self.imt.bs_ohmic_loss = config.getfloat("IMT", "bs_ohmic_loss")
- self.imt.ul_attenuation_factor = config.getfloat("IMT", "ul_attenuation_factor")
- self.imt.ul_sinr_min = config.getfloat("IMT", "ul_sinr_min")
- self.imt.ul_sinr_max = config.getfloat("IMT", "ul_sinr_max")
- self.imt.ue_k = config.getint("IMT", "ue_k")
- self.imt.ue_k_m = config.getint("IMT", "ue_k_m")
- self.imt.ue_indoor_percent = config.getfloat("IMT", "ue_indoor_percent")
- self.imt.ue_distribution_type = config.get("IMT", "ue_distribution_type")
- self.imt.ue_distribution_distance = config.get("IMT", "ue_distribution_distance")
- self.imt.ue_distribution_azimuth = config.get("IMT", "ue_distribution_azimuth")
- self.imt.ue_tx_power_control = config.get("IMT", "ue_tx_power_control")
- self.imt.ue_p_o_pusch = config.getfloat("IMT", "ue_p_o_pusch")
- self.imt.ue_alpha = config.getfloat("IMT", "ue_alpha")
- self.imt.ue_p_cmax = config.getfloat("IMT", "ue_p_cmax")
- self.imt.ue_power_dynamic_range = config.getfloat("IMT", "ue_power_dynamic_range")
- self.imt.ue_height = config.getfloat("IMT", "ue_height")
- self.imt.ue_noise_figure = config.getfloat("IMT", "ue_noise_figure")
- self.imt.ue_ohmic_loss = config.getfloat("IMT", "ue_ohmic_loss")
- self.imt.ue_body_loss = config.getfloat("IMT", "ue_body_loss")
- self.imt.dl_attenuation_factor = config.getfloat("IMT", "dl_attenuation_factor")
- self.imt.dl_sinr_min = config.getfloat("IMT", "dl_sinr_min")
- self.imt.dl_sinr_max = config.getfloat("IMT", "dl_sinr_max")
- self.imt.channel_model = config.get("IMT", "channel_model")
- self.imt.los_adjustment_factor = config.getfloat("IMT", "los_adjustment_factor")
- self.imt.shadowing = config.getboolean("IMT", "shadowing")
- self.imt.noise_temperature = config.getfloat("IMT", "noise_temperature")
- self.imt.BOLTZMANN_CONSTANT = config.getfloat("IMT", "BOLTZMANN_CONSTANT")
+ self.imt.load_parameters_from_file(self.file_name)
#######################################################################
- # IMT ANTENNA
+ # FSS space station
#######################################################################
- self.antenna_imt.adjacent_antenna_model = config.get("IMT_ANTENNA", "adjacent_antenna_model")
- self.antenna_imt.bs_normalization = config.getboolean("IMT_ANTENNA", "bs_normalization")
- self.antenna_imt.ue_normalization = config.getboolean("IMT_ANTENNA", "ue_normalization")
- self.antenna_imt.bs_normalization_file = config.get("IMT_ANTENNA", "bs_normalization_file")
- self.antenna_imt.ue_normalization_file = config.get("IMT_ANTENNA", "ue_normalization_file")
- self.antenna_imt.bs_element_pattern = config.get("IMT_ANTENNA", "bs_element_pattern")
- self.antenna_imt.ue_element_pattern = config.get("IMT_ANTENNA", "ue_element_pattern")
-
- self.antenna_imt.bs_element_max_g = config.getfloat("IMT_ANTENNA", "bs_element_max_g")
- self.antenna_imt.bs_element_phi_3db = config.getfloat("IMT_ANTENNA", "bs_element_phi_3db")
- self.antenna_imt.bs_element_theta_3db = config.getfloat("IMT_ANTENNA", "bs_element_theta_3db")
- self.antenna_imt.bs_element_am = config.getfloat("IMT_ANTENNA", "bs_element_am")
- self.antenna_imt.bs_element_sla_v = config.getfloat("IMT_ANTENNA", "bs_element_sla_v")
- self.antenna_imt.bs_n_rows = config.getfloat("IMT_ANTENNA", "bs_n_rows")
- self.antenna_imt.bs_n_columns = config.getfloat("IMT_ANTENNA", "bs_n_columns")
- self.antenna_imt.bs_element_horiz_spacing = config.getfloat("IMT_ANTENNA", "bs_element_horiz_spacing")
- self.antenna_imt.bs_element_vert_spacing = config.getfloat("IMT_ANTENNA", "bs_element_vert_spacing")
- self.antenna_imt.bs_multiplication_factor = config.getfloat("IMT_ANTENNA", "bs_multiplication_factor")
- self.antenna_imt.bs_minimum_array_gain = config.getfloat("IMT_ANTENNA", "bs_minimum_array_gain")
-
- self.antenna_imt.ue_element_max_g = config.getfloat("IMT_ANTENNA", "ue_element_max_g")
- self.antenna_imt.ue_element_phi_3db = config.getfloat("IMT_ANTENNA", "ue_element_phi_3db")
- self.antenna_imt.ue_element_theta_3db = config.getfloat("IMT_ANTENNA", "ue_element_theta_3db")
- self.antenna_imt.ue_element_am = config.getfloat("IMT_ANTENNA", "ue_element_am")
- self.antenna_imt.ue_element_sla_v = config.getfloat("IMT_ANTENNA", "ue_element_sla_v")
- self.antenna_imt.ue_n_rows = config.getfloat("IMT_ANTENNA", "ue_n_rows")
- self.antenna_imt.ue_n_columns = config.getfloat("IMT_ANTENNA", "ue_n_columns")
- self.antenna_imt.ue_element_horiz_spacing = config.getfloat("IMT_ANTENNA", "ue_element_horiz_spacing")
- self.antenna_imt.ue_element_vert_spacing = config.getfloat("IMT_ANTENNA", "ue_element_vert_spacing")
- self.antenna_imt.ue_multiplication_factor = config.getfloat("IMT_ANTENNA", "ue_multiplication_factor")
- self.antenna_imt.ue_minimum_array_gain = config.getfloat("IMT_ANTENNA", "ue_minimum_array_gain")
-
- self.antenna_imt.bs_downtilt = config.getfloat("IMT_ANTENNA", "bs_downtilt")
+ self.fss_ss.load_parameters_from_file(self.file_name)
#######################################################################
- # HOTSPOT
+ # FSS earth station
#######################################################################
- self.hotspot.num_hotspots_per_cell = config.getint("HOTSPOT", "num_hotspots_per_cell")
- self.hotspot.max_dist_hotspot_ue = config.getfloat("HOTSPOT", "max_dist_hotspot_ue")
- self.hotspot.min_dist_bs_hotspot = config.getfloat("HOTSPOT", "min_dist_bs_hotspot")
+ self.fss_es.load_parameters_from_file(self.file_name)
#######################################################################
- # INDOOR
+ # Fixed wireless service
#######################################################################
- self.indoor.basic_path_loss = config.get("INDOOR", "basic_path_loss")
- self.indoor.n_rows = config.getint("INDOOR", "n_rows")
- self.indoor.n_colums = config.getint("INDOOR", "n_colums")
- self.indoor.num_imt_buildings = config.get("INDOOR", "num_imt_buildings")
- self.indoor.street_width = config.getint("INDOOR", "street_width")
- self.indoor.intersite_distance = config.getfloat("INDOOR", "intersite_distance")
- self.indoor.num_cells = config.getint("INDOOR", "num_cells")
- self.indoor.num_floors = config.getint("INDOOR", "num_floors")
- self.indoor.ue_indoor_percent = config.getfloat("INDOOR", "ue_indoor_percent")
- self.indoor.building_class = config.get("INDOOR", "building_class")
+ self.fs.load_parameters_from_file(self.file_name)
#######################################################################
- # FSS space station
+ # HAPS (airbone) station
#######################################################################
- self.fss_ss.frequency = config.getfloat("FSS_SS", "frequency")
- self.fss_ss.bandwidth = config.getfloat("FSS_SS", "bandwidth")
- self.fss_ss.tx_power_density = config.getfloat("FSS_SS", "tx_power_density")
- self.fss_ss.altitude = config.getfloat("FSS_SS", "altitude")
- self.fss_ss.lat_deg = config.getfloat("FSS_SS", "lat_deg")
- self.fss_ss.elevation = config.getfloat("FSS_SS", "elevation")
- self.fss_ss.azimuth = config.getfloat("FSS_SS", "azimuth")
- self.fss_ss.noise_temperature = config.getfloat("FSS_SS", "noise_temperature")
- self.fss_ss.adjacent_ch_selectivity = config.getfloat("FSS_SS", "adjacent_ch_selectivity")
- self.fss_ss.antenna_gain = config.getfloat("FSS_SS", "antenna_gain")
- self.fss_ss.antenna_pattern = config.get("FSS_SS", "antenna_pattern")
- self.fss_ss.imt_altitude = config.getfloat("FSS_SS", "imt_altitude")
- self.fss_ss.imt_lat_deg = config.getfloat("FSS_SS", "imt_lat_deg")
- self.fss_ss.imt_long_diff_deg = config.getfloat("FSS_SS", "imt_long_diff_deg")
- self.fss_ss.season = config.get("FSS_SS", "season")
- self.fss_ss.channel_model = config.get("FSS_SS", "channel_model")
- self.fss_ss.antenna_l_s = config.getfloat("FSS_SS", "antenna_l_s")
- self.fss_ss.antenna_3_dB = config.getfloat("FSS_SS", "antenna_3_dB")
- self.fss_ss.BOLTZMANN_CONSTANT = config.getfloat("FSS_SS", "BOLTZMANN_CONSTANT")
- self.fss_ss.EARTH_RADIUS = config.getfloat("FSS_SS", "EARTH_RADIUS")
+ self.haps.load_parameters_from_file(self.file_name)
#######################################################################
- # FSS earth station
+ # RNS
#######################################################################
- self.fss_es.location = config.get("FSS_ES", "location")
- self.fss_es.x = config.getfloat("FSS_ES", "x")
- self.fss_es.y = config.getfloat("FSS_ES", "y")
- self.fss_es.min_dist_to_bs = config.getfloat("FSS_ES", "min_dist_to_bs")
- self.fss_es.max_dist_to_bs = config.getfloat("FSS_ES", "max_dist_to_bs")
- self.fss_es.height = config.getfloat("FSS_ES", "height")
- self.fss_es.elevation_min = config.getfloat("FSS_ES", "elevation_min")
- self.fss_es.elevation_max = config.getfloat("FSS_ES", "elevation_max")
- self.fss_es.azimuth = config.get("FSS_ES", "azimuth")
- self.fss_es.frequency = config.getfloat("FSS_ES", "frequency")
- self.fss_es.bandwidth = config.getfloat("FSS_ES", "bandwidth")
- self.fss_es.adjacent_ch_selectivity = config.getfloat("FSS_ES", "adjacent_ch_selectivity")
- self.fss_es.tx_power_density = config.getfloat("FSS_ES", "tx_power_density")
- self.fss_es.noise_temperature = config.getfloat("FSS_ES", "noise_temperature")
- self.fss_es.antenna_gain = config.getfloat("FSS_ES", "antenna_gain")
- self.fss_es.antenna_pattern = config.get("FSS_ES", "antenna_pattern")
- self.fss_es.antenna_envelope_gain = config.getfloat("FSS_ES", "antenna_envelope_gain")
- self.fss_es.diameter = config.getfloat("FSS_ES", "diameter")
- self.fss_es.channel_model = config.get("FSS_ES", "channel_model")
- self.fss_es.BOLTZMANN_CONSTANT = config.getfloat("FSS_ES", "BOLTZMANN_CONSTANT")
- self.fss_es.EARTH_RADIUS = config.getfloat("FSS_ES", "EARTH_RADIUS")
-
- # P452 parameters
- self.fss_es.atmospheric_pressure = config.getfloat("FSS_ES", "atmospheric_pressure")
- self.fss_es.air_temperature = config.getfloat("FSS_ES", "air_temperature")
- self.fss_es.N0 = config.getfloat("FSS_ES", "N0")
- self.fss_es.delta_N = config.getfloat("FSS_ES", "delta_N")
- self.fss_es.percentage_p = config.get("FSS_ES", "percentage_p")
- self.fss_es.Dct = config.getfloat("FSS_ES", "Dct")
- self.fss_es.Dcr = config.getfloat("FSS_ES", "Dcr")
- self.fss_es.Hte = config.getfloat("FSS_ES", "Hte")
- self.fss_es.Hre = config.getfloat("FSS_ES", "Hre")
- self.fss_es.tx_lat = config.getfloat("FSS_ES", "tx_lat")
- self.fss_es.rx_lat = config.getfloat("FSS_ES", "rx_lat")
- self.fss_es.polarization = config.get("FSS_ES", "polarization")
- self.fss_es.clutter_loss = config.getboolean("FSS_ES", "clutter_loss")
-
- # HDFSS propagation parameters
- self.fss_es.es_position = config.get("FSS_ES", "es_position")
- self.fss_es.shadow_enabled = config.getboolean("FSS_ES", "shadow_enabled")
- self.fss_es.building_loss_enabled = config.getboolean("FSS_ES", "building_loss_enabled")
- self.fss_es.same_building_enabled = config.getboolean("FSS_ES", "same_building_enabled")
- self.fss_es.diffraction_enabled = config.getboolean("FSS_ES", "diffraction_enabled")
- self.fss_es.bs_building_entry_loss_type = config.get("FSS_ES", "bs_building_entry_loss_type")
- self.fss_es.bs_building_entry_loss_prob = config.getfloat("FSS_ES", "bs_building_entry_loss_prob")
- self.fss_es.bs_building_entry_loss_value = config.getfloat("FSS_ES", "bs_building_entry_loss_value")
+ self.rns.load_parameters_from_file(self.file_name)
#######################################################################
- # Fixed wireless service
+ # RAS station
#######################################################################
- self.fs.x = config.getfloat("FS", "x")
- self.fs.y = config.getfloat("FS", "y")
- self.fs.height = config.getfloat("FS", "height")
- self.fs.elevation = config.getfloat("FS", "elevation")
- self.fs.azimuth = config.getfloat("FS", "azimuth")
- self.fs.frequency = config.getfloat("FS", "frequency")
- self.fs.bandwidth = config.getfloat("FS", "bandwidth")
- self.fs.noise_temperature = config.getfloat("FS", "noise_temperature")
- self.fs.adjacent_ch_selectivity = config.getfloat("FS", "adjacent_ch_selectivity")
- self.fs.tx_power_density = config.getfloat("FS", "tx_power_density")
- self.fs.antenna_gain = config.getfloat("FS", "antenna_gain")
- self.fs.antenna_pattern = config.get("FS", "antenna_pattern")
- self.fs.diameter = config.getfloat("FS", "diameter")
- self.fs.channel_model = config.get("FS", "channel_model")
- self.fs.BOLTZMANN_CONSTANT = config.getfloat("FS", "BOLTZMANN_CONSTANT")
- self.fs.EARTH_RADIUS = config.getfloat("FS", "EARTH_RADIUS")
+ self.ras.load_parameters_from_file(self.file_name)
#######################################################################
- # HAPS (airbone) station
+ # EESS passive
#######################################################################
- self.haps.frequency = config.getfloat("HAPS", "frequency")
- self.haps.bandwidth = config.getfloat("HAPS", "bandwidth")
- self.haps.antenna_gain = config.getfloat("HAPS", "antenna_gain")
- self.haps.tx_power_density = config.getfloat("HAPS", "eirp_density") - self.haps.antenna_gain - 60
- self.haps.altitude = config.getfloat("HAPS", "altitude")
- self.haps.lat_deg = config.getfloat("HAPS", "lat_deg")
- self.haps.elevation = config.getfloat("HAPS", "elevation")
- self.haps.azimuth = config.getfloat("HAPS", "azimuth")
- self.haps.antenna_pattern = config.get("HAPS", "antenna_pattern")
- self.haps.imt_altitude = config.getfloat("HAPS", "imt_altitude")
- self.haps.imt_lat_deg = config.getfloat("HAPS", "imt_lat_deg")
- self.haps.imt_long_diff_deg = config.getfloat("HAPS", "imt_long_diff_deg")
- self.haps.season = config.get("HAPS", "season")
- self.haps.acs = config.getfloat("HAPS", "acs")
- self.haps.channel_model = config.get("HAPS", "channel_model")
- self.haps.antenna_l_n = config.getfloat("HAPS", "antenna_l_n")
- self.haps.BOLTZMANN_CONSTANT = config.getfloat("HAPS", "BOLTZMANN_CONSTANT")
- self.haps.EARTH_RADIUS = config.getfloat("HAPS", "EARTH_RADIUS")
+ self.eess_ss.load_parameters_from_file(self.file_name)
#######################################################################
- # RNS
+ # Single Earth Station
#######################################################################
- self.rns.x = config.getfloat("RNS", "x")
- self.rns.y = config.getfloat("RNS", "y")
- self.rns.altitude = config.getfloat("RNS", "altitude")
- self.rns.frequency = config.getfloat("RNS", "frequency")
- self.rns.bandwidth = config.getfloat("RNS", "bandwidth")
- self.rns.noise_temperature = config.getfloat("RNS", "noise_temperature")
- self.rns.tx_power_density = config.getfloat("RNS", "tx_power_density")
- self.rns.antenna_gain = config.getfloat("RNS", "antenna_gain")
- self.rns.antenna_pattern = config.get("RNS", "antenna_pattern")
- self.rns.season = config.get("RNS", "season")
- self.rns.imt_altitude = config.getfloat("RNS", "imt_altitude")
- self.rns.imt_lat_deg = config.getfloat("RNS", "imt_lat_deg")
- self.rns.channel_model = config.get("RNS", "channel_model")
- self.rns.acs = config.getfloat("RNS", "acs")
- self.rns.BOLTZMANN_CONSTANT = config.getfloat("RNS", "BOLTZMANN_CONSTANT")
- self.rns.EARTH_RADIUS = config.getfloat("RNS", "EARTH_RADIUS")
+ self.single_earth_station.load_parameters_from_file(self.file_name)
#######################################################################
- # RAS station
+ # MSS_SS
#######################################################################
- self.ras.x = config.getfloat("RAS", "x")
- self.ras.y = config.getfloat("RAS", "y")
- self.ras.height = config.getfloat("RAS", "height")
- self.ras.elevation = config.getfloat("RAS", "elevation")
- self.ras.azimuth = config.getfloat("RAS", "azimuth")
- self.ras.frequency = config.getfloat("RAS", "frequency")
- self.ras.bandwidth = config.getfloat("RAS", "bandwidth")
- self.ras.antenna_noise_temperature = config.getfloat("RAS", "antenna_noise_temperature")
- self.ras.receiver_noise_temperature = config.getfloat("RAS", "receiver_noise_temperature")
- self.ras.adjacent_ch_selectivity = config.getfloat("FSS_ES", "adjacent_ch_selectivity")
- self.ras.antenna_efficiency = config.getfloat("RAS", "antenna_efficiency")
- self.ras.antenna_gain = config.getfloat("RAS", "antenna_gain")
- self.ras.antenna_pattern = config.get("RAS", "antenna_pattern")
- self.ras.diameter = config.getfloat("RAS", "diameter")
- self.ras.channel_model = config.get("RAS", "channel_model")
- self.ras.BOLTZMANN_CONSTANT = config.getfloat("RAS", "BOLTZMANN_CONSTANT")
- self.ras.EARTH_RADIUS = config.getfloat("RAS", "EARTH_RADIUS")
- self.ras.SPEED_OF_LIGHT = config.getfloat("RAS", "SPEED_OF_LIGHT")
-
- # P452 parameters
- self.ras.atmospheric_pressure = config.getfloat("RAS", "atmospheric_pressure")
- self.ras.air_temperature = config.getfloat("RAS", "air_temperature")
- self.ras.N0 = config.getfloat("RAS", "N0")
- self.ras.delta_N = config.getfloat("RAS", "delta_N")
- self.ras.percentage_p = config.get("RAS", "percentage_p")
- self.ras.Dct = config.getfloat("RAS", "Dct")
- self.ras.Dcr = config.getfloat("RAS", "Dcr")
- self.ras.Hte = config.getfloat("RAS", "Hte")
- self.ras.Hre = config.getfloat("RAS", "Hre")
- self.ras.tx_lat = config.getfloat("RAS", "tx_lat")
- self.ras.rx_lat = config.getfloat("RAS", "rx_lat")
- self.ras.polarization = config.get("RAS", "polarization")
- self.ras.clutter_loss = config.getboolean("RAS", "clutter_loss")
+ self.mss_ss.load_parameters_from_file(self.file_name)
#######################################################################
- # EESS passive
+ # MSS_D2d
#######################################################################
- self.eess_passive.frequency = config.getfloat("EESS_PASSIVE", "frequency")
- self.eess_passive.bandwidth = config.getfloat("EESS_PASSIVE", "bandwidth")
- self.eess_passive.nadir_angle = config.getfloat("EESS_PASSIVE", "nadir_angle")
- self.eess_passive.altitude = config.getfloat("EESS_PASSIVE", "altitude")
- self.eess_passive.antenna_pattern = config.get("EESS_PASSIVE", "antenna_pattern")
- self.eess_passive.antenna_efficiency = config.getfloat("EESS_PASSIVE", "antenna_efficiency")
- self.eess_passive.antenna_diameter = config.getfloat("EESS_PASSIVE", "antenna_diameter")
- self.eess_passive.antenna_gain = config.getfloat("EESS_PASSIVE", "antenna_gain")
- self.eess_passive.channel_model = config.get("EESS_PASSIVE", "channel_model")
- self.eess_passive.imt_altitude = config.getfloat("EESS_PASSIVE", "imt_altitude")
- self.eess_passive.imt_lat_deg = config.getfloat("EESS_PASSIVE", "imt_lat_deg")
- self.eess_passive.season = config.get("EESS_PASSIVE", "season")
- self.eess_passive.BOLTZMANN_CONSTANT = config.getfloat("EESS_PASSIVE", "BOLTZMANN_CONSTANT")
- self.eess_passive.EARTH_RADIUS = config.getfloat("EESS_PASSIVE", "EARTH_RADIUS")
+ self.mss_d2d.load_parameters_from_file(self.file_name)
+
+ self.single_space_station.load_parameters_from_file(self.file_name)
+
+
+if __name__ == "__main__":
+ from pprint import pprint
+ parameters = Parameters()
+ param_sections = [
+ a for a in dir(parameters) if not a.startswith('__') and not
+ callable(getattr(parameters, a))
+ ]
+ print("\n#### Dumping default parameters:")
+ for p in param_sections:
+ print("\n")
+ pprint(getattr(parameters, p))
diff --git a/sharc/parameters/parameters_antenna.py b/sharc/parameters/parameters_antenna.py
new file mode 100644
index 000000000..f1bebbbb3
--- /dev/null
+++ b/sharc/parameters/parameters_antenna.py
@@ -0,0 +1,131 @@
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter
+from sharc.parameters.parameters_antenna_with_envelope_gain import ParametersAntennaWithEnvelopeGain
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+
+from dataclasses import dataclass, field
+import typing
+
+
+@dataclass
+class ParametersAntenna(ParametersBase):
+ # available antenna radiation patterns
+ __SUPPORTED_ANTENNA_PATTERNS = [
+ "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855",
+ "ITU-R Reg. RR. Appendice 7 Annex 3", "ARRAY", "ITU-R-S.1528-Taylor",
+ "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ ]
+
+ # chosen antenna radiation pattern
+ pattern: typing.Literal[
+ "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855",
+ "ITU-R Reg. RR. Appendice 7 Annex 3", "ARRAY", "ITU-R-S.1528-Taylor",
+ "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ ] = None
+
+ # antenna gain [dBi]
+ gain: float = None
+
+ itu_r_f_699: ParametersAntennaWithDiameter = field(
+ default_factory=ParametersAntennaWithDiameter,
+ )
+
+ itu_r_s_465: ParametersAntennaWithDiameter = field(
+ default_factory=ParametersAntennaWithDiameter,
+ )
+
+ itu_r_s_1855: ParametersAntennaWithDiameter = field(
+ default_factory=ParametersAntennaWithDiameter,
+ )
+
+ itu_r_s_580: ParametersAntennaWithDiameter = field(
+ default_factory=ParametersAntennaWithDiameter,
+ )
+
+ itu_r_s_465_modified: ParametersAntennaWithEnvelopeGain = field(
+ default_factory=ParametersAntennaWithEnvelopeGain,
+ )
+
+ itu_reg_rr_a7_3: ParametersAntennaWithDiameter = field(
+ default_factory=ParametersAntennaWithDiameter,
+ )
+
+ array: ParametersAntennaImt = field(default_factory=lambda: ParametersAntennaImt(downtilt=0.0))
+
+ # TODO: maybe separate each different S.1528 parameter?
+ itu_r_s_1528: ParametersAntennaS1528 = field(
+ default_factory=ParametersAntennaS1528,
+ )
+
+ def set_external_parameters(self, **kwargs):
+ attr_list = [
+ a for a in dir(self) if not a.startswith('__') and isinstance(getattr(self, a), ParametersBase)
+ ]
+
+ for attr_name in attr_list:
+ param = getattr(self, attr_name)
+
+ for k, v in kwargs.items():
+ if k in dir(param):
+ setattr(param, k, v)
+
+ if "antenna_gain" in dir(param):
+ param.antenna_gain = self.gain
+
+ def load_parameters_from_file(self, config_file):
+ # should not be loaded except as subparameter
+ raise NotImplementedError()
+
+ def validate(self, ctx):
+ if None in [self.pattern]:
+ raise ValueError(
+ f"{ctx}.pattern should be set. Is None instead",
+ )
+
+ if self.pattern != "ARRAY" and self.gain is None:
+ raise ValueError(
+ f"{ctx}.gain should be set if not using array antenna.",
+ )
+
+ if self.pattern not in self.__SUPPORTED_ANTENNA_PATTERNS:
+ raise ValueError(
+ f"Invalid {ctx}.pattern. It should be one of: {self.__SUPPORTED_ANTENNA_PATTERNS}.",
+ )
+
+ match self.pattern:
+ case "OMNI":
+ pass
+ case "ITU-R F.699":
+ self.itu_r_f_699.validate(f"{ctx}.itu_r_f_699")
+ case "ITU-R S.465":
+ self.itu_r_s_465.validate(f"{ctx}.itu_r_s_465")
+ case "ITU-R S.1855":
+ self.itu_r_s_1855.validate(f"{ctx}.itu_r_s_1855")
+ case "MODIFIED ITU-R S.465":
+ self.itu_r_s_465_modified.validate(
+ f"{ctx}.itu_r_s_465_modified",
+ )
+ case "ITU-R S.580":
+ self.itu_r_s_580.validate(f"{ctx}.itu_r_s_580")
+ case "ITU-R Reg. RR. Appendice 7 Annex 3":
+ if self.itu_reg_rr_a7_3.diameter is None:
+ # just hijacking validation since diameter is optional
+ self.itu_reg_rr_a7_3.diameter = 0
+ self.itu_reg_rr_a7_3.validate(f"{ctx}.itu_reg_rr_a7_3")
+ case "ARRAY":
+ # TODO: validate here and make array non imt specific
+ # self.array.validate(
+ # f"{ctx}.array",
+ # )
+ pass
+ case "ITU-R-S.1528-Taylor":
+ self.itu_r_s_1528.validate(f"{ctx}.itu_r_s_1528")
+ case "ITU-R-S.1528-Section1.2":
+ self.itu_r_s_1528.validate(f"{ctx}.itu_r_s_1528")
+ case "ITU-R-S.1528-LEO":
+ self.itu_r_s_1528.validate(f"{ctx}.itu_r_s_1528")
+ case _:
+ raise NotImplementedError(
+ "ParametersAntenna.validate does not implement this antenna validation!",
+ )
diff --git a/sharc/parameters/parameters_antenna_imt.py b/sharc/parameters/parameters_antenna_imt.py
deleted file mode 100644
index d128a7862..000000000
--- a/sharc/parameters/parameters_antenna_imt.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Sat Apr 15 16:29:36 2017
-
-@author: Calil
-"""
-
-import sys
-
-from sharc.support.named_tuples import AntennaPar
-from sharc.support.enumerations import StationType
-from numpy import load
-
-class ParametersAntennaImt(object):
- """
- Defines parameters for antenna array.
- """
-
- def __init__(self):
- pass
-
-
- ###########################################################################
- # Named tuples which contain antenna types
-
- def get_antenna_parameters(self, sta_type: StationType)-> AntennaPar:
- if sta_type is StationType.IMT_BS:
- if self.bs_normalization:
- # Load data, save it in dict and close it
- data = load(self.bs_normalization_file)
- data_dict = {key:data[key] for key in data}
- self.bs_normalization_data = data_dict
- data.close()
- else:
- self.bs_normalization_data = None
- tpl = AntennaPar(self.adjacent_antenna_model,
- self.bs_normalization,
- self.bs_normalization_data,
- self.bs_element_pattern,
- self.bs_element_max_g,
- self.bs_element_phi_3db,
- self.bs_element_theta_3db,
- self.bs_element_am,
- self.bs_element_sla_v,
- self.bs_n_rows,
- self.bs_n_columns,
- self.bs_element_horiz_spacing,
- self.bs_element_vert_spacing,
- self.bs_multiplication_factor,
- self.bs_minimum_array_gain,
- self.bs_downtilt)
- elif sta_type is StationType.IMT_UE:
- if self.ue_normalization:
- # Load data, save it in dict and close it
- data = load(self.ue_normalization_file)
- data_dict = {key:data[key] for key in data}
- self.ue_normalization_data = data_dict
- data.close()
- else:
- self.ue_normalization_data = None
- tpl = AntennaPar(self.adjacent_antenna_model,
- self.ue_normalization,
- self.ue_normalization_data,
- self.ue_element_pattern,
- self.ue_element_max_g,
- self.ue_element_phi_3db,
- self.ue_element_theta_3db,
- self.ue_element_am,
- self.ue_element_sla_v,
- self.ue_n_rows,
- self.ue_n_columns,
- self.ue_element_horiz_spacing,
- self.ue_element_vert_spacing,
- self.ue_multiplication_factor,
- self.ue_minimum_array_gain,
- 0)
- else:
- sys.stderr.write("ERROR\nInvalid station type: " + sta_type)
- sys.exit(1)
-
- return tpl
diff --git a/sharc/parameters/parameters_antenna_with_diameter.py b/sharc/parameters/parameters_antenna_with_diameter.py
new file mode 100644
index 000000000..a34c21be9
--- /dev/null
+++ b/sharc/parameters/parameters_antenna_with_diameter.py
@@ -0,0 +1,30 @@
+from sharc.parameters.parameters_base import ParametersBase
+
+from dataclasses import dataclass
+
+
+@dataclass
+class ParametersAntennaWithDiameter(ParametersBase):
+ antenna_gain: float = None
+ frequency: float = None
+ diameter: float = None
+
+ # ideally we would make this happen at the ParametersBase.load_subparameters, but
+ # since parser is a bit hacky, this is created without acces to system freq and antenna gain
+ # so validation needs to happen manually afterwards
+ def validate(self, ctx):
+ if None in [
+ self.antenna_gain,
+ self.diameter,
+ self.frequency,
+ ]:
+ raise ValueError(f"{ctx} needs to have all its parameters set")
+
+ if not isinstance(self.antenna_gain, int) and not isinstance(self.antenna_gain, float):
+ raise ValueError(f"{ctx}.antenna_gain needs to be a number")
+
+ if (not isinstance(self.diameter, int) and not isinstance(self.diameter, float)) or self.diameter <= 0:
+ raise ValueError(f"{ctx}.diameter needs to be a positive number")
+
+ if not isinstance(self.frequency, int) and not isinstance(self.frequency, float):
+ raise ValueError(f"{ctx}.frequency needs to be a number")
diff --git a/sharc/parameters/parameters_antenna_with_envelope_gain.py b/sharc/parameters/parameters_antenna_with_envelope_gain.py
new file mode 100644
index 000000000..59a451ecd
--- /dev/null
+++ b/sharc/parameters/parameters_antenna_with_envelope_gain.py
@@ -0,0 +1,25 @@
+from sharc.parameters.parameters_base import ParametersBase
+
+from dataclasses import dataclass
+
+
+@dataclass
+class ParametersAntennaWithEnvelopeGain(ParametersBase):
+ antenna_gain: float = None
+ envelope_gain: float = None
+
+ # ideally we would make this happen at the ParametersBase.load_subparameters, but
+ # since parser is a bit hacky, this is created without acces to system freq and antenna gain
+ # so validation needs to happen manually afterwards
+ def validate(self, ctx):
+ if None in [
+ self.antenna_gain,
+ self.envelope_gain,
+ ]:
+ raise ValueError(f"{ctx} needs to have all its parameters set")
+
+ if not isinstance(self.gain, int) and not isinstance(self.gain, float):
+ raise ValueError(f"{ctx}.gain needs to be a number")
+
+ if not isinstance(self.envelope_gain, int) and not isinstance(self.envelope_gain, float):
+ raise ValueError(f"{ctx}.envelope_gain needs to be a number")
diff --git a/sharc/parameters/parameters_base.py b/sharc/parameters/parameters_base.py
new file mode 100644
index 000000000..5438b793a
--- /dev/null
+++ b/sharc/parameters/parameters_base.py
@@ -0,0 +1,219 @@
+import yaml
+from dataclasses import dataclass
+from copy import deepcopy
+
+
+# Register a tuple constructor with PyYAML
+def tuple_constructor(loader, node):
+ """Load the sequence of values from the YAML node and returns a tuple constructed from the sequence."""
+ values = loader.construct_sequence(node)
+ return tuple(values)
+
+
+yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor)
+
+
+@dataclass
+class ParametersBase:
+ """Base class for parameter dataclassess
+ """
+ section_name: str = "default"
+ is_space_to_earth: bool = False # whether the system is a space station or not
+
+ # whether to enable recursive parameters setting on .yaml file
+ # TODO: make every system have this be True and remove this attribute
+ nested_parameters_enabled: bool = False
+
+ # TODO: make this be directly called as the default load method, after reading .yml file
+ def load_subparameters(self, ctx: str, params: dict, quiet=True):
+ """
+ ctx: provides information on what subattribute is being parsed.
+ This is mainly for debugging/logging/error handling
+ params: dict that contains the attributes needed by the
+ quiet: if True the parser will not warn about unset paramters
+ """
+ # Load all the parameters from the configuration file
+ attr_list = [
+ a for a in dir(self) if not a.startswith('_') and not callable(getattr(self, a)) and a not in
+ ["section_name", "nested_parameters_enabled",]]
+
+ params_keys = params.keys()
+
+ for k in params_keys:
+ if k not in attr_list:
+ raise ValueError(
+ f"The parameter {ctx}.{k} was passed, but it doesn't exist on parameters definitions!"
+ )
+
+ for attr_name in attr_list:
+ default_attr_value = getattr(self, attr_name)
+
+ if attr_name not in params:
+ if not quiet:
+ print(
+ f"[INFO]: WARNING. Using default parameters for {ctx}.{attr_name}: {default_attr_value}",
+ )
+ elif isinstance(default_attr_value, ParametersBase):
+ if not isinstance(params[attr_name], dict):
+ raise ValueError(
+ f"ERROR: Cannot parse section {ctx}.{attr_name}, is {params[attr_name]} instead of a dictionary",
+ )
+
+ # try to recursively set config
+ # is a bit hacky and limits some stuff, since it doesn't know the context it is in
+ # for example, it cannot get system frequency to set some value
+ default_attr_value.load_subparameters(
+ f"{ctx}.{attr_name}", params[attr_name],
+ )
+ elif isinstance(default_attr_value, list):
+ if not isinstance(params[attr_name], list):
+ raise ValueError(
+ f"ERROR: Cannot parse parameter {ctx}.{attr_name}, is \
+ {params[attr_name]} instead of a list",
+ )
+ loaded_attr_vals = list()
+ default_item = default_attr_value[0]
+
+ if isinstance(default_item, ParametersBase):
+ for prm in params[attr_name]:
+ new_item = deepcopy(default_item)
+ new_item.load_subparameters(
+ f"{self.section_name}.{attr_name}", prm,
+ )
+ loaded_attr_vals.append(new_item)
+ else:
+ for prm in params[attr_name]:
+ if not isinstance(prm, type(default_item)):
+ raise ValueError(
+ f"ERROR: Cannot parse section {ctx}.{attr_name}\n"
+ f"List item does not respect expected type of {type(default_item)}\n"
+ f"{prm} has type of {type(prm)}"
+ )
+ loaded_attr_vals.append(prm)
+
+ setattr(self, attr_name, loaded_attr_vals)
+ else:
+ setattr(self, attr_name, params[attr_name])
+
+ def validate(self, ctx: str):
+ """
+ This method exists because there was a need to separate params parsing from validating,
+ since nested parameters may need some attributes to be set by a "parent"
+ before proper validation.
+ ctx: context string. It should be a string that gives more information about where
+ validation is being called on, so that errors may be better handled/alerted
+ """
+ attr_list = [
+ a for a in dir(self) if not a.startswith('_') and isinstance(getattr(self, a), ParametersBase)
+ ]
+
+ for attr in attr_list:
+ getattr(self, attr).validate(f"{ctx}.{attr}")
+
+ def load_parameters_from_file(self, config_file: str, quiet=True):
+ """Load the parameters from file.
+ The sanity check is child class reponsibility
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+ quiet: if True the parser will not warn about unset paramters
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+
+ with open(config_file, 'r') as file:
+ config = yaml.safe_load(file)
+
+ if self.section_name.lower() not in config.keys():
+ if not quiet:
+ print(f"ParameterBase: section {self.section_name} not in parameter file.\
+ Only default parameters where loaded.")
+ return
+
+ # Load all the parameters from the configuration file
+ attr_list = [
+ a for a in dir(self) if not a.startswith('_') and not
+ callable(getattr(self, a)) and a != "section_name"
+ ]
+
+ params_keys = config[self.section_name].keys()
+
+ for k in params_keys:
+ if k not in attr_list:
+ raise ValueError(
+ f"The parameter {self.section_name}.{k} was passed, but it doesn't exist on parameters definitions!"
+ )
+
+ for attr_name in attr_list:
+ try:
+ attr_val = getattr(self, attr_name)
+ # since there are no tuple types in yaml:
+ if isinstance(attr_val, tuple):
+ # TODO: test this conditional. There are no test cases for tuple, and no tuple in any of current parameters
+ # Check if the string defines a list of floats
+ try:
+ param_val = config[self.section_name][attr_name]
+ tmp_val = list(map(float, param_val.split(",")))
+ setattr(self, attr_name, tuple(tmp_val))
+ except ValueError:
+ # its a regular string. Let the specific class implementation
+ # do the sanity check
+ print(
+ f"ParametersBase: could not convert string to tuple \"{self.section_name}.{attr_name}\"",
+ )
+ exit()
+
+ # TODO: make every parameters use this way of setting its own attributes, and remove
+ # attr_val.nested_parameters_enabled we check for attr_val.nested_parameters_enabled because we don't
+ # want to print notice for this kind of parameter YET
+ elif isinstance(attr_val, ParametersBase):
+ if not self.nested_parameters_enabled:
+ continue
+
+ if not isinstance(config[self.section_name][attr_name], dict):
+ raise ValueError(
+ f"ERROR: Cannot parse section {self.section_name}.{attr_name}, is \
+ {config[self.section_name][attr_name]} instead of a dictionary",
+ )
+
+ # try to recursively set config
+ # is a bit hacky and limits some stuff, since it doesn't know the context it is in
+ # for example, it cannot get system frequency to set some value
+ attr_val.load_subparameters(
+ f"{self.section_name}.{attr_name}", config[self.section_name][attr_name],
+ )
+ elif isinstance(attr_val, list):
+ if not self.nested_parameters_enabled:
+ continue
+ if not isinstance(config[self.section_name][attr_name], list):
+ raise ValueError(
+ f"ERROR: Cannot parse section {self.section_name}.{attr_name}, is \
+ {config[self.section_name][attr_name]} instead of a list",
+ )
+ loaded_attr_vals = list()
+ default_item = attr_val[0]
+ for params in config[self.section_name][attr_name]:
+ new_item = deepcopy(default_item)
+ new_item.load_subparameters(
+ f"{self.section_name}.{attr_name}", params,
+ )
+ loaded_attr_vals.append(new_item)
+ setattr(self, attr_name, loaded_attr_vals)
+
+ else:
+ setattr(
+ self, attr_name,
+ config[self.section_name][attr_name],
+ )
+
+ except KeyError:
+ if not quiet:
+ print(
+ f"ParametersBase: NOTICE! Configuration parameter \"{self.section_name}.{attr_name}\" \
+ is not set in configuration file. Using default value {attr_val}",
+ )
diff --git a/sharc/parameters/parameters_eess_passive.py b/sharc/parameters/parameters_eess_passive.py
deleted file mode 100644
index 1b6ee46fe..000000000
--- a/sharc/parameters/parameters_eess_passive.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Thu Feb 21 11:58:56 2019
-
-@author: Edgar Souza
-"""
-
-class ParametersEessPassive(object):
- """
- Simulation parameters for EESS passive services
- """
-
- def __init__(self):
- pass
\ No newline at end of file
diff --git a/sharc/parameters/parameters_eess_ss.py b/sharc/parameters/parameters_eess_ss.py
new file mode 100644
index 000000000..d646b467d
--- /dev/null
+++ b/sharc/parameters/parameters_eess_ss.py
@@ -0,0 +1,120 @@
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.parameters_space_station import ParametersSpaceStation
+
+
+@dataclass
+class ParametersEessSS(ParametersSpaceStation):
+ """
+ Defines parameters for Earth Exploration Satellite Service (EESS) sensors/space stations (SS)
+ and their interaction with other services based on ITU recommendations.
+ """
+ section_name: str = "eess_ss"
+
+ is_space_to_earth: bool = True
+
+ # Sensor center frequency [MHz]
+ frequency: float = 23900.0 # Center frequency of the sensor in MHz
+
+ # Sensor bandwidth [MHz]
+ bandwidth: float = 200.0 # Bandwidth of the sensor in MHz
+
+ # Off-nadir pointing angle [deg]
+ nadir_angle: float = 46.6 # Angle in degrees away from the nadir point
+
+ # Sensor altitude [m]
+ # Altitude of the sensor above the Earth's surface in meters
+ altitude: float = 828000.0
+
+ # Antenna pattern of the sensor
+ # Possible values: "ITU-R RS.1813", "ITU-R RS.1861 9a", "ITU-R RS.1861 9b",
+ # "ITU-R RS.1861 9c", "ITU-R RS.2043", "OMNI"
+ # TODO: check `x` and `y`:
+ # @important: for EESS Passive, antenna pattern is from Recommendatio ITU-R RS.1813
+ antenna_pattern: str = "ITU-R RS.1813" # Antenna radiation pattern
+
+ # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1]
+ # Efficiency factor for the antenna, range from 0 to 1
+ antenna_efficiency: float = 0.6
+
+ # Antenna diameter for ITU-R RS.1813 [m]
+ antenna_diameter: float = 2.2 # Diameter of the antenna in meters
+
+ # Receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: float = 52.0 # Gain of the antenna in dBi
+
+ # Channel model, possible values are "FSPL" (free-space path loss), "P619"
+ channel_model: str = "FSPL" # Channel model to be used
+
+ # Parameters for the P.619 propagation model
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ param_p619 = ParametersP619()
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+
+ ########### Creates a statistical distribution of nadir angle###############
+ ############## following variables nadir_angle_distribution#################
+ # if distribution_enable = ON, nadir_angle will vary statistically#########
+ # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ###
+ # distribution_type = UNIFORM
+ # UNIFORM = UNIFORM distribution in nadir_angle
+ # - nadir_angle_distribution = initial nadir angle, final nadir angle
+ distribution_enable: bool = False
+ distribution_type: str = "UNIFORM"
+ nadir_angle_distribution: tuple = (18.5, 49.3)
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+ # self.param_p619.load_from_paramters(self)
+ # # print("altitude, earth_station_alt_m", self.altitude, self.earth_station_alt_m)
+ # print([
+ # self.earth_station_alt_m, self.earth_station_lat_deg, self.earth_station_long_diff_deg
+ # ])
+ # Implement additional sanity checks for EESS specific parameters
+ if not (0 <= self.antenna_efficiency or self.antenna_efficiency <= 1):
+ raise ValueError("antenna_efficiency must be between 0 and 1")
+
+ if self.antenna_pattern not in [
+ "ITU-R RS.1813", "ITU-R RS.1861 9a",
+ "ITU-R RS.1861 9b", "ITU-R RS.1861 9c",
+ "ITU-R RS.2043", "OMNI",
+ ]:
+ raise ValueError(f"Invalid antenna_pattern: {self.antenna_pattern}")
+
+ if self.antenna_pattern == "ITU-R RS.2043" and \
+ (self.frequency <= 9000.0 or self.frequency >= 10999.0):
+ raise ValueError(
+ f"Frequency {self.frequency} MHz is not in the range for antenna pattern \"ITU-R RS.2043\"",
+ )
+
+ # Check channel model
+ if self.channel_model not in ["FSPL", "P619"]:
+ raise ValueError(
+ "Invalid channel_model, must be either 'FSPL' or 'P619'",
+ )
+
+ # Check season
+ if self.season not in ["SUMMER", "WINTER"]:
+ raise ValueError(
+ "Invalid season, must be either 'SUMMER' or 'WINTER'",
+ )
diff --git a/sharc/parameters/parameters_fs.py b/sharc/parameters/parameters_fs.py
index c1332b932..7fd2d69f4 100644
--- a/sharc/parameters/parameters_fs.py
+++ b/sharc/parameters/parameters_fs.py
@@ -1,14 +1,124 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Wed Aug 9 19:35:52 2017
+from dataclasses import dataclass
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p452 import ParametersP452
-@author: edgar
-"""
-class ParametersFs(object):
+@dataclass
+class ParametersFs(ParametersBase):
"""
- Simulation parameters for Fixed Services
+ Parameters definitions for fixed wireless service systems.
"""
- def __init__(self):
- pass
+ section_name: str = "fs"
+
+ # x-y coordinates [meters]
+ x: float = 1000.0
+ y: float = 0.0
+
+ # Antenna height [meters]
+ height: float = 15.0
+
+ # Elevation angle [degrees]
+ elevation: float = -10.0
+
+ # Azimuth angle [degrees]
+ azimuth: float = 180.0
+
+ # Center frequency [MHz]
+ frequency: float = 27250.0
+
+ # Bandwidth [MHz]
+ bandwidth: float = 112.0
+
+ # System receive noise temperature [Kelvin]
+ noise_temperature: float = 290.0
+
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: float = 20.0
+
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = -68.3
+
+ # Antenna peak gain [dBi]
+ antenna_gain: float = 36.9
+
+ # Antenna pattern of the fixed wireless service
+ # Possible values: "ITU-R F.699", "OMNI"
+ antenna_pattern: str = "ITU-R F.699"
+
+ # Diameter of antenna [meters]
+ diameter: float = 0.3
+
+ # Channel model, possible values are "FSPL" (free-space path loss),
+ # "TerrestrialSimple" (FSPL + clutter loss),
+ # P452
+ channel_model: str = "FSPL"
+
+ # P452 parameters
+ param_p452 = ParametersP452()
+ # Total air pressure in hPa
+ atmospheric_pressure: float = 935.0
+ # Temperature in Kelvin
+ air_temperature: float = 300.0
+ # Sea-level surface refractivity (use the map)
+ N0: float = 352.58
+ # Average radio-refractive (use the map)
+ delta_N: float = 43.127
+ # Percentage p. Float (0 to 100) or RANDOM
+ percentage_p: str = "0.2"
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dct: float = 70.0
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dcr: float = 70.0
+ # Effective height of interfering antenna (m)
+ Hte: float = 20.0
+ # Effective height of interfered-with antenna (m)
+ Hre: float = 3.0
+ # Latitude of transmitter
+ tx_lat: float = -23.55028
+ # Latitude of receiver
+ rx_lat: float = -23.17889
+ # Antenna polarization
+ polarization: str = "horizontal"
+ # Determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
+ clutter_loss: bool = True
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ # Implementing sanity checks for critical parameters
+ if not (-90 <= self.elevation <= 90):
+ raise ValueError(
+ "Elevation angle must be between -90 and 90 degrees.",
+ )
+
+ if not (0 <= self.azimuth <= 360):
+ raise ValueError(
+ "Azimuth angle must be between 0 and 360 degrees.",
+ )
+
+ if self.antenna_pattern not in ["ITU-R F.699", "OMNI"]:
+ raise ValueError(
+ f"Invalid antenna_pattern: {self.antenna_pattern}",
+ )
+
+ # Sanity check for channel model
+ if self.channel_model not in ["FSPL", "TerrestrialSimple", "P452"]:
+ raise ValueError(
+ "Invalid channel_model, must be either 'FSPL', 'TerrestrialSimple', or 'P452'",
+ )
+ if self.channel_model == "P452":
+ self.param_p452.load_from_paramters(self)
diff --git a/sharc/parameters/parameters_fss_es.py b/sharc/parameters/parameters_fss_es.py
index 8ce8f2414..954ed162a 100644
--- a/sharc/parameters/parameters_fss_es.py
+++ b/sharc/parameters/parameters_fss_es.py
@@ -1,14 +1,212 @@
# -*- coding: utf-8 -*-
-"""
-Created on Mon Jul 24 15:30:49 2017
+from dataclasses import dataclass
-@author: edgar
-"""
+from sharc.support.sharc_utils import is_float
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p452 import ParametersP452
+from sharc.parameters.parameters_p619 import ParametersP619
-class ParametersFssEs(object):
- """
- Simulation parameters for FSS Earth Station.
+
+@dataclass
+class ParametersFssEs(ParametersBase):
+ """Dataclass containing the Fixed Satellite Services - Earth Station
+ parameters for the simulator
"""
-
- def __init__(self):
- pass
\ No newline at end of file
+ section_name: str = "fss_es"
+
+ # type of FSS-ES location:
+ # FIXED - position must be given
+ # CELL - random within central cell
+ # NETWORK - random within whole network
+ # UNIFORM_DIST - uniform distance from cluster centre,
+ # between min_dist_to_bs and max_dist_to_bs
+ location: str = "UNIFORM_DIST"
+ # x-y coordinates [m] (only if FIXED location is chosen)
+ x: float = 10000.0
+ y: float = 0.0
+ # minimum distance from BSs [m]
+ min_dist_to_bs: float = 10.0
+ # maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen)
+ max_dist_to_bs: float = 10.0
+ # antenna height [m]
+ height: float = 6.0
+ # Elevation angle [deg], minimum and maximum, values
+ elevation_min: float = 48.0
+ elevation_max: float = 80.0
+ # Azimuth angle [deg]
+ # either a specific angle or string 'RANDOM'
+ azimuth: str = 0.2
+ # center frequency [MHz]
+ frequency: float = 43000.0
+ # bandwidth [MHz]
+ bandwidth: float = 6.0
+ # adjacent channel selectivity (dB)
+ adjacent_ch_selectivity: float = 0.0
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = -68.3
+ # System receive noise temperature [K]
+ noise_temperature: float = 950.0
+ # antenna peak gain [dBi]
+ antenna_gain: float = 0.0
+ # Antenna pattern of the FSS Earth station
+ # Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI",
+ # "Modified ITU-R S.465"
+ antenna_pattern: str = "Modified ITU-R S.465"
+ # Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model
+ antenna_envelope_gain: float = 0.0
+ # Diameter of the antenna [m]
+ diameter: float = 1.8
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "TerrestrialSimple" (FSPL + clutter loss)
+ # "P452"
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "HDFSS"
+ channel_model: str = "P452"
+
+ # P452 parameters
+ param_p452 = ParametersP452()
+ # Total air pressure in hPa
+ atmospheric_pressure: float = 935.0
+ # Temperature in Kelvin
+ air_temperature: float = 300.0
+ # Sea-level surface refractivity (use the map)
+ N0: float = 352.58
+ # Average radio-refractive (use the map)
+ delta_N: float = 43.127
+ # Percentage p. Float (0 to 100) or RANDOM
+ percentage_p: str = "0.2"
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dct: float = 70.0
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dcr: float = 70.0
+ # Effective height of interfering antenna (m)
+ Hte: float = 20.0
+ # Effective height of interfered-with antenna (m)
+ Hre: float = 3.0
+ # Latitude of transmitter
+ tx_lat: float = -23.55028
+ # Latitude of receiver
+ rx_lat: float = -23.17889
+ # Antenna polarization
+ polarization: str = "horizontal"
+ # Determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
+ clutter_loss: bool = True
+ clutter_type: str = "one-end"
+
+ # Parameters for the P.619 propagation model used for sharing studies between IMT-NTN and FSS-ES
+ # space_station_alt_m - altiteude of the IMT-MSS station
+ # earth_station_alt_m - altitude of FSS-ES system (in meters)
+ # earth_station_lat_deg - latitude of FSS-ES system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT-NTN station and FSS-ES system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ param_p619 = ParametersP619()
+ space_station_alt_m: float = 35780000.0
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+
+ # HDFSS propagation parameters
+ # HDFSS position relative to building it is on. Possible values are
+ # ROOFTOP and BUILDINGSIDE
+ es_position: str = "ROOFTOP"
+ # Enable shadowing loss
+ shadow_enabled: bool = True
+ # Enable building entry loss
+ building_loss_enabled: bool = True
+ # Enable interference from IMT stations at the same building as the HDFSS
+ same_building_enabled: bool = False
+ # Enable diffraction loss
+ diffraction_enabled: bool = False
+ # Building entry loss type applied between BSs and HDFSS ES. Options are:
+ # P2109_RANDOM: random probability at P.2109 model, considering elevation
+ # P2109_FIXED: fixed probability at P.2109 model, considering elevation.
+ # Probability must be specified in bs_building_entry_loss_prob.
+ # FIXED_VALUE: fixed value per BS. Value must be specified in
+ # bs_building_entry_loss_value.
+ bs_building_entry_loss_type: str = "P2109_FIXED"
+ # Probability of building entry loss not exceeded if
+ # bs_building_entry_loss_type = P2109_FIXED
+ bs_building_entry_loss_prob: float = 0.75
+ # Value in dB of building entry loss if
+ # bs_building_entry_loss_type = FIXED_VALUE
+ bs_building_entry_loss_value: float = 35
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+
+ if self.location not in ["FIXED", "CELL", "NETWORK", "UNIFORM_DIST"]:
+ raise ValueError(f"ParametersFssEs: \
+ Invalid value for paramter location - {self.location}. \
+ Allowed values are \"FIXED\", \"CELL\", \"NETWORK\", \"UNIFORM_DIST\".")
+
+ if self.antenna_pattern not in [
+ "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI",
+ "Modified ITU-R S.465",
+ ]:
+ raise ValueError(f"ParametersFssEs: \
+ Invalid value for paramter antenna_pattern - {self.antenna_pattern}. \
+ Allowed values are \
+ \"ITU-R S.1855\", \"ITU-R S.465\", \"ITU-R S.580\", \"OMNI\", \
+ \"Modified ITU-R S.465\"")
+
+ if is_float(self.azimuth):
+ self.azimuth = float(self.azimuth)
+ elif self.azimuth.upper() != "RANDOM":
+ if self.azimuth.isnumeric():
+ self.azimuth = float(self.azimuth)
+ else:
+ raise ValueError(f"""ParametersFssEs:
+ Invalid value for parameter azimuth - {self.azimuth}.
+ Allowed values are \"RANDOM\" or a angle in degrees.""")
+
+ if isinstance(self.percentage_p, str) and self.percentage_p.upper() != "RANDOM":
+ percentage_p = float(self.percentage_p)
+ if percentage_p <= 0 or percentage_p > 1:
+ raise ValueError(f"""ParametersFssEs:
+ Invalid value for parameter percentage_p - {self.percentage_p}.
+ Allowed values are a percentage between 0 and 1 (exclusive).""")
+
+ if self.polarization.lower() not in ["horizontal", "vertical"]:
+ raise ValueError(f"ParametersFssEss: \
+ Invalid value for parameter polarization - {self.polarization}. \
+ Allowed values are: \"horizontal\", \"vertical\"")
+
+ if self.es_position.upper() not in ["BUILDINGSIDE", "ROOFTOP"]:
+ raise ValueError(f"ParametersFssEss: \
+ Invalid value for parameter es_position - {self.es_position} \
+ Allowed values are \"BUILDINGSIDE\", \"ROOFTOP\".")
+
+ if self.bs_building_entry_loss_type not in ["P2109_RANDOM", "P2109_FIXED", "FIXED_VALUE"]:
+ raise ValueError(f"ParametersFssEs: \
+ Invalid value for parameter bs_building_entry_loss_type - \
+ {self.bs_building_entry_loss_type} \
+ Allowd values are \"P2109_RANDOM\", \"P2109_FIXED\", \"FIXED_VALUE\".")
+ if self.channel_model.upper() not in [
+ "FSPL", "TERRESTRIALSIMPLE", "P452", "P619",
+ "TVRO-URBAN", "TVRO-SUBURBAN", "HDFSS", "UMA", "UMI",
+ ]:
+ raise ValueError(
+ f"ParametersFssEs: Invalid value for parameter channel_model - {self.channel_model}",
+ )
+
+ if self.channel_model == "P452":
+ self.param_p452.load_from_paramters(self)
+
+ elif self.channel_model == "P619":
+ self.param_p619.load_from_paramters(self)
diff --git a/sharc/parameters/parameters_fss_ss.py b/sharc/parameters/parameters_fss_ss.py
index 37b0a6085..1f1be2b36 100644
--- a/sharc/parameters/parameters_fss_ss.py
+++ b/sharc/parameters/parameters_fss_ss.py
@@ -1,11 +1,107 @@
# -*- coding: utf-8 -*-
-"""
-Created on Thu Apr 13 13:16:02 2017
+from dataclasses import dataclass, field
+import numpy as np
-@author: edgar
-"""
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
-class ParametersFssSs(object):
- def __init__(self):
- pass
\ No newline at end of file
+@dataclass
+class ParametersFssSs(ParametersBase):
+ """Dataclass containing the Fixed Satellite Services - Space Station
+ parameters for the simulator
+ """
+ section_name: str = "fss_ss"
+ nested_parameters_enabled: bool = True
+ is_space_to_earth: bool = True
+ # satellite center frequency [MHz]
+ frequency: float = 43000.0
+ # satellite bandwidth [MHz]
+ bandwidth: float = 200.0
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = -5.0
+ # satellite altitude [m]
+ altitude: float = 35780000.0
+ # satellite latitude [deg]
+ lat_deg: float = 0.0
+ # Elevation angle [deg]
+ elevation: float = 270.0
+ # Azimuth angle [deg]
+ azimuth: float = 0.0
+ # System receive noise temperature [K]
+ noise_temperature: float = 950.0
+ # Adjacent channel selectivity (dB)
+ adjacent_ch_selectivity: float = 0.0
+
+ # Antenna parameters
+ # Antenna pattern of the FSS space station
+ # Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI"
+ antenna_pattern: str = "ITU-R S.672"
+ ############################
+ # Satellite peak receive antenna gain [dBi]
+ antenna_gain: float = 46.6
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ # according to ITU-R S.672-4
+ antenna_l_s: float = -20.0
+ # Parameters if antenna_pattern = ITU_R S.1528
+ antenna_s1528: ParametersAntennaS1528 = field(default_factory=ParametersAntennaS1528)
+
+ ############################
+ # Parameters for the P.619 propagation model
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ param_p619 = ParametersP619()
+ space_station_alt_m: float = 35780000.0
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: str = "P619"
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB: float = 0.65
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+
+ # Now do the sanity check for some parameters
+ if self.antenna_pattern not in ["ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI"]:
+ raise ValueError(f"ParametersFssSs: \
+ invalid value for parameter antenna_pattern - {self.antenna_pattern}. \
+ Possible values \
+ are \"ITU-R S.672\", \"ITU-R S.1528\", \"FSS_SS\", \"OMNI\"")
+
+ if self.season.upper() not in ["SUMMER", "WINTER"]:
+ raise ValueError(f"ParametersFssSs: \
+ Invalid value for parameter season - {self.season}. \
+ Possible values are \"SUMMER\", \"WINTER\".")
+
+ if self.channel_model.upper() not in ["FSPL", "SATELLITESIMPLE", "P619"]:
+ raise ValueError(f"ParametersFssSs: \
+ Invalid value for paramter channel_model = {self.channel_model}. \
+ Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".")
+ self.param_p619.load_from_paramters(self)
+ self.antenna_s1528.set_external_parameters(frequency=self.frequency,
+ bandwidth=self.bandwidth,
+ antenna_gain=self.antenna_gain,
+ antenna_l_s=self.antenna_l_s,
+ antenna_3_dB_bw=self.antenna_3_dB,)
diff --git a/sharc/parameters/parameters_general.py b/sharc/parameters/parameters_general.py
index 77873f0f9..a040358c4 100644
--- a/sharc/parameters/parameters_general.py
+++ b/sharc/parameters/parameters_general.py
@@ -1,11 +1,45 @@
# -*- coding: utf-8 -*-
-"""
-Created on Wed Feb 15 16:05:46 2017
+from dataclasses import dataclass
-@author: edgar
-"""
+from sharc.sharc_definitions import SHARC_IMPLEMENTED_SYSTEMS
+from sharc.parameters.parameters_base import ParametersBase
-class ParametersGeneral(object):
- def __init__(self):
- pass
\ No newline at end of file
+@dataclass
+class ParametersGeneral(ParametersBase):
+ """Dataclass containing the general parameters for the simulator
+ """
+ section_name: str = "general"
+ num_snapshots: int = 10000
+ imt_link: str = "DOWNLINK"
+ system: str = "RAS"
+ enable_cochannel: bool = False
+ enable_adjacent_channel: bool = True
+ seed: int = 101
+ overwrite_output: bool = True
+ output_dir: str = "output"
+ output_dir_prefix: str = "output"
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+
+ # Now do the sanity check for some parameters
+ if self.imt_link.upper() not in ["DOWNLINK", "UPLINK"]:
+ raise ValueError(f"ParametersGeneral: \
+ Invalid value for parameter imt_link - {self.imt_link} \
+ Possible values are DOWNLINK and UPLINK")
+
+ if self.system not in SHARC_IMPLEMENTED_SYSTEMS:
+ raise ValueError(f"Invalid system name {self.system}")
diff --git a/sharc/parameters/parameters_haps.py b/sharc/parameters/parameters_haps.py
index 251a486df..991e9aedb 100644
--- a/sharc/parameters/parameters_haps.py
+++ b/sharc/parameters/parameters_haps.py
@@ -1,15 +1,82 @@
# -*- coding: utf-8 -*-
-"""
-Created on Thu Oct 19 12:32:30 2017
+from dataclasses import dataclass
-@author: edgar
-"""
+from sharc.parameters.parameters_base import ParametersBase
-class ParametersHaps(object):
+@dataclass
+class ParametersHaps(ParametersBase):
+ """Dataclass containing the IMT system parameters
"""
- Simulation parameters for HAPS (airbone) platform.
- """
-
- def __init__(self):
- pass
\ No newline at end of file
+ section_name: str = "haps"
+ # HAPS center frequency [MHz]
+ frequency: float = 27250.0
+ # HAPS bandwidth [MHz]
+ bandwidth: float = 200.0
+ # HAPS peak antenna gain [dBi]
+ antenna_gain: float = 28.1
+ # EIRP spectral density [dBW/MHz]
+ eirp_density: float = 4.4
+ # tx antenna power density [dBW/MHz]
+ tx_power_density: float = eirp_density - antenna_gain - 60
+ # HAPS altitude [m] and latitude [deg]
+ altitude: float = 20000.0
+ lat_deg: float = 0.0
+ # Elevation angle [deg]
+ elevation: float = 270.0
+ # Azimuth angle [deg]
+ azimuth: float = 0.0
+ # Antenna pattern of the HAPS (airbone) station
+ # Possible values: "ITU-R F.1891", "OMNI"
+ antenna_pattern: str = "ITU-R F.1891"
+ # Parameters for the P.619 propagation model
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ # Adjacent channel selectivity [dB]
+ acs: float = 30.0
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: str = "P619"
+ # Near side-lobe level (dB) relative to the peak gain required by the system
+ # design, and has a maximum value of โ25 dB
+ antenna_l_n: float = -25.0
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+ if self.antenna_pattern not in ["ITU-R F.1891", "OMNI"]:
+ raise ValueError(f"ParametersHaps: \
+ Invalid value for parameter {self.antenna_pattern}. \
+ Allowed values are \"ITU-R F.1891\", \"OMNI\".")
+ if self.channel_model.upper() not in ["FSPL", "SatelliteSimple", "P619"]:
+ raise ValueError(f"ParametersHaps: \
+ Invalid value for paramter channel_model = {self.channel_model}. \
+ Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".")
+ if self.season.upper() not in ["SUMMER", "WINTER"]:
+ raise ValueError(f"ParametersHaps: \
+ Invalid value for parameter season - {self.season}. \
+ Possible values are \"SUMMER\", \"WINTER\".")
+
+ # Post initialization
+ # tx antenna power density [dBW/MHz]
+ self.tx_power_density = self.eirp_density - self.antenna_gain - 60
diff --git a/sharc/parameters/parameters_hdfss.py b/sharc/parameters/parameters_hdfss.py
new file mode 100644
index 000000000..4f878f1ba
--- /dev/null
+++ b/sharc/parameters/parameters_hdfss.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersHDFSS(ParametersBase):
+ """
+ Dataclass containing the HDFSS (High-Density Fixed Satellite System Propagation Model)
+ propagation model parameters
+ """
+ # HDFSS position relative to building it is on. Possible values are
+ # ROOFTOP and BUILDINGSIDE
+ es_position: str = "ROOFTOP"
+ # Enable shadowing loss
+ shadow_enabled: bool = True
+ # Enable building entry loss
+ building_loss_enabled: bool = True
+ # Enable interference from IMT stations at the same building as the HDFSS
+ same_building_enabled: bool = False
+ # Enable diffraction loss
+ diffraction_enabled: bool = False
+ # Building entry loss type applied between BSs and HDFSS ES. Options are:
+ # P2109_RANDOM: random probability at P.2109 model, considering elevation
+ # P2109_FIXED: fixed probability at P.2109 model, considering elevation.
+ # Probability must be specified in bs_building_entry_loss_prob.
+ # FIXED_VALUE: fixed value per BS. Value must be specified in
+ # bs_building_entry_loss_value.
+ bs_building_entry_loss_type: str = "P2109_FIXED"
+ # Probability of building entry loss not exceeded if
+ # bs_building_entry_loss_type = P2109_FIXED
+ bs_building_entry_loss_prob: float = 0.75
+ # Value in dB of building entry loss if
+ # bs_building_entry_loss_type = FIXED_VALUE
+ bs_building_entry_loss_value: float = 35
diff --git a/sharc/parameters/parameters_hotspot.py b/sharc/parameters/parameters_hotspot.py
deleted file mode 100644
index 4479d375f..000000000
--- a/sharc/parameters/parameters_hotspot.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Wed May 17 15:47:05 2017
-
-@author: edgar
-"""
-
-class ParametersHotspot(object):
-
- def __init__(self):
- pass
\ No newline at end of file
diff --git a/sharc/parameters/parameters_imt.py b/sharc/parameters/parameters_imt.py
deleted file mode 100644
index 4438bb4f5..000000000
--- a/sharc/parameters/parameters_imt.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Wed Feb 15 16:05:58 2017
-
-@author: edgar
-"""
-
-class ParametersImt(object):
-
- def __init__(self):
- pass
-
diff --git a/sharc/parameters/parameters_indoor.py b/sharc/parameters/parameters_indoor.py
deleted file mode 100644
index 3dd695a8e..000000000
--- a/sharc/parameters/parameters_indoor.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Tue Dec 5 17:50:05 2017
-
-@author: edgar
-"""
-
-class ParametersIndoor(object):
- """
- Simulation parameters for indoor network topology.
- """
-
- def __init__(self):
- pass
\ No newline at end of file
diff --git a/sharc/parameters/parameters_metsat_ss.py b/sharc/parameters/parameters_metsat_ss.py
new file mode 100644
index 000000000..aa847516e
--- /dev/null
+++ b/sharc/parameters/parameters_metsat_ss.py
@@ -0,0 +1,76 @@
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_space_station import ParametersSpaceStation
+
+# The default values come from Report ITU-R SA.2488-0, table 19 (the only earth-to-space MetSat entry)
+# TODO: let MetSat as interferrer
+# TODO: ver com professor se considerar as tabelas do report estรก correto
+
+
+@dataclass
+class ParametersMetSatSS(ParametersSpaceStation):
+ """
+ Defines parameters for MetSat space stations (SS)
+ and their interaction with other services based on ITU recommendations.
+ """
+ section_name: str = "mestat_ss"
+
+ # raw data transmission in 8175-8215 range
+ frequency: float = 8195.0 # Satellite center frequency [MHz]
+ bandwidth: float = 20.0
+
+ # Elevation angle [deg]
+ elevation: float = 3 # Minimum == 3
+
+ # Satellite altitude [m]
+ # Altitude of the satellite above the Earth's surface in meters
+ altitude: float = 35786000.0 # 35786000 for GSO
+
+ # Antenna pattern of the satellite
+ # Possible values: "ITU-R S.672"
+ antenna_pattern: str = "ITU-R S.672" # Antenna radiation pattern
+
+ # antenna peak gain [dBi]
+ antenna_gain: float = 52.0
+
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ # according to ITU-R S.672-4
+ # TODO: check if this changes from fss_ss
+ antenna_l_s: float = -20.0
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB: float = 0.65
+
+ # Channel model, possible values are "FSPL" (free-space path loss), "P619"
+ # See params space station to check P619 needed params
+ channel_model: str = "FSPL" # Channel model to be used
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+ print(self.antenna_pattern)
+ if self.antenna_pattern not in ["ITU-R S.672"]:
+ raise ValueError(f"Invalid antenna_pattern: {self.antenna_pattern}")
+
+ # Check channel model
+ if self.channel_model not in ["FSPL", "P619"]:
+ raise ValueError(
+ "Invalid channel_model, must be either 'FSPL' or 'P619'",
+ )
+
+ # Check season
+ if self.season not in ["SUMMER", "WINTER"]:
+ raise ValueError(
+ "Invalid season, must be either 'SUMMER' or 'WINTER'",
+ )
diff --git a/sharc/parameters/parameters_mss_d2d.py b/sharc/parameters/parameters_mss_d2d.py
new file mode 100644
index 000000000..b2f2f30f8
--- /dev/null
+++ b/sharc/parameters/parameters_mss_d2d.py
@@ -0,0 +1,179 @@
+import numpy as np
+from dataclasses import dataclass, field, asdict
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_orbit import ParametersOrbit
+from sharc.parameters.imt.parameters_imt_mss_dc import ParametersSelectActiveSatellite, ParametersSectorPositioning
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+
+
+@dataclass
+class ParametersMssD2d(ParametersBase):
+ """Define parameters for a MSS-D2D - NGSO Constellation."""
+ section_name: str = "mss_d2d"
+
+ nested_parameters_enabled: bool = True
+
+ is_space_to_earth: bool = True
+
+ # MSS_D2D system name
+ name: str = "Default"
+
+ # Orbit parameters
+ orbits: list[ParametersOrbit] = field(default_factory=lambda: [ParametersOrbit()])
+
+ # MSS_D2D system center frequency in MHz
+ frequency: float = 2110.0
+
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: float = 5.0
+
+ # In case you want to use a load factor for beams
+ # that means that each beam has a probability of `beams_load_factor` to be active
+ beams_load_factor: float = 1.0
+
+ # Central beam positioning
+ center_beam_positioning: ParametersSectorPositioning = field(default_factory=ParametersSectorPositioning)
+
+ # Adjacent channel emissions type
+ # Possible values are "ACLR", "SPECTRAL_MASK" and "OFF"
+ adjacent_ch_emissions: str = "OFF"
+
+ # Transmitter spectral mask
+ spectral_mask: str = "MSS"
+
+ # Out-of-band spurious emissions in dB/MHz
+ spurious_emissions: float = -13.0
+
+ # Adjacent channel leakage ratio in dB
+ adjacent_ch_leak_ratio: float = 45.0
+
+ # Single beam cell radius in network topology [m]
+ cell_radius: float = 19000.0
+
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ intersite_distance: float = np.sqrt(3) * cell_radius
+
+ # Satellite tx power density in dBW/MHz
+ tx_power_density: float = 40.0
+
+ # # Satellite Tx max Gain in dBi
+ # antenna_gain: float = 30.0
+
+ # Number of beams per satellite
+ num_sectors: int = 19
+
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Taylor", "ITU-R-S.1528-LEO"
+ antenna_pattern: str = "ITU-R-S.1528-Taylor"
+
+ # Radius of the antenna's circular aperture in meters
+ antenna_diamter: float = 1.0
+
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ antenna_l_s: float = -6.75
+
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB_bw: float = 4.4127
+
+ # Paramters for the ITU-R-S.1528 antenna patterns
+ antenna_s1528: ParametersAntennaS1528 = field(default_factory=ParametersAntennaS1528)
+
+ sat_is_active_if: ParametersSelectActiveSatellite = field(default_factory=ParametersSelectActiveSatellite)
+
+ # paramters for channel model
+ param_p619: ParametersP619 = field(default_factory=ParametersP619)
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: str = "P619"
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ self.propagate_parameters()
+
+ self.validate(self.section_name)
+
+ def __post_init__(self):
+ self.beam_radius = self.cell_radius
+ self.num_beams = self.num_sectors
+
+ def validate(self, ctx):
+ # Now do the sanity check for some parameters
+ if self.num_sectors not in [1, 7, 19]:
+ raise ValueError(f"ParametersMssD2d: Invalid number of sectors {self.num_sectors}")
+ self.num_beams = self.num_sectors
+
+ if self.cell_radius <= 0:
+ raise ValueError(f"ParametersMssD2d: cell_radius must be greater than 0, but is {self.cell_radius}")
+ else:
+ self.intersite_distance = np.sqrt(3) * self.cell_radius
+ self.beam_radius = self.cell_radius
+
+ if self.adjacent_ch_emissions not in ["SPECTRAL_MASK", "ACLR", "OFF"]:
+ raise ValueError(f"""ParametersMssD2d: Invalid adjacent channel emissions {self.adjacent_ch_emissions}""")
+
+ if self.spectral_mask.upper() not in ["IMT-2020", "3GPP E-UTRA", "MSS"]:
+ raise ValueError(f"""ParametersMssD2d: Inavlid Spectral Mask Name {self.spectral_mask}""")
+
+ if self.channel_model.upper() not in ["FSPL", "P619", "SATELLITESIMPLE"]:
+ raise ValueError(f"Invalid channel model name {self.channel_model}")
+
+ if self.beams_load_factor < 0.0 or self.beams_load_factor > 1.0:
+ raise ValueError(f"{ctx}.beams_load_factor must be in interval [0.0, 1.0]")
+
+ super().validate(ctx)
+
+ def propagate_parameters(self):
+ self.antenna_s1528.set_external_parameters(antenna_pattern=self.antenna_pattern,
+ frequency=self.frequency,
+ bandwidth=self.bandwidth,
+ antenna_l_s=self.antenna_l_s,
+ antenna_3_dB_bw=self.antenna_3_dB_bw,)
+
+ if self.channel_model == "P619":
+ # mean station altitude in meters
+ m_alt = 0
+ for orbit in self.orbits:
+ m_alt += orbit.perigee_alt_km * 1e3
+ m_alt /= len(self.orbits)
+
+ self.param_p619.set_external_parameters(
+ space_station_alt_m=m_alt,
+ earth_station_alt_m=self.earth_station_alt_m,
+ earth_station_lat_deg=self.earth_station_lat_deg,
+ earth_station_long_diff_deg=self.earth_station_long_diff_deg,
+ season=self.season
+ )
+
+
+if __name__ == "__main__":
+ # Run validation for input parameters
+ import os
+ import pprint
+
+ # Load default simulator parameters
+ yaml_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../input/parameters.yaml")
+ mss_d2d_params = ParametersMssD2d()
+ mss_d2d_params.load_parameters_from_file(yaml_file_path)
+ pprint.pprint(asdict(mss_d2d_params), sort_dicts=False)
diff --git a/sharc/parameters/parameters_mss_ss.py b/sharc/parameters/parameters_mss_ss.py
new file mode 100644
index 000000000..9a579b2e7
--- /dev/null
+++ b/sharc/parameters/parameters_mss_ss.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""Parameters definitions for NTN systems
+"""
+from dataclasses import dataclass, field
+
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+import numpy as np
+
+
+@dataclass
+class ParametersMssSs(ParametersBase):
+ """
+ Simulation parameters for Mobile Satellite System - Space Station.
+ """
+ section_name: str = "mss_ss"
+
+ is_space_to_earth: bool = True
+
+ # Satellite bore-sight - this is the center of the central beam.
+ x: float = 0.0
+ y: float = 0.0
+
+ # MSS_SS system center frequency in MHz
+ frequency: float = 2110.0
+
+ # MSS_SS system bandwidth in MHz
+ bandwidth: float = 20.0
+
+ # Transmitter spectral mask
+ spectral_mask: str = "3GPP E-UTRA"
+
+ # Out-of-band spurious emissions in dB/MHz
+ spurious_emissions: float = -13
+
+ # Adjacent channel leakage ratio in dB
+ adjacent_ch_leak_ratio: float = 45.0
+
+ # MSS_SS altitude w.r.t. sea level
+ altitude: float = 1200000.0
+
+ # NTN cell radius in network topology [m]
+ cell_radius: float = 45000.0
+
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ intersite_distance: float = np.sqrt(3) * cell_radius
+
+ # Satellite tx power density in dBW/MHz
+ tx_power_density: float = 40.0
+
+ # Satellite Tx max Gain in dBi
+ antenna_gain: float = 30.0
+
+ # Satellite azimuth w.r.t. simulation x-axis
+ azimuth: float = 45.0
+
+ # Satellite elevation w.r.t. simulation xy-plane (horizon)
+ elevation: float = 90.0
+
+ # Number of sectors
+ num_sectors: int = 7
+
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: str = "ITU-R-S.1528-LEO"
+
+ # Radius of the antenna's circular aperture in meters
+ antenna_diamter: float = 1.0
+
+ # The required near-in-side-lobe level (dB) relative to peak gain
+ antenna_l_s: float = -6.75
+
+ # 3 dB beamwidth angle (3 dB below maximum gain) [degrees]
+ antenna_3_dB_bw: float = 4.4127
+
+ # Paramters for the ITU-R-S.1528 antenna patterns
+ antenna_s1528: ParametersAntennaS1528 = field(default_factory=ParametersAntennaS1528)
+
+ # paramters for channel model
+ param_p619: ParametersP619 = field(default_factory=ParametersP619)
+ space_station_alt_m: float = 35780000.0
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: str = "P619"
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ # Now do the sanity check for some parameters
+ if self.num_sectors not in [1, 7, 19]:
+ raise ValueError(f"ParametersMssSs: Invalid number of sectors {self.num_sectors}")
+
+ if self.cell_radius <= 0:
+ raise ValueError(f"ParametersMssSs: cell_radius must be greater than 0, but is {self.cell_radius}")
+ else:
+ self.intersite_distance = np.sqrt(3) * self.cell_radius
+
+ if not np.all((0 <= self.azimuth) & (self.azimuth <= 360)):
+ raise ValueError(
+ "ParametersMssSs: bs_azimuth values must be between 0 and 360 degrees")
+
+ if not np.all((0 <= self.elevation) & (self.elevation <= 90)):
+ raise ValueError(
+ "ParametersMssSs: bs_elevation values must be between 0 and 90 degrees")
+
+ if self.spectral_mask.upper() not in ["IMT-2020", "3GPP E-UTRA"]:
+ raise ValueError(f"""ParametersImt: Inavlid Spectral Mask Name {self.spectral_mask}""")
+
+ self.antenna_s1528.set_external_parameters(frequency=self.frequency,
+ bandwidth=self.bandwidth,
+ antenna_gain=self.antenna_gain,
+ antenna_l_s=self.antenna_l_s,
+ antenna_3_dB_bw=self.antenna_3_dB_bw,)
+
+ if self.channel_model.upper() not in ["FSPL", "P619", "SATELLITESIMPLE"]:
+ raise ValueError(f"Invalid channel model name {self.channel_model}")
+
+ if self.channel_model == "P619":
+ self.param_p619.set_external_parameters(
+ space_station_alt_m=self.altitude,
+ earth_station_alt_m=self.earth_station_alt_m,
+ earth_station_lat_deg=self.earth_station_lat_deg,
+ earth_station_long_diff_deg=self.earth_station_long_diff_deg,
+ season=self.season
+ )
diff --git a/sharc/parameters/parameters_ngso_constellation.py b/sharc/parameters/parameters_ngso_constellation.py
new file mode 100644
index 000000000..b866ce38c
--- /dev/null
+++ b/sharc/parameters/parameters_ngso_constellation.py
@@ -0,0 +1,184 @@
+from dataclasses import dataclass, field
+from typing import List
+import numpy as np
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_orbit import ParametersOrbit
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+from sharc.parameters.parameters_p619 import ParametersP619
+
+
+@dataclass
+class ParametersNgsoConstellation(ParametersBase):
+ """
+ Combines parameters for NGSO constellations and Mobile Satellite Systems (MSS).
+ """
+
+ # General configuration for NGSO constellation
+ section_name: str = "ngso_mss" # Section name in configuration file
+ name: str = "Default" # Name of the NGSO constellation
+
+ # Orbital configuration
+ orbits: List[ParametersOrbit] = field(default_factory=list) # List of orbit parameters
+
+ # Antenna configuration
+ antenna: str = None # Antenna type for the constellation
+ max_transmit_power_dB: float = 46.0 # Maximum transmit power in dB
+ max_transmit_gain_dBi: float = 30.0 # Maximum transmit gain in dBi
+ max_receive_gain_dBi: float = 30.0 # Maximum receive gain in dBi
+ max_num_of_beams: int = 19 # Maximum number of beams per satellite
+ cell_radius_m: float = 19000.0 # Cell radius in meters
+ antenna_s1528: ParametersAntennaS1528 = field(default_factory=ParametersAntennaS1528) # Antenna parameters
+
+ # MSS-specific parameters
+ is_space_to_earth: bool = True # Direction of communication (space-to-earth or earth-to-space)
+ x: float = 0.0 # X-coordinate of the satellite bore-sight
+ y: float = 0.0 # Y-coordinate of the satellite bore-sight
+ frequency: float = 2110.0 # System center frequency in MHz
+ bandwidth: float = 20.0 # System bandwidth in MHz
+ spectral_mask: str = "3GPP E-UTRA" # Transmitter spectral mask type
+ spurious_emissions: float = -13 # Out-of-band spurious emissions in dB/MHz
+ adjacent_ch_leak_ratio: float = 45.0 # Adjacent channel leakage ratio in dB
+ altitude: float = 1200000.0 # Satellite altitude above sea level in meters
+ intersite_distance: float = field(init=False) # Inter-site distance, calculated from cell radius
+ tx_power_density: float = 40.0 # Satellite transmit power density in dBW/MHz
+ azimuth: float = 45.0 # Azimuth angle in degrees
+ elevation: float = 90.0 # Elevation angle in degrees
+ num_sectors: int = 7 # Number of sectors served by the satellite
+ antenna_pattern: str = "ITU-R-S.1528-LEO" # Antenna pattern type
+ antenna_diameter: float = 1.0 # Antenna diameter in meters
+ antenna_l_s: float = -6.75 # Near-in-side-lobe level in dB relative to peak gain
+ antenna_3_dB_bw: float = 4.4127 # 3 dB beamwidth angle in degrees
+
+ # Channel model configuration
+ param_p619: ParametersP619 = field(default_factory=ParametersP619) # Parameters for the P.619 channel model
+ space_station_alt_m: float = 35780000.0 # Altitude of the space station in meters
+ earth_station_alt_m: float = 0.0 # Altitude of the earth station in meters
+ earth_station_lat_deg: float = 0.0 # Latitude of the earth station in degrees
+ earth_station_long_diff_deg: float = 0.0 # Longitude difference for the earth station in degrees
+ season: str = "SUMMER" # Season for atmospheric conditions
+ channel_model: str = "P619" # Channel model type (e.g., FSPL, SatelliteSimple, P619)
+
+ def __post_init__(self):
+ """
+ Perform initialization calculations.
+ """
+ # Calculate inter-site distance from cell radius
+ self.intersite_distance = np.sqrt(3) * self.cell_radius_m
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load parameters from a configuration file and validate.
+
+ Parameters
+ ----------
+ config_file : str
+ Path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter fails validation.
+ """
+ super().load_parameters_from_file(config_file)
+
+ # Validate NGSO constellation parameters
+ if not self.name or not isinstance(self.name, str):
+ raise ValueError(f"ParametersNgsoMss: Invalid name = {self.name}. Must be a non-empty string.")
+
+ if not self.orbits:
+ raise ValueError("ParametersNgsoMss: No orbits defined. At least one orbit must be specified.")
+
+ for orbit in self.orbits:
+ if not isinstance(orbit, ParametersOrbit):
+ raise ValueError("ParametersNgsoMss: Invalid orbit configuration. Must be instances of ParametersOrbit.")
+
+ # Validate MSS-specific parameters
+ if self.num_sectors not in [1, 7, 19]:
+ raise ValueError(f"ParametersNgsoMss: Invalid number of sectors: {self.num_sectors}")
+
+ if self.cell_radius_m <= 0:
+ raise ValueError(f"ParametersNgsoMss: cell_radius must be greater than 0, but is {self.cell_radius_m}")
+ else:
+ self.intersite_distance = np.sqrt(3) * self.cell_radius_m
+
+ if not (0 <= self.azimuth <= 360):
+ raise ValueError("ParametersNgsoMss: azimuth must be between 0 and 360 degrees")
+
+ if not (0 <= self.elevation <= 90):
+ raise ValueError("ParametersNgsoMss: elevation must be between 0 and 90 degrees")
+
+ if self.channel_model.upper() not in ["FSPL", "P619", "SATELLITESIMPLE"]:
+ raise ValueError(f"ParametersNgsoMss: Invalid channel model name {self.channel_model}")
+
+ if self.channel_model == "P619":
+ self.param_p619.set_external_parameters(
+ space_station_alt_m=self.altitude,
+ earth_station_alt_m=self.earth_station_alt_m,
+ earth_station_lat_deg=self.earth_station_lat_deg,
+ earth_station_long_diff_deg=self.earth_station_long_diff_deg,
+ season=self.season
+ )
+
+
+if __name__ == "__main__":
+
+ try:
+ # Create orbital parameters for the first orbit
+ orbit_1 = ParametersOrbit(
+ n_planes=20, # Number of orbital planes
+ sats_per_plane=32, # Satellites per plane
+ phasing_deg=3.9, # Phasing angle in degrees
+ long_asc_deg=18.0, # Longitude of ascending node
+ inclination_deg=54.5, # Orbital inclination in degrees
+ perigee_alt_km=525.0, # Perigee altitude in kilometers
+ apogee_alt_km=525.0 # Apogee altitude in kilometers
+ )
+
+ # Create orbital parameters for the second orbit
+ orbit_2 = ParametersOrbit(
+ n_planes=12, # Number of orbital planes
+ sats_per_plane=20, # Satellites per plane
+ phasing_deg=2.0, # Phasing angle in degrees
+ long_asc_deg=30.0, # Longitude of ascending node
+ inclination_deg=26.0, # Orbital inclination in degrees
+ perigee_alt_km=580.0, # Perigee altitude in kilometers
+ apogee_alt_km=580.0 # Apogee altitude in kilometers
+ )
+
+ # Instantiate the ParametersNgsoConstellation class with manually defined attributes
+ constellation = ParametersNgsoConstellation(
+ name="Acme-Star-1", # Name of the constellation
+ antenna="Taylor1.4", # Antenna type
+ max_transmit_power_dB=46.0, # Maximum transmit power in dB
+ max_transmit_gain_dBi=30.0, # Maximum transmit gain in dBi
+ max_receive_gain_dBi=30.0, # Maximum receive gain in dBi
+ max_num_of_beams=19, # Maximum number of beams
+ cell_radius_m=19000.0, # Cell radius in meters
+ orbits=[orbit_1, orbit_2] # List of orbital parameters
+ )
+
+ # Display the manually set parameters
+ print(f"\n#### Constellation Details ####")
+ print(f"Name: {constellation.name}")
+ print(f"Antenna Type: {constellation.antenna}")
+ print(f"Maximum Transmit Power (dB): {constellation.max_transmit_power_dB}")
+ print(f"Maximum Transmit Gain (dBi): {constellation.max_transmit_gain_dBi}")
+ print(f"Maximum Receive Gain (dBi): {constellation.max_receive_gain_dBi}")
+ print(f"Maximum Number of Beams: {constellation.max_num_of_beams}")
+ print(f"Cell Radius (m): {constellation.cell_radius_m}")
+
+ # Iterate through the orbits and display their parameters
+ print("\n#### Orbital Parameters ####")
+ for idx, orbit in enumerate(constellation.orbits, start=1):
+ print(f"Orbit {idx}:")
+ print(f" Number of Orbital Planes: {orbit.n_planes}")
+ print(f" Satellites per Plane: {orbit.sats_per_plane}")
+ print(f" Phasing Angle (deg): {orbit.phasing_deg}")
+ print(f" Longitude of Ascending Node (deg): {orbit.long_asc_deg}")
+ print(f" Orbital Inclination (deg): {orbit.inclination_deg}")
+ print(f" Perigee Altitude (km): {orbit.perigee_alt_km}")
+ print(f" Apogee Altitude (km): {orbit.apogee_alt_km}")
+ except ValueError as error:
+ print(f"Validation Error: {error}")
+ except Exception as unexpected_error:
+ print(f"An unexpected error occurred: {unexpected_error}")
diff --git a/sharc/parameters/parameters_orbit.py b/sharc/parameters/parameters_orbit.py
new file mode 100644
index 000000000..884681a67
--- /dev/null
+++ b/sharc/parameters/parameters_orbit.py
@@ -0,0 +1,35 @@
+from dataclasses import dataclass, field
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersOrbit(ParametersBase):
+ n_planes: int = 8
+ sats_per_plane: int = 6
+ phasing_deg: float = 7.5
+ long_asc_deg: float = 0.0
+ omega_deg: float = 0.0
+ inclination_deg: float = 52.0
+ perigee_alt_km: float = 1414.0
+ apogee_alt_km: float = 1414.0
+ initial_mean_anomaly: float = 0.0
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load parameters from file and validate."""
+ super().load_parameters_from_file(config_file)
+
+ self.validate("ParametersOrbit")
+
+ def validate(self, ctx: str):
+ if not (0 <= self.inclination_deg <= 180):
+ raise ValueError(f"Invalid {ctx}.inclination_deg = {self.inclination_deg}. \
+ Must be in the range [0, 180] degrees.")
+ if self.perigee_alt_km < 0:
+ raise ValueError(f"Invalid {ctx}.perigee_alt_km = {self.perigee_alt_km}. \
+ Altitude must be non-negative.")
+ if self.apogee_alt_km < self.perigee_alt_km:
+ raise ValueError(f"Invalid {ctx}.apogee_alt_km = {self.apogee_alt_km}. \
+ Must be greater than or equal to perigee_alt_km.")
+ if not (0 <= self.phasing_deg <= 360):
+ raise ValueError(f"Invalid {ctx}.phasing_deg = {self.phasing_deg}. \
+ Must be in the range [0, 360] degrees.")
diff --git a/sharc/parameters/parameters_p452.py b/sharc/parameters/parameters_p452.py
new file mode 100644
index 000000000..5fc0ff2af
--- /dev/null
+++ b/sharc/parameters/parameters_p452.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+from dataclasses import dataclass
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersP452(ParametersBase):
+ """Dataclass containing the P.452 propagation model parameters
+ """
+ # Total air pressure in hPa
+ atmospheric_pressure: float = 935.0
+ # Temperature in Kelvin
+ air_temperature: float = 300.0
+ # Sea-level surface refractivity (use the map)
+ N0: float = 352.58
+ # Average radio-refractive (use the map)
+ delta_N: float = 43.127
+ # percentage p. Float (0 to 100) or RANDOM
+ percentage_p: float = 0.2
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dct: float = 70.0
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dcr: float = 70.0
+ # Effective height of interfering antenna (m)
+ Hte: float = 20.0
+ # Effective height of interfered-with antenna (m)
+ Hre: float = 3.0
+ # Latitude of transmitter
+ tx_lat: float = -23.55028
+ # Latitude of receiver
+ rx_lat: float = -23.17889
+ # Antenna polarization
+ polarization: str = "horizontal"
+ # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
+ clutter_loss: bool = True
+ # Determine if clutter is applied to "one-end" or "both-ends"
+ clutter_type: str = "one-end"
+
+ def load_from_paramters(self, param: ParametersBase):
+ """Used to load parameters of P.452 from IMT or system parameters
+
+ Parameters
+ ----------
+ param : ParametersBase
+ IMT or system parameters
+ """
+ self.atmospheric_pressure = param.atmospheric_pressure
+ self.air_temperature = param.air_temperature
+ self.N0 = param.N0
+ self.delta_N = param.delta_N
+ self.percentage_p = param.percentage_p
+ self.Dct = param.Dct
+ self.Dcr = param.Dcr
+ self.Hte = param.Hte
+ self.Hre = param.Hre
+ self.tx_lat = param.tx_lat
+ self.rx_lat = param.rx_lat
+ self.polarization = param.polarization
+ self.clutter_loss = param.clutter_loss
+ self.clutter_type = param.clutter_type
\ No newline at end of file
diff --git a/sharc/parameters/parameters_p619.py b/sharc/parameters/parameters_p619.py
new file mode 100644
index 000000000..bd53fb215
--- /dev/null
+++ b/sharc/parameters/parameters_p619.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Object that loads the parameters for the P.619 propagation model.
+"""Parameters definitions for IMT systems
+"""
+from dataclasses import dataclass
+import typing
+
+from sharc.parameters.parameters_base import ParametersBase
+
+
+@dataclass
+class ParametersP619(ParametersBase):
+ """Dataclass containing the P.619 propagation model parameters
+ """
+ # Parameters for the P.619 propagation model
+ # For IMT NTN the model is used for calculating the coupling loss between
+ # the BS space station and the UEs on Earth's surface.
+ # For now, the NTN footprint is centered over the BS nadir point, therefore
+ # the paramters imt_lag_deg and imt_long_diff_deg SHALL be zero.
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ space_station_alt_m: float = 20000.0
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ shadowing: bool = True
+ noise_temperature: float = 290.0
+ mean_clutter_height: str = "high"
+ below_rooftop: float = 100
+
+ def load_from_paramters(self, param: ParametersBase):
+ """Used to load parameters of P.619 from IMT or system parameters
+
+ Parameters
+ ----------
+ param : ParametersBase
+ IMT or system parameters
+ """
+ self.space_station_alt_m = param.space_station_alt_m
+ self.earth_station_alt_m = param.earth_station_alt_m
+ self.earth_station_lat_deg = param.earth_station_lat_deg
+ self.earth_station_long_diff_deg = param.earth_station_long_diff_deg
+ self.season = param.season
+
+ if self.season.upper() not in ["SUMMER", "WINTER"]:
+ raise ValueError(f"{self.__class__.__name__}: \
+ Invalid value for parameter season - {self.season}. \
+ Possible values are \"SUMMER\", \"WINTER\".")
+
+ def set_external_parameters(self, *,
+ space_station_alt_m: float,
+ earth_station_alt_m: float,
+ earth_station_lat_deg: float,
+ earth_station_long_diff_deg: float,
+ season: typing.Literal["SUMMER", "WINTER"]):
+ self.space_station_alt_m = space_station_alt_m
+ self.earth_station_alt_m = earth_station_alt_m
+ self.earth_station_lat_deg = earth_station_lat_deg
+ self.earth_station_long_diff_deg = earth_station_long_diff_deg
+ self.season = season
diff --git a/sharc/parameters/parameters_ras.py b/sharc/parameters/parameters_ras.py
index 1ebbf20e2..9a9956eda 100644
--- a/sharc/parameters/parameters_ras.py
+++ b/sharc/parameters/parameters_ras.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
-"""
-Created on Thu Nov 16 17:11:06 2017
+from dataclasses import dataclass
+import numpy as np
-@author: Calil
-"""
+from sharc.parameters.parameters_single_earth_station import ParametersSingleEarthStation
-class ParametersRas(object):
+
+@dataclass
+class ParametersRas(ParametersSingleEarthStation):
"""
Simulation parameters for Radio Astronomy Service
"""
-
- def __init__(self):
- pass
\ No newline at end of file
+ section_name: str = "ras"
+ polarization_loss: float = 0.0
diff --git a/sharc/parameters/parameters_rns.py b/sharc/parameters/parameters_rns.py
index e38f8efdb..0ed01de96 100644
--- a/sharc/parameters/parameters_rns.py
+++ b/sharc/parameters/parameters_rns.py
@@ -1,14 +1,74 @@
# -*- coding: utf-8 -*-
-"""
-Created on Wed Dec 20 16:27:43 2017
+from dataclasses import dataclass
-@author: edgar
-"""
+from sharc.parameters.parameters_base import ParametersBase
-class ParametersRns(object):
+
+@dataclass
+class ParametersRns(ParametersBase):
"""
Simulation parameters for radionavigation service
"""
-
- def __init__(self):
- pass
\ No newline at end of file
+ section_name: str = "rns"
+ # x-y coordinates [m]
+ x: float = 660.0
+ y: float = -370.0
+ # altitude [m]
+ altitude: float = 150.0
+ # center frequency [MHz]
+ frequency: float = 32000.0
+ # bandwidth [MHz]
+ bandwidth: float = 60.0
+ # System receive noise temperature [K]
+ noise_temperature: float = 1154.0
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = -70.79
+ # antenna peak gain [dBi]
+ antenna_gain: float = 30.0
+ # Antenna pattern of the fixed wireless service
+ # Possible values: "ITU-R M.1466", "OMNI"
+ antenna_pattern: str = "ITU-R M.1466"
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 dB + clutter loss)
+ # "P619"
+ channel_model: str = "P619"
+ # Parameters for the P.619 propagation model
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+ # Adjacent channel selectivity [dB]
+ acs: float = 30.0
+
+ def load_parameters_from_file(self, config_file: str):
+ """Load the parameters from file an run a sanity check
+
+ Parameters
+ ----------
+ file_name : str
+ the path to the configuration file
+
+ Raises
+ ------
+ ValueError
+ if a parameter is not valid
+ """
+ super().load_parameters_from_file(config_file)
+ if self.antenna_pattern not in ["ITU-R M.1466", "OMNI"]:
+ raise ValueError(f"ParametersRns: \
+ Invalid value for parameter {self.antenna_pattern}. \
+ Allowed values are \"ITU-R M.1466\", \"OMNI\".")
+ if self.channel_model.upper() not in ["FSPL", "SatelliteSimple", "P619"]:
+ raise ValueError(f"ParametersRns: \
+ Invalid value for paramter channel_model = {self.channel_model}. \
+ Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".")
+ if self.season.upper() not in ["SUMMER", "WINTER"]:
+ raise ValueError(f"ParametersRns: \
+ Invalid value for parameter season - {self.season}. \
+ Possible values are \"SUMMER\", \"WINTER\".")
diff --git a/sharc/parameters/parameters_single_earth_station.py b/sharc/parameters/parameters_single_earth_station.py
new file mode 100644
index 000000000..122a1d7fb
--- /dev/null
+++ b/sharc/parameters/parameters_single_earth_station.py
@@ -0,0 +1,250 @@
+from dataclasses import dataclass, field
+import typing
+
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_antenna import ParametersAntenna
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.parameters_p452 import ParametersP452
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
+
+
+@dataclass
+class ParametersSingleEarthStation(ParametersBase):
+ """
+ Defines parameters for passive Earth Exploration Satellite Service (EESS) sensors
+ and their interaction with other services based on ITU recommendations.
+ """
+ section_name: str = "single_earth_station"
+ nested_parameters_enabled: bool = True
+
+ # for when other system needs it
+ central_latitude: float = None
+ central_altitude: float = None
+ central_longitude: float = None
+
+ # NOTE: when using P.619 it is suggested that polarization loss = 3dB is normal
+ # also,
+ # NOTE: Verification needed:
+ # polarization mismatch between IMT BS and linear polarization = 3dB in earth P2P case?
+ # = 0 is safer choice
+ polarization_loss: float = 3.0
+
+ # Sensor center frequency [MHz]
+ frequency: float = None # Center frequency of the sensor in MHz
+
+ # Sensor bandwidth [MHz]
+ bandwidth: float = None # Bandwidth of the sensor in MHz
+
+ # System receive noise temperature [K]
+ noise_temperature: float = None
+
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: float = None
+
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_emissions: float = None
+
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = None
+
+ # Antenna pattern of the sensor
+ antenna: ParametersAntenna = field(default_factory=ParametersAntenna)
+
+ # Channel model, possible values are "FSPL" (free-space path loss), "P619"
+ channel_model: typing.Literal[
+ "FSPL", "P619",
+ "P452",
+ ] = "FSPL" # Channel model to be used
+
+ param_p619: ParametersP619 = field(default_factory=ParametersP619)
+ # TODO: remove season from system parameter and put it as p619 parameter
+ season: typing.Literal["WINTER", "SUMMER"] = "SUMMER"
+
+ param_p452: ParametersP452 = field(default_factory=ParametersP452)
+
+ param_hdfss: ParametersHDFSS = field(default_factory=ParametersHDFSS)
+
+ @dataclass
+ class EarthStationGeometry(ParametersBase):
+ height: float = None
+
+ @dataclass
+ class FixedOrUniformDist(ParametersBase):
+ __EXISTING_TYPES = ["UNIFORM_DIST", "FIXED"]
+ type: typing.Literal["UNIFORM_DIST", "FIXED"] = None
+ fixed: float = None
+
+ @dataclass
+ class UniformDistParams(ParametersBase):
+ min: float = None
+ max: float = None
+
+ def validate(self, ctx):
+ if not isinstance(self.min, int) and not isinstance(self.min, float):
+ raise ValueError(
+ f"{ctx}.min parameter should be a number",
+ )
+ if not isinstance(self.max, int) and not isinstance(self.max, float):
+ raise ValueError(
+ f"{ctx}.max parameter should be a number",
+ )
+ if self.max < self.min:
+ raise ValueError(
+ f"{ctx}.max parameter should be greater than or equal to {ctx}.min",
+ )
+ uniform_dist: UniformDistParams = field(
+ default_factory=UniformDistParams,
+ )
+
+ def validate(self, ctx):
+ if self.type not in self.__EXISTING_TYPES:
+ raise ValueError(
+ f"Invalid value for {ctx}.type. Should be one of {self.__EXISTING_TYPES}",
+ )
+
+ match self.type:
+ case "UNIFORM_DIST":
+ self.uniform_dist.validate(f"{ctx}.uniform_dist")
+ case "FIXED":
+ if not isinstance(self.fixed, int) and not isinstance(self.fixed, float):
+ raise ValueError(f"{ctx}.fixed should be a number")
+ case _:
+ raise NotImplementedError(
+ f"Validation for {ctx}.type = {self.type} is not implemented",
+ )
+
+ azimuth: FixedOrUniformDist = field(default_factory=FixedOrUniformDist)
+ elevation: FixedOrUniformDist = field(
+ default_factory=FixedOrUniformDist,
+ )
+
+ @dataclass
+ class Location(ParametersBase):
+ __EXISTING_TYPES = ["FIXED", "CELL", "NETWORK", "UNIFORM_DIST"]
+ type: typing.Literal[
+ "FIXED", "CELL",
+ "NETWORK", "UNIFORM_DIST",
+ ] = None
+
+ @dataclass
+ class LocationFixed(ParametersBase):
+ x: float = None
+ y: float = None
+
+ def validate(self, ctx):
+ if not isinstance(self.x, int) and not isinstance(self.x, float):
+ raise ValueError(f"{ctx}.x needs to be a number")
+ if not isinstance(self.y, int) and not isinstance(self.y, float):
+ raise ValueError(f"{ctx}.y needs to be a number")
+
+ @dataclass
+ class LocationDistributed(ParametersBase):
+ min_dist_to_bs: float = None
+
+ def validate(self, ctx):
+ if not isinstance(self.min_dist_to_bs, int) and not isinstance(self.min_dist_to_bs, float):
+ raise ValueError(
+ f"{ctx}.min_dist_to_bs needs to be a number",
+ )
+
+ @dataclass
+ class LocationDistributedWithinCircle(ParametersBase):
+ min_dist_to_center: float = None
+ max_dist_to_center: float = None
+
+ def validate(self, ctx):
+ if not isinstance(self.min_dist_to_center, int) and not isinstance(self.min_dist_to_center, float):
+ raise ValueError(
+ f"{ctx}.min_dist_to_center needs to be a number",
+ )
+ if not isinstance(self.max_dist_to_center, int) and not isinstance(self.max_dist_to_center, float):
+ raise ValueError(
+ f"{ctx}.max_dist_to_center needs to be a number",
+ )
+
+ fixed: LocationFixed = field(default_factory=LocationFixed)
+ cell: LocationDistributed = field(
+ default_factory=LocationDistributed,
+ )
+ network: LocationDistributed = field(
+ default_factory=LocationDistributed,
+ )
+ uniform_dist: LocationDistributedWithinCircle = field(
+ default_factory=LocationDistributedWithinCircle,
+ )
+
+ def validate(self, ctx):
+ match self.type:
+ case "FIXED":
+ self.fixed.validate(f"{ctx}.fixed")
+ case "CELL":
+ self.cell.validate(f"{ctx}.cell")
+ case "NETWORK":
+ self.network.validate(f"{ctx}.network")
+ case "UNIFORM_DIST":
+ self.uniform_dist.validate(f"{ctx}.uniform_dist")
+ case _:
+ raise NotImplementedError(
+ f"ParametersSingleEarthStation.Location.type = {self.type} has no validation implemented!",
+ )
+
+ location: Location = field(default_factory=Location)
+
+ geometry: EarthStationGeometry = field(
+ default_factory=EarthStationGeometry,
+ )
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ # this is needed because nested parameters
+ # don't know/cannot access parents attributes
+ self.antenna.set_external_parameters(
+ frequency=self.frequency,
+ )
+
+ # this parameter is required in system get description
+ self.antenna_pattern = self.antenna.pattern
+
+ # this should be done by validating this parameters only if it is the selected system on the general section
+ # TODO: make this better by changing the Parameters class itself
+ should_validate = any(
+ v is not None for v in [
+ self.frequency, self.bandwidth,
+ ]
+ )
+
+ if should_validate:
+ self.validate(self.section_name)
+
+ def validate(self, ctx="single_earth_station"):
+ super().validate(ctx)
+
+ if None in [self.frequency, self.bandwidth, self.channel_model]:
+ raise ValueError(
+ "ParametersSingleEarthStation required parameters are not all set",
+ )
+
+ if self.season not in ["WINTER", "SUMMER"]:
+ raise ValueError(
+ f"{ctx}.season needs to be either 'WINTER' or 'SUMMER'",
+ )
+
+ if self.channel_model not in ["FSPL", "P619", "P452", "TerrestrialSimple", "TVRO-URBAN", "TVRO-SUBURBAN"]:
+ raise ValueError(
+ f"{ctx}.channel_model" +
+ "needs to be in ['FSPL', 'P619', 'P452', 'TerrestrialSimple', 'TVRO-URBAN', 'TVRO-SUBURBAN']",
+ )
diff --git a/sharc/parameters/parameters_single_space_station.py b/sharc/parameters/parameters_single_space_station.py
new file mode 100644
index 000000000..9be33587a
--- /dev/null
+++ b/sharc/parameters/parameters_single_space_station.py
@@ -0,0 +1,213 @@
+from dataclasses import dataclass, field
+import typing
+
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_antenna import ParametersAntenna
+from sharc.parameters.parameters_p619 import ParametersP619
+from sharc.parameters.parameters_p452 import ParametersP452
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
+
+
+@dataclass
+class ParametersSingleSpaceStation(ParametersBase):
+ """
+ Defines parameters for a single generic space station
+ """
+ section_name: str = "single_space_station"
+ nested_parameters_enabled: bool = True
+ is_space_to_earth: bool = True
+
+ # Sensor center frequency [MHz]
+ frequency: float = None # Center frequency of the sensor in MHz
+
+ # Sensor bandwidth [MHz]
+ bandwidth: float = None # Bandwidth of the sensor in MHz
+
+ # System receive noise temperature [K]
+ noise_temperature: float = None
+
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: float = None
+
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: float = None
+
+ # Antenna pattern of the sensor
+ antenna: ParametersAntenna = field(default_factory=ParametersAntenna)
+
+ # Receiver polarization loss
+ # e.g. could come from polarization mismatch or depolarization
+ # check if IMT parameters don't come in values for single polarization
+ # before adding loss here
+ polarization_loss: float = 0.0
+
+ # Channel model, possible values are "FSPL" (free-space path loss), "P619"
+ channel_model: typing.Literal[
+ "FSPL", "P619"
+ ] = None # Channel model to be used
+
+ param_p619: ParametersP619 = field(default_factory=ParametersP619)
+ # TODO: remove season from system parameter and put it as p619 parameter
+ season: typing.Literal["WINTER", "SUMMER"] = "SUMMER"
+
+ @dataclass
+ class SpaceStationGeometry(ParametersBase):
+ # NOTE: This does not directly translate to simulator 'height' param
+ # default is GSO altitude
+ altitude: float = 35786000.0
+ es_altitude: float = 1200.0
+ es_long_deg: float = -47.882778
+ es_lat_deg: float = -15.793889
+
+ @dataclass
+ class PointingParam(ParametersBase):
+ __EXISTING_TYPES = ["FIXED", "POINTING_AT_IMT"]
+ type: typing.Literal["FIXED", "POINTING_AT_IMT"] = None
+ fixed: float = None
+
+ def validate(self, ctx):
+ if self.type not in self.__EXISTING_TYPES:
+ raise ValueError(
+ f"Invalid value for {ctx}.type. Should be one of {self.__EXISTING_TYPES}",
+ )
+
+ match self.type:
+ case "FIXED":
+ if not isinstance(self.fixed, int) and not isinstance(self.fixed, float):
+ raise ValueError(f"{ctx}.fixed should be a number")
+ case "POINTING_AT_IMT":
+ pass
+ case _:
+ raise NotImplementedError(
+ f"Validation for {ctx}.type = {self.type} is not implemented",
+ )
+
+ azimuth: PointingParam = field(
+ default_factory=lambda: ParametersSingleSpaceStation.SpaceStationGeometry.PointingParam(type="POINTING_AT_IMT"),
+ )
+ # default pointing directly downwards
+ elevation: PointingParam = field(
+ default_factory=lambda: ParametersSingleSpaceStation.SpaceStationGeometry.PointingParam(type="POINTING_AT_IMT"),
+ )
+
+ @dataclass
+ class Location(ParametersBase):
+ __EXISTING_TYPES = ["FIXED"]
+ type: typing.Literal["FIXED"] = None
+
+ @dataclass
+ class LocationFixed(ParametersBase):
+ # This should be the difference between SS lat/long and ES lat/long
+ lat_deg: float = None
+ long_deg: float = None
+
+ def validate(self, ctx):
+ if not isinstance(self.lat_deg, int) and not isinstance(self.lat_deg, float):
+ raise ValueError(f"{ctx}.lat_deg needs to be a number")
+ if not isinstance(self.long_deg, int) and not isinstance(self.long_deg, float):
+ raise ValueError(f"{ctx}.long_deg needs to be a number")
+
+ fixed: LocationFixed = field(default_factory=LocationFixed)
+
+ def validate(self, ctx):
+ match self.type:
+ case "FIXED":
+ self.fixed.validate(f"{ctx}.fixed")
+ case _:
+ raise NotImplementedError(
+ f"ParametersSingleSpaceStation.Location.type = {self.type} has no validation implemented!",
+ )
+
+ location: Location = field(default_factory=Location)
+
+ geometry: SpaceStationGeometry = field(
+ default_factory=SpaceStationGeometry,
+ )
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ self.propagate_parameters()
+
+ # this should be done by validating this parameters only if it is the selected system on the general section
+ # TODO: make this better by changing the Parameters class itself
+ should_validate = any(
+ v is not None for v in [
+ self.frequency, self.bandwidth,
+ ]
+ )
+
+ if should_validate:
+ self.validate(self.section_name)
+
+ def propagate_parameters(self):
+ if self.channel_model == "P619":
+ if self.param_p619.earth_station_alt_m != ParametersP619.earth_station_alt_m:
+ raise ValueError(
+ f"{self.section_name}.param_p619.earth_station_alt_m should not be set by hand."
+ "It is automatically set by other parameters in system"
+ )
+ if self.param_p619.earth_station_lat_deg != ParametersP619.earth_station_lat_deg:
+ raise ValueError(
+ f"{self.section_name}.param_p619.earth_station_lat_deg should not be set by hand."
+ "It is automatically set by other parameters in system"
+ )
+ if self.param_p619.space_station_alt_m != ParametersP619.space_station_alt_m:
+ raise ValueError(
+ f"{self.section_name}.param_p619.space_station_alt_m should not be set by hand."
+ "It is automatically set by other parameters in system"
+ )
+ if self.param_p619.earth_station_lat_deg != ParametersP619.earth_station_lat_deg:
+ raise ValueError(
+ f"{self.section_name}.param_p619.earth_station_lat_deg should not be set by hand."
+ "It is automatically set by other parameters in system"
+ )
+ self.param_p619.space_station_alt_m = self.geometry.altitude
+ self.param_p619.earth_station_alt_m = self.geometry.es_altitude
+ self.param_p619.earth_station_lat_deg = self.geometry.es_lat_deg
+
+ if self.geometry.location.type == "FIXED":
+ self.param_p619.earth_station_long_diff_deg = self.geometry.location.fixed.long_deg - self.geometry.es_long_deg
+ else:
+ self.param_p619.earth_station_long_diff_deg = None
+
+ # this is needed because nested parameters
+ # don't know/cannot access parents attributes
+ self.antenna.set_external_parameters(
+ frequency=self.frequency,
+ )
+
+ # this parameter is required in system get description
+ self.antenna_pattern = self.antenna.pattern
+
+ def validate(self, ctx="single_space_station"):
+ super().validate(ctx)
+
+ if None in [self.frequency, self.bandwidth, self.channel_model, self.tx_power_density]:
+ raise ValueError(
+ "ParametersSingleSpaceStation required parameters are not all set",
+ )
+
+ if self.season not in ["WINTER", "SUMMER"]:
+ raise ValueError(
+ f"{ctx}.season needs to be either 'WINTER' or 'SUMMER'",
+ )
+
+ if self.channel_model not in ["FSPL", "P619"]:
+ raise ValueError(
+ f"{ctx}.channel_model" +
+ "needs to be in ['FSPL', 'P619']",
+ )
diff --git a/sharc/parameters/parameters_space_station.py b/sharc/parameters/parameters_space_station.py
new file mode 100644
index 000000000..8d4493124
--- /dev/null
+++ b/sharc/parameters/parameters_space_station.py
@@ -0,0 +1,136 @@
+from dataclasses import dataclass
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.parameters_p619 import ParametersP619
+import math
+from sharc.parameters.constants import EARTH_RADIUS
+
+
+@dataclass
+class ParametersSpaceStation(ParametersBase):
+ """
+ Defines parameters that should be used for orbiting Space Stations.
+ TODO: use for FSS_SS in the future as well.
+ """
+ section_name: str = "space_station"
+ is_space_to_earth: bool = True
+
+ # Satellite center frequency [MHz]
+ frequency: float = 0.0 # Center frequency of the satellite in MHz
+
+ # Satellite bandwidth [MHz]
+ bandwidth: float = 0.0 # Bandwidth of the satellite in MHz
+
+ # Off-nadir pointing angle [deg]. Should only be set if elevation is not set
+ nadir_angle: float = 0.0 # Angle in degrees away from the nadir point
+ # Elevation angle [deg]. Should only be set if nadir_angle is not set
+ elevation: float = 0.0
+
+ # Satellite altitude [m]
+ altitude: float = 0.0 # Altitude of the satellite above the Earth's surface in meters
+
+ # satellite latitude [deg]
+ lat_deg: float = 0.0
+
+ # Antenna pattern of the satellite
+ antenna_pattern: str | None = None # Antenna radiation pattern
+
+ # Antenna efficiency for pattern
+ # Efficiency factor for the antenna, range from 0 to 1
+ antenna_efficiency: float = 0.0
+
+ # Antenna diameter
+ antenna_diameter: float = 0.0 # Diameter of the antenna in meters
+
+ # Receive antenna gain - applicable for 9a, 9b and OMNI [dBi]
+ antenna_gain: float = 0.0 # Gain of the antenna in dBi
+
+ # Channel model, possible values are "FSPL" (free-space path loss), "P619"
+ channel_model: str = "FSPL" # Channel model to be used
+
+ # Parameters for the P.619 propagation model
+ # earth_station_alt_m - altitude of IMT system (in meters)
+ # earth_station_lat_deg - latitude of IMT system (in degrees)
+ # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ # season - season of the year.
+ param_p619 = ParametersP619()
+ earth_station_alt_m: float = 0.0
+ earth_station_lat_deg: float = 0.0
+ earth_station_long_diff_deg: float = 0.0
+ season: str = "SUMMER"
+
+ # This parameter is also used by P619, but should not be set manually.
+ # this should always == params.altitude
+ space_station_alt_m: float = None
+
+ def load_parameters_from_file(self, config_file: str):
+ """
+ Load the parameters from a file and run a sanity check.
+
+ Parameters
+ ----------
+ config_file : str
+ The path to the configuration file.
+
+ Raises
+ ------
+ ValueError
+ If a parameter is not valid.
+ """
+ super().load_parameters_from_file(config_file)
+
+ if self.space_station_alt_m is not None:
+ raise ValueError(
+ "'space_station_alt_m' should not be set manually. It is always equal to 'altitude'",
+ )
+
+ if self.nadir_angle != 0 and self.elevation != 0:
+ raise ValueError("'elevation' and 'nadir_angle' should not both be set at the same time. Choose either\
+ parameter to set")
+
+ # Check channel model
+ if self.channel_model not in ["FSPL", "P619"]:
+ raise ValueError(
+ "Invalid channel_model, must be either 'FSPL' or 'P619'",
+ )
+
+ if self.channel_model == "P619":
+ # check necessary parameters for P619
+ if None in [
+ self.param_p619, self.earth_station_alt_m, self.earth_station_lat_deg, self.earth_station_long_diff_deg,
+ ]:
+ raise ValueError("When using P619 should set 'self.earth_station_alt_m', 'self.earth_station_lat_deg',\
+ 'self.earth_station_long_diff_deg' deafult or on parameter file and 'param_p619' \
+ on child class default")
+ # Check season
+ if self.season not in ["SUMMER", "WINTER"]:
+ raise ValueError(
+ "Invalid season, must be either 'SUMMER' or 'WINTER'",
+ )
+
+ self.set_derived_parameters()
+
+ def set_derived_parameters(self):
+ self.space_station_alt_m = self.altitude
+
+ if self.param_p619:
+ self.param_p619.load_from_paramters(self)
+
+ if self.elevation != 0.0:
+ # this relationship comes directly from law of sines
+ self.nadir_angle = math.degrees(
+ math.asin(
+ EARTH_RADIUS * math.sin(math.radians(self.elevation + 90)) /
+ (EARTH_RADIUS + self.altitude),
+ ),
+ )
+ elif self.nadir_angle != 0.0:
+ # this relationship comes directly from law of sines
+ # can also be derived from incidence angle according to Rec. ITU-R RS.1861-0
+ self.elevation = math.degrees(
+ math.asin(
+ (EARTH_RADIUS + self.altitude) *
+ math.sin(math.radians(self.nadir_angle)) /
+ EARTH_RADIUS,
+ ),
+ ) - 90
diff --git a/sharc/plot.py b/sharc/plot.py
index 863391340..a99a18de0 100644
--- a/sharc/plot.py
+++ b/sharc/plot.py
@@ -5,8 +5,9 @@
@author: edgar
"""
+
class Plot(object):
-
+
def __init__(self, x, y, x_label, y_label, title, file_name, **kwargs):
self.x = x
self.y = y
@@ -14,12 +15,12 @@ def __init__(self, x, y, x_label, y_label, title, file_name, **kwargs):
self.y_label = y_label
self.title = title
self.file_name = file_name
-
+
if "x_lim" in kwargs:
self.x_lim = kwargs["x_lim"]
else:
self.x_lim = None
-
+
if "y_lim" in kwargs:
self.y_lim = kwargs["y_lim"]
else:
diff --git a/sharc/plots/plot_cdf.py b/sharc/plots/plot_cdf.py
new file mode 100644
index 000000000..9583ac034
--- /dev/null
+++ b/sharc/plots/plot_cdf.py
@@ -0,0 +1,372 @@
+import os
+import pandas as pd
+import plotly.graph_objects as go
+
+
+def plot_cdf(
+ base_dir, file_prefix, passo_xticks=5, xaxis_title='Value', legends=None, subfolders=None, save_file=True,
+ show_plot=False,
+):
+
+ label = 'CDF'
+ # Ensure at least one of save_file or show_plot is true
+ if not save_file and not show_plot:
+ raise ValueError("Either save_file or show_plot must be True.")
+
+ # Define the base directory dynamically based on user input
+ workfolder = os.path.dirname(os.path.abspath(__file__))
+ csv_folder = os.path.abspath(
+ os.path.join(
+ workfolder, '..', "campaigns", base_dir, "output",
+ ),
+ )
+ figs_folder = os.path.abspath(
+ os.path.join(
+ workfolder, '..', "campaigns", base_dir, "output", "figs",
+ ),
+ )
+
+ # Check if the figs output folder exists, if not, create it
+ if not os.path.exists(figs_folder):
+ os.makedirs(figs_folder)
+
+ # List all subfolders in the base directory or only those specified by the user
+ if subfolders:
+ subdirs = [
+ os.path.join(csv_folder, d) for d in subfolders if os.path.isdir(
+ os.path.join(csv_folder, d),
+ )
+ ]
+ else:
+ subdirs = [
+ os.path.join(csv_folder, d) for d in os.listdir(csv_folder)
+ if os.path.isdir(os.path.join(csv_folder, d)) and d.startswith(f"output_{base_dir}_")
+ ]
+
+ # Validate the number of legends
+ if legends and len(legends) != len(subdirs):
+ raise ValueError(
+ "The number of provided legends does not match the number of found subfolders.",
+ )
+
+ # Initialize global min and max values
+ global_min = float('inf')
+ global_max = float('-inf')
+
+ # First, calculate the global min and max values
+ for subdir in subdirs:
+ all_files = [
+ f for f in os.listdir(subdir) if f.endswith('.csv',) and label in f and file_prefix in f
+ ]
+
+ for file_name in all_files:
+ file_path = os.path.join(subdir, file_name)
+ if os.path.exists(file_path):
+ try:
+ # Try reading the .csv file using pandas with different delimiters
+ try:
+ data = pd.read_csv(
+ file_path, delimiter=',', skiprows=1,
+ )
+ except pd.errors.ParserError:
+ data = pd.read_csv(
+ file_path, delimiter=';', skiprows=1,
+ )
+
+ # Ensure the data has at least two columns
+ if data.shape[1] < 2:
+ print(
+ f"The file {file_name} does not have enough columns to plot.",
+ )
+ continue
+
+ # Remove rows that do not contain valid numeric values
+ data = data.apply(pd.to_numeric, errors='coerce').dropna()
+
+ if not data.empty:
+ global_min = min(global_min, data.iloc[:, 0].min())
+ global_max = max(global_max, data.iloc[:, 0].max())
+ except Exception as e:
+ print(f"Error processing the file {file_name}: {e}")
+
+ # If no valid data was found, set reasonable defaults for global_min and global_max
+ if global_min == float('inf') or global_max == float('-inf'):
+ global_min, global_max = 0, 1
+
+ # Plot the graphs adjusting the axes
+ fig = go.Figure()
+ for idx, subdir in enumerate(subdirs):
+ all_files = [
+ f for f in os.listdir(subdir) if f.endswith('.csv',) and label in f and file_prefix in f
+ ]
+ legenda = legends[idx] if legends else os.path.basename(
+ subdir,
+ ).split(f"output_{base_dir}_")[1]
+
+ for file_name in all_files:
+ file_path = os.path.join(subdir, file_name)
+ if os.path.exists(file_path):
+ try:
+ # Try reading the .csv file using pandas with different delimiters
+ try:
+ data = pd.read_csv(
+ file_path, delimiter=',', skiprows=1,
+ )
+ except pd.errors.ParserError:
+ data = pd.read_csv(
+ file_path, delimiter=';', skiprows=1,
+ )
+
+ # Ensure the data has at least two columns
+ if data.shape[1] < 2:
+ print(
+ f"The file {file_name} does not have enough columns to plot.",
+ )
+ continue
+
+ # Remove rows that do not contain valid numeric values
+ data = data.apply(pd.to_numeric, errors='coerce').dropna()
+
+ # Check if there are enough data points to plot
+ if data.empty or data.shape[0] < 2:
+ print(
+ f"The file {file_name} does not have enough data to plot.",
+ )
+ continue
+
+ # Plot the CDF
+ fig.add_trace(
+ go.Scatter(x=data.iloc[:, 0], y=data.iloc[:, 1], mode='lines', name=f'{legenda}',),
+ )
+ except Exception as e:
+ print(f"Error processing the file {file_name}: {e}")
+
+ # Graph configurations
+ fig.update_layout(
+ title=f'CDF Plot for {file_prefix}',
+ xaxis_title=xaxis_title,
+ yaxis_title='CDF',
+ yaxis=dict(tickmode='array', tickvals=[0, 0.25, 0.5, 0.75, 1]),
+ xaxis=dict(tickmode='linear', tick0=global_min, dtick=passo_xticks),
+ legend_title="Labels",
+ )
+
+ # Show the figure if requested
+ if show_plot:
+ fig.show()
+
+ # Save the figure if requested
+ if save_file:
+ fig_file_path = os.path.join(
+ figs_folder, f"CDF_Plot_{file_prefix}.png",
+ )
+ fig.write_image(fig_file_path)
+ print(f"Figure saved: {fig_file_path}")
+
+# Specific functions for different metrics
+
+
+def plot_bs_antenna_gain_towards_the_ue(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_BS_antenna_gain_towards_the_UE', passo_xticks, xaxis_title='Antenna Gain (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_coupling_loss(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_coupling_loss', passo_xticks, xaxis_title='Coupling Loss (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_dl_sinr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_DL_SINR', passo_xticks, xaxis_title='DL SINR (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_dl_snr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_DL_SNR', passo_xticks, xaxis_title='DL SNR (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_dl_throughput(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_DL_throughput', passo_xticks, xaxis_title='DL Throughput (Mbps)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_dl_transmit_power(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_DL_transmit_power', passo_xticks, xaxis_title='DL Transmit Power (dBm)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_imt_station_antenna_gain_towards_system(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_IMT_station_antenna_gain_towards_system', passo_xticks,
+ xaxis_title='Antenna Gain (dB)', legends=legends, subfolders=subfolders, save_file=save_file,
+ show_plot=show_plot,
+ )
+
+
+def plot_path_loss(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_path_loss', passo_xticks, xaxis_title='Path Loss (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_ue_antenna_gain_towards_the_bs(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'IMT_CDF_of_UE_antenna_gain_towards_the_BS', passo_xticks, xaxis_title='Antenna Gain (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_imt_to_system_path_loss(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_IMT_to_system_path_loss', passo_xticks, xaxis_title='Path Loss (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_system_antenna_towards_imt_stations(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_system_antenna_gain_towards_IMT_stations', passo_xticks,
+ xaxis_title='Antenna Gain (dB)', legends=legends, subfolders=subfolders, save_file=save_file,
+ show_plot=show_plot,
+ )
+
+
+def plot_system_inr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_system_INR', passo_xticks, xaxis_title='INR (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_system_interference_power_from_imt_dl(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_system_interference_power_from_IMT_DL', passo_xticks,
+ xaxis_title='Interference Power (dBm/MHz)', legends=legends, subfolders=subfolders, save_file=save_file,
+ show_plot=show_plot,
+ )
+
+
+def plot_system_interference_power_from_imt_ul(
+ base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True,
+ show_plot=True,
+):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_system_interference_power_from_IMT_UL', passo_xticks,
+ xaxis_title='Interference Power (dBm/MHz)', legends=legends, subfolders=subfolders, save_file=save_file,
+ show_plot=show_plot,
+ )
+
+
+def plot_system_pfd(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'SYS_CDF_of_system_PFD', passo_xticks, xaxis_title='PFD (dBW/mยฒ)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+
+def plot_inr_samples(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True):
+ plot_cdf(
+ base_dir, 'INR_samples', passo_xticks, xaxis_title='INR Samples (dB)',
+ legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+
+# Main function to identify labels and call the appropriate functions
+
+
+def all_plots(base_dir, legends=None, subfolders=None, save_file=True, show_plot=False):
+ plot_bs_antenna_gain_towards_the_ue(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_coupling_loss(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_dl_sinr(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_dl_snr(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_dl_throughput(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_dl_transmit_power(
+ base_dir, legends=legends,
+ subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_imt_station_antenna_gain_towards_system(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_path_loss(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_ue_antenna_gain_towards_the_bs(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_imt_to_system_path_loss(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_system_antenna_towards_imt_stations(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_system_inr(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+ plot_system_interference_power_from_imt_dl(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_system_interference_power_from_imt_ul(
+ base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot,
+ )
+ plot_system_pfd(
+ base_dir, legends=legends, subfolders=subfolders,
+ save_file=save_file, show_plot=show_plot,
+ )
+
+
+if __name__ == "__main__":
+ # Example usage
+ name = "imt_hibs_ras_2600_MHz"
+ legends = None # Replace with a list of legends if needed
+ subfolders = None # Replace with a list of specific subfolders if needed
+ all_plots(
+ name, legends=legends, subfolders=subfolders,
+ save_file=True, show_plot=False,
+ )
diff --git a/sharc/post_processor.py b/sharc/post_processor.py
new file mode 100644
index 000000000..7283b97e9
--- /dev/null
+++ b/sharc/post_processor.py
@@ -0,0 +1,700 @@
+from sharc.results import Results
+
+from dataclasses import dataclass, field
+import plotly.graph_objects as go
+from plotly.colors import DEFAULT_PLOTLY_COLORS
+import os
+import numpy as np
+import scipy
+import typing
+import pathlib
+import pathlib
+
+
+class FieldStatistics:
+ field_name: str
+ median: float
+ mean: float
+ variance: float
+ confidence_interval: (float, float)
+ standard_deviation: float
+
+ def load_from_sample(
+ self, field_name: str, sample: list[float], *, confidence=0.95
+ ) -> "FieldStatistics":
+ self.field_name = field_name
+ self.median = np.median(sample)
+ self.mean = np.mean(sample)
+ self.variance = np.var(sample)
+ self.standard_deviation = np.std(sample)
+ # @important TODO: check if using t distribution here is correct
+ self.confidence_interval = scipy.stats.norm.interval(
+ confidence, loc=self.mean, scale=self.standard_deviation
+ )
+ return self
+
+ def __str__(self):
+ attr_names = filter(
+ lambda x: x != "field_name"
+ and not x.startswith("__")
+ and not callable(getattr(self, x)),
+ dir(self),
+ )
+ readable_attrs = "\n".join(
+ list(
+ map(
+ lambda attr_name: f"\t{attr_name}: {getattr(self, attr_name)}",
+ attr_names,
+ )
+ )
+ )
+ return f"""{self.field_name}:
+{readable_attrs}"""
+
+
+class ResultsStatistics:
+ fields_statistics: list[FieldStatistics]
+ results_output_dir: str = "default_output"
+
+ def load_from_results(self, result: Results) -> "ResultsStatistics":
+ """
+ Loads all relevant attributes from result and generates their statistics
+ """
+ self.results_output_dir = result.output_directory
+ self.fields_statistics = []
+ attr_names = result.get_relevant_attributes()
+ for attr_name in attr_names:
+ samples = getattr(result, attr_name)
+ if len(samples) == 0:
+ continue
+ self.fields_statistics.append(
+ FieldStatistics().load_from_sample(attr_name, samples)
+ )
+
+ return self
+
+ def write_to_results_dir(self, filename="stats.txt") -> "ResultsStatistics":
+ """
+ Writes statistics file to the same directory of the results loaded into this class
+ """
+ with open(os.path.join(self.results_output_dir, filename), "w") as f:
+ f.write(str(self))
+
+ return self
+
+ def get_stat_by_name(self, field_name: str) -> typing.Union[None, FieldStatistics]:
+ """
+ Gets a single field's statistics by its name.
+ E.g.: get_stat_by_name("system_dl_interf_power")
+ Returns
+ None if not found
+ FieldStatistics if found only one match
+ """
+ stats_found = filter(lambda field_stat: field_stat.field_name == field_name, self.fields_statistics)
+
+ if len(stats_found) > 1:
+ raise Exception(
+ f"ResultsStatistics.get_stat_by_name found more than one statistic by the field name '{field_name}'\n"
+ + "You probably loaded more than one result to the same ResultsStatistics object"
+ )
+
+ if len(stats_found) == 0:
+ return None
+
+ return stats_found[0]
+
+ def __str__(self):
+ return f"[{self.results_output_dir}]\n{'\n'.join(list(map(str, self.fields_statistics)))}"
+
+
+@dataclass
+class PostProcessor:
+ """
+ PostProcessor provides utilities for plotting, aggregating, and analyzing simulation results,
+ including CDF/CCDF plot generation, statistics calculation, and plot saving.
+ """
+ IGNORE_FIELD = {
+ "title": "ANTES NAO PLOTAVAMOS ISSO, ENTรO CONTINUA SEM PLOTAR",
+ "x_label": "",
+ }
+ # TODO: move units in the result to the Results class instead of Plot Info?
+ # TODO: rename x_label to something else when making plots other than cdf
+ RESULT_FIELDNAME_TO_PLOT_INFO = {
+ "imt_ul_tx_power_density": {
+ "x_label": "Transmit power density [dBm/Hz]",
+ "title": "[IMT] UE transmit power density",
+ },
+ "imt_ul_tx_power": {
+ "x_label": "Transmit power [dBm]",
+ "title": "[IMT] UE transmit power",
+ },
+ "imt_ul_sinr_ext": {
+ "x_label": "SINR [dB]",
+ "title": "[IMT] UL SINR with external interference",
+ },
+ "imt_ul_snr": {
+ "title": "[IMT] UL SNR",
+ "x_label": "SNR [dB]",
+ },
+ "imt_ul_inr": {
+ "title": "[IMT] UL interference-to-noise ratio",
+ "x_label": "$I/N$ [dB]",
+ },
+ "imt_ul_sinr": {
+ "x_label": "SINR [dB]",
+ "title": "[IMT] UL SINR",
+ },
+ "imt_system_build_entry_loss": {
+ "x_label": "Building entry loss [dB]",
+ "title": "[SYS] IMT to system building entry loss",
+ },
+ "imt_ul_tput_ext": {
+ "title": "[IMT] UL throughput with external interference",
+ "x_label": "Throughput [bits/s/Hz]",
+ },
+ "imt_ul_tput": {
+ "title": "[IMT] UL throughput",
+ "x_label": "Throughput [bits/s/Hz]",
+ },
+ "imt_path_loss": {
+ "title": "[IMT] path loss",
+ "x_label": "Path loss [dB]",
+ },
+ "imt_coupling_loss": {
+ "title": "[IMT] coupling loss",
+ "x_label": "Coupling loss [dB]",
+ },
+ "imt_bs_antenna_gain": {
+ "x_label": "Antenna gain [dBi]",
+ "title": "[IMT] BS antenna gain towards the UE",
+ },
+ "imt_ue_antenna_gain": {
+ "x_label": "Antenna gain [dBi]",
+ "title": "[IMT] UE antenna gain towards the BS",
+ },
+ "system_imt_antenna_gain": {
+ "x_label": "Antenna gain [dBi]",
+ "title": "[SYS] system antenna gain towards IMT stations",
+ },
+ "imt_system_antenna_gain": {
+ "x_label": "Antenna gain [dBi]",
+ "title": "[IMT] IMT station antenna gain towards system",
+ },
+ "imt_system_path_loss": {
+ "x_label": "Path Loss [dB]",
+ "title": "[SYS] IMT to system path loss",
+ },
+ "sys_to_imt_coupling_loss": {
+ "x_label": "Coupling Loss [dB]",
+ "title": "[SYS] IMT to system coupling loss",
+ },
+ "system_dl_interf_power": {
+ "x_label": "Interference Power [dBm/BMHz]",
+ "title": "[SYS] system interference power from IMT DL",
+ },
+ "imt_system_diffraction_loss": {
+ "x_label": "Building entry loss [dB]",
+ "title": "[SYS] IMT to system diffraction loss",
+ },
+ "imt_dl_sinr_ext": {
+ "x_label": "SINR [dB]",
+ "title": "[IMT] DL SINR with external interference",
+ },
+ "imt_dl_sinr": {
+ "x_label": "SINR [dB]",
+ "title": "[IMT] DL SINR",
+ },
+ "imt_dl_snr": {
+ "title": "[IMT] DL SNR",
+ "x_label": "SNR [dB]",
+ },
+ "imt_dl_inr": {
+ "title": "[IMT] DL interference-to-noise ratio",
+ "x_label": "$I/N$ [dB]",
+ },
+ "imt_dl_tput_ext": {
+ "title": "[IMT] DL throughput with external interference",
+ "x_label": "Throughput [bits/s/Hz]",
+ },
+ "imt_dl_tput": {
+ "title": "[IMT] DL throughput",
+ "x_label": "Throughput [bits/s/Hz]",
+ },
+ "system_ul_interf_power": {
+ "title": "[SYS] system interference power from IMT UL",
+ "x_label": "Interference Power [dBm/BMHz]",
+ },
+ "system_ul_interf_power_per_mhz": {
+ "title": "[SYS] system interference PSD from IMT UL",
+ "x_label": "Interference Power [dBm/MHz]",
+ },
+ "system_dl_interf_power_per_mhz": {
+ "title": "[SYS] system interference PSD from IMT DL",
+ "x_label": "Interference Power [dBm/MHz]",
+ },
+ "system_inr": {
+ "title": "[SYS] system INR",
+ "x_label": "INR [dB]",
+ },
+ "system_pfd": {
+ "title": "[SYS] system PFD",
+ "x_label": "PFD [dBm/m^2]",
+ },
+ "imt_dl_tx_power": {
+ "x_label": "Transmit power [dBm]",
+ "title": "[IMT] DL transmit power",
+ },
+ "imt_dl_pfd_external": {
+ "title": "[IMT] DL external Power Flux Density (PFD) ",
+ "x_label": "PFD [dBW/mยฒ/MHz]",
+ },
+ "imt_dl_pfd_external_aggregated": {
+ "title": "[IMT] Aggregated DL external Power Flux Density (PFD)",
+ "x_label": "PFD [dBW/mยฒ/MHz]",
+ },
+ # these ones were not plotted already, so will continue to not be plotted:
+ "imt_dl_tx_power_density": IGNORE_FIELD,
+ "system_ul_coupling_loss": IGNORE_FIELD,
+ "system_dl_coupling_loss": IGNORE_FIELD,
+ "system_rx_interf": IGNORE_FIELD,
+ }
+
+ plot_legend_patterns: list = field(default_factory=list)
+ legends_generator = None
+ linestyle_getter = None
+
+ plots: list[go.Figure] = field(default_factory=list)
+ results: list[Results] = field(default_factory=list)
+
+ def add_plot_legend_generator(
+ self, generator
+ ) -> "PostProcessor":
+ """
+ You can either add a plot generator or many plot legend patterns.
+ A generator is much more flexible.
+ """
+ if self.legends_generator is not None:
+ raise ValueError("Can only have one legends generator at a time")
+ self.legends_generator = generator
+
+ def add_results_linestyle_getter(
+ self, getter
+ ) -> None:
+ """
+ When plotting, this function will be called for each result to decide
+ on the linestyle used
+ """
+ if self.linestyle_getter is not None:
+ raise ValueError("You are trying to set PostProcessor.linestyle_getter twice!")
+ self.linestyle_getter = getter
+
+ def add_plot_legend_pattern(
+ self, *, dir_name_contains: str, legend: str
+ ) -> "PostProcessor":
+ self.plot_legend_patterns.append(
+ {"dir_name_contains": dir_name_contains, "legend": legend}
+ )
+ self.plot_legend_patterns.sort(key=lambda p: -len(p["dir_name_contains"]))
+
+ return self
+
+ def get_results_possible_legends(self, result: Results) -> list[dict]:
+ """
+ You get a list with dicts tha have at least { "legend": str } in them.
+ They may also have { "dir_name_contains": str }
+ """
+ possible = list(
+ filter(
+ lambda pl: pl["dir_name_contains"]
+ in os.path.basename(result.output_directory),
+ self.plot_legend_patterns,
+ )
+ )
+
+ if len(possible) == 0 and self.legends_generator is not None:
+ return [
+ {"legend": self.legends_generator(os.path.basename(result.output_directory))}
+ ]
+
+ return possible
+
+ def generate_cdf_plots_from_results(
+ self, results: list[Results], *, n_bins=None
+ ) -> list[go.Figure]:
+ """
+ Generates CDF (Cumulative Distribution Function) plots from a list of Results objects.
+
+ This method processes each Results object, extracts relevant attributes, and generates
+ CDF plots for each attribute using Plotly. Each plot is configured with appropriate
+ titles, axis labels, and legends. The method supports custom line styles and colors
+ for different result sets.
+
+ Args:
+ results (list[Results]): A list of Results objects containing data to plot.
+ n_bins (Optional[int], optional): Number of bins to use for the CDF calculation.
+ If None, a default binning strategy is used.
+
+ Returns:
+ list[go.Figure]: A list of Plotly Figure objects, each representing a CDF plot
+ for a relevant attribute found in the Results objects.
+ """
+ figs: dict[str, list[go.Figure]] = {}
+ COLORS = DEFAULT_PLOTLY_COLORS
+
+ linestyle_color = {}
+
+ # Sort based on path name - TODO: sort alphabeticaly by legend
+ results.sort(key=lambda r: r.output_directory)
+ for res in results:
+ if self.linestyle_getter is not None:
+ linestyle = self.linestyle_getter(res)
+ else:
+ linestyle = "solid"
+
+ if linestyle not in linestyle_color:
+ linestyle_color[linestyle] = 0
+
+ possible_legends_mapping = self.get_results_possible_legends(res)
+
+ if len(possible_legends_mapping):
+ legend = possible_legends_mapping[0]["legend"]
+ else:
+ legend = res.output_directory
+
+ attr_names = res.get_relevant_attributes()
+
+ for attr_name in attr_names:
+ attr_val = getattr(res, attr_name)
+ if not len(attr_val):
+ continue
+ if attr_name not in PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO:
+ print(
+ f"[WARNING]: {attr_name} is not a plottable field, because it does not have a configuration set on PostProcessor."
+ )
+ continue
+ attr_plot_info = PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO[attr_name]
+ if attr_plot_info == PostProcessor.IGNORE_FIELD:
+ print(
+ f"[WARNING]: {attr_name} is currently being ignored on plots."
+ )
+ continue
+ if attr_name not in figs:
+ figs[attr_name] = go.Figure()
+ figs[attr_name].update_layout(
+ title=f'CDF Plot for {attr_plot_info["title"]}',
+ xaxis_title=attr_plot_info["x_label"],
+ yaxis_title="CDF",
+ yaxis=dict(tickmode="array", tickvals=[0, 0.25, 0.5, 0.75, 1]),
+ xaxis=dict(tickmode="linear", dtick=5),
+ legend_title="Labels",
+ meta={"related_results_attribute": attr_name, "plot_type": "cdf"},
+ )
+
+ # TODO: take this fn as argument, to plot more than only cdf's
+ x, y = PostProcessor.cdf_from(attr_val, n_bins=n_bins)
+
+ fig = figs[attr_name]
+
+ fig.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode="lines",
+ name=f"{legend}",
+ line=dict(color=COLORS[linestyle_color[linestyle]], dash=linestyle)
+ ),
+ )
+
+ linestyle_color[linestyle] += 1
+ if linestyle_color[linestyle] >= len(COLORS):
+ linestyle_color[linestyle] = 0
+
+ return figs.values()
+
+ def generate_ccdf_plots_from_results(
+ self, results: list[Results], *, n_bins=None, cutoff_percentage=0.001, logy=True
+ ) -> list[go.Figure]:
+ """
+ Generates CCDF (Complementary Cumulative Distribution Function) plots from a list of Results objects.
+
+ This method processes each Results object, extracts relevant attributes, and generates
+ CCDF plots for each attribute using Plotly. Each plot is configured with appropriate
+ titles, axis labels, and legends. The method supports log-scale y-axes and custom cutoff percentages.
+
+ Args:
+ results (list[Results]): A list of Results objects containing data to plot.
+ n_bins (Optional[int], optional): Number of bins to use for the CCDF calculation.
+ If None, a default binning strategy is used.
+ cutoff_percentage (float, optional): The minimum probability to display on the y-axis.
+ logy (bool, optional): Whether to use a logarithmic scale for the y-axis.
+
+ Returns:
+ list[go.Figure]: A list of Plotly Figure objects, each representing a CCDF plot
+ for a relevant attribute found in the Results objects.
+ """
+ figs: dict[str, list[go.Figure]] = {}
+
+ for res in results:
+ possible_legends_mapping = self.get_results_possible_legends(res)
+
+ if len(possible_legends_mapping):
+ legend = possible_legends_mapping[0]["legend"]
+ else:
+ legend = res.output_directory
+
+ attr_names = res.get_relevant_attributes()
+
+ next_tick = 1
+ ticks_at = []
+ while next_tick > cutoff_percentage:
+ ticks_at.append(next_tick)
+ next_tick /= 10
+ ticks_at.append(cutoff_percentage)
+ ticks_at.reverse()
+
+ for attr_name in attr_names:
+ attr_val = getattr(res, attr_name)
+ if not len(attr_val):
+ continue
+ if attr_name not in PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO:
+ print(
+ f"[WARNING]: {attr_name} is not a plottable field, because it does not have a configuration set on PostProcessor."
+ )
+ continue
+ attr_plot_info = PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO[attr_name]
+ if attr_plot_info == PostProcessor.IGNORE_FIELD:
+ print(
+ f"[WARNING]: {attr_name} is currently being ignored on plots."
+ )
+ continue
+ if attr_name not in figs:
+ figs[attr_name] = go.Figure()
+ figs[attr_name].update_layout(
+ title=f'CCDF Plot for {attr_plot_info["title"]}',
+ xaxis_title=attr_plot_info["x_label"],
+ yaxis_title="$\\text{P } I > X$",
+ yaxis=dict(tickmode="array", tickvals=ticks_at, type="log", range=[np.log10(cutoff_percentage), 0]),
+ xaxis=dict(tickmode="linear", dtick=5),
+ legend_title="Labels",
+ meta={"related_results_attribute": attr_name, "plot_type": "ccdf"},
+ )
+
+ # TODO: take this fn as argument, to plot more than only cdf's
+ x, y = PostProcessor.ccdf_from(attr_val, n_bins=n_bins)
+
+ fig = figs[attr_name]
+
+ fig.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ mode="lines",
+ name=f"{legend}",
+ ),
+ )
+ # A trick to plog semi-logy plots with better scientific aspect.
+ # I should have left it to the user to decide if they want logy or not,
+ # but I think it is better to have it by default.
+ if logy:
+ fig.update_yaxes(type="log")
+ yticks = []
+ n_right_zeros = -int(np.floor(np.log10(cutoff_percentage)))
+ for i in range(n_right_zeros, 0, -1):
+ for j in range(1, 10):
+ yticks.append(j * (10**(-i)))
+ yticks = yticks + [1.0]
+ major_yticks = [10**(-i) for i in range(n_right_zeros)]
+ ytick_text = [str(v) if v in major_yticks else "" for v in yticks]
+ fig.update_yaxes(tickvals=yticks, ticktext=ytick_text)
+
+ return figs.values()
+
+ def add_plots(self, plots: list[go.Figure]) -> None:
+ self.plots.extend(plots)
+
+ def add_results(self, results: list[Results]) -> None:
+ self.results.extend(results)
+
+ def get_results_by_output_dir(self, dir_name_contains: str, *, single_result=True):
+ filtered_results = list(
+ filter(
+ lambda res: dir_name_contains in os.path.basename(res.output_directory),
+ self.results,
+ )
+ )
+ if len(filtered_results) == 0:
+ raise ValueError(
+ f"Could not find result that contains '{dir_name_contains}'"
+ )
+
+ if len(filtered_results) > 1:
+ raise ValueError(
+ f"There is more than one possible result with pattern '{dir_name_contains}'"
+ )
+
+ return filtered_results[0]
+
+ def get_plot_by_results_attribute_name(self, attr_name: str, *, plot_type="cdf") -> go.Figure:
+ """
+ You can get a plot using an attribute name from Results.
+ See Results class to check what attributes exist.
+ plot_type: 'cdf', 'ccdf'
+ """
+ filtered = list(
+ filter(
+ lambda x: x.layout.meta["related_results_attribute"] == attr_name and x.layout.meta["plot_type"] == plot_type,
+ self.plots,
+ )
+ )
+
+ if 0 == len(filtered):
+ return None
+
+ return filtered[0]
+
+ @staticmethod
+ def aggregate_results(
+ *,
+ dl_samples: list[float],
+ ul_samples: list[float],
+ ul_tdd_factor: float,
+ n_bs_sim: int,
+ n_bs_actual: int,
+ random_number_gen=np.random.RandomState(31),
+ ):
+ """
+ The method was adapted from document 'TG51_201805_E07_FSS_Uplink_ study 48GHz_GSMA_v1.5.pdf',
+ a document created for Task Group 5/1.
+ This is used to aggregate both uplink and downlink interference towards another system
+ into a result that makes more sense to the case study.
+ Inputs:
+ downlink/uplink_result: list[float]
+ Samples that should be aggregated.
+ ul_tdd_factor: float
+ The tdd ratio that uplink is activated for.
+ n_bs_sim: int
+ Number of simulated base stations.
+ Should probably be 7 * 19 * 3 * 3 or 1 * 19 * 3 * 3
+ n_bs_actual: int
+ The number of base stations the study wants to have conclusions for.
+ random_number_gen: np.random.RandomState
+ Since this methods uses another montecarlo to aggregate results,
+ it needs a random number generator
+ """
+ if ul_tdd_factor > 1 or ul_tdd_factor < 0:
+ raise ValueError(
+ "PostProcessor.aggregate_results() was called with invalid ul_tdd_factor parameter."
+ + f"ul_tdd_factor must be in interval [0, 1], but is {ul_tdd_factor}"
+ )
+
+ segment_factor = round(n_bs_actual / n_bs_sim)
+
+ dl_tdd_factor = 1 - ul_tdd_factor
+
+ if ul_tdd_factor == 0:
+ n_aggregate = len(dl_samples)
+ elif dl_tdd_factor == 0:
+ n_aggregate = len(ul_samples)
+ else:
+ n_aggregate = min(len(ul_samples), len(dl_samples))
+
+ aggregate_samples = np.empty(n_aggregate)
+
+ for i in range(n_aggregate):
+ # choose S random samples
+ ul_random_indexes = np.floor(
+ random_number_gen.random(size=segment_factor)
+ * len(ul_samples)
+ )
+ dl_random_indexes = np.floor(
+ random_number_gen.random(size=segment_factor)
+ * len(dl_samples)
+ )
+ aggregate_samples[i] = 0
+
+ if ul_tdd_factor:
+ for j in ul_random_indexes: # random samples
+ aggregate_samples[i] += (
+ np.power(10, ul_samples[int(j)] / 10) * ul_tdd_factor
+ )
+
+ if dl_tdd_factor:
+ for j in dl_random_indexes: # random samples
+ aggregate_samples[i] += (
+ np.power(10, dl_samples[int(j)] / 10) * dl_tdd_factor
+ )
+
+ # convert back to dB or dBm (as was previously)
+ aggregate_samples[i] = 10 * np.log10(
+ aggregate_samples[i]
+ )
+
+ return aggregate_samples
+
+ @staticmethod
+ def cdf_from(data: list[float], *, n_bins=None) -> (list[float], list[float]):
+ """
+ Takes a dataset and returns both axis of a cdf (x, y)
+ """
+ values, base = np.histogram(
+ data,
+ bins=n_bins if n_bins is not None else "auto",
+ )
+ cumulative = np.cumsum(values)
+ x = base[:-1]
+ y = cumulative / cumulative[-1]
+
+ return (x, y)
+
+ @staticmethod
+ def ccdf_from(data: list[float], *, n_bins=None) -> (list[float], list[float]):
+ """
+ Takes a dataset and returns both axis of a ccdf (x, y)
+ """
+ x, y = PostProcessor.cdf_from(data, n_bins=n_bins)
+
+ return (x, 1 - y)
+
+ @staticmethod
+ def save_plots(
+ dir: str,
+ plots: list[go.Figure],
+ *,
+ width=1200,
+ height=800
+ ) -> None:
+ """
+ dir: A directory path on which to save the plot files
+ plots: Figures to save. They are saved by their name
+ """
+ pathlib.Path(dir).mkdir(parents=True, exist_ok=True)
+
+ for plot in plots:
+ # TODO: check if reset to previous state is functional
+ # so much state used in this post processor, should def. migrate
+ # post processing to Haskell (obv. not rly)
+ prev_autosize = plot.layout.autosize
+ prev_width = plot.layout.width
+ prev_height = plot.layout.height
+
+ plot.update_layout(
+ autosize=False,
+ width=width,
+ height=height
+ )
+
+ plot.write_image(os.path.join(dir, f"{plot.layout.title.text}.jpg"))
+
+ plot.update_layout(
+ autosize=prev_autosize,
+ width=prev_width,
+ height=prev_height
+ )
+
+ @staticmethod
+ def generate_statistics(result: Results) -> ResultsStatistics:
+ return ResultsStatistics().load_from_results(result)
+
+ @staticmethod
+ def generate_sample_statistics(fieldname: str, sample: list[float]) -> ResultsStatistics:
+ return FieldStatistics().load_from_sample(fieldname, sample)
diff --git a/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv b/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv
new file mode 100644
index 000000000..1a0534933
--- /dev/null
+++ b/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv
@@ -0,0 +1,91 @@
+apparent_elevation,loss
+0,1.6972748682898624
+1,1.014727203552297
+2,0.6949893820538064
+3,0.5192948386632888
+4,0.410974371448515
+5,0.3385733887990494
+6,0.2872066989323976
+7,0.2490691608495016
+8,0.21973085330778497
+9,0.19651352250780812
+10,0.17771245593414225
+11,0.1621954400421193
+12,0.14918320109111569
+13,0.13812292210399307
+14,0.12861214208533311
+15,0.12035122108906711
+16,0.11311266573642513
+17,0.10672076205657326
+18,0.1010377074383433
+19,0.09595395437041371
+20,0.09138135119434165
+21,0.0872481814845774
+22,0.08349551794281049
+23,0.08007450281263388
+24,0.0769442920534981
+25,0.07407048214413517
+26,0.07142389262271462
+27,0.06897961413419912
+28,0.06671625694285364
+29,0.06461535242677624
+30,0.06266087248080865
+31,0.060838840636564535
+32,0.05913701514414105
+33,0.05754462896743164
+34,0.05605217513545873
+35,0.05465122849189033
+36,0.053334296851397275
+37,0.0520946960619322
+38,0.05092644461617275
+39,0.0498241743398364
+40,0.04878305436991813
+41,0.04779872617585115
+42,0.0468672477974085
+43,0.04598504581454966
+44,0.045148873825493485
+45,0.044355776432720456
+46,0.043603057900702236
+47,0.04288825479785755
+48,0.04220911204462238
+49,0.04156356188438681
+50,0.040949705370952785
+51,0.04036579603002079
+52,0.03981022540253115
+53,0.03928151022567325
+54,0.03877828103708686
+55,0.03829927202645512
+56,0.03784331197626133
+57,0.03740931616114804
+58,0.036996279090277265
+59,0.03660326799403585
+60,0.03622941696784316
+61,0.03587392170174962
+62,0.03553603472480396
+63,0.035215061114966556
+64,0.0349103546176535
+65,0.034621314134231106
+66,0.034347380541628815
+67,0.03408803380253576
+68,0.033842790352832886
+69,0.03361120071962308
+70,0.03339284736158612
+71,0.033187342705105906
+72,0.032994327359448954
+73,0.03281346849436573
+74,0.032644458366651474
+75,0.03248701298141076
+76,0.03234087087874395
+77,0.03220579203443745
+78,0.03208155686639729
+79,0.03196796533784747
+80,0.03186483615274847
+81,0.031772006034729586
+82,0.0316893290847309
+83,0.0316166762140294
+84,0.03155393464639214
+85,0.031501007487167165
+86,0.03145781335580062
+87,0.03142428607934875
+88,0.03140037444348659
+89,0.03138604200265539
diff --git a/sharc/propagation/Dataset/generate_loss_table.py b/sharc/propagation/Dataset/generate_loss_table.py
new file mode 100644
index 000000000..58a778e86
--- /dev/null
+++ b/sharc/propagation/Dataset/generate_loss_table.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Aug 31 12:59:12 2017
+
+"""
+
+import os
+import csv
+import numpy as np
+from sharc.propagation.propagation_p619 import PropagationP619
+
+# Constants
+frequency_MHz = 2680.0
+space_station_alt_m = 35786.0 * 1000 # Geostationary orbit altitude in meters
+earth_station_alt_m = 1000.0
+earth_station_lat_deg = -15.7801
+earth_station_long_diff_deg = 0.0 # Not used in the loss calculation
+season = "SUMMER"
+apparent_elevation = np.arange(0, 90) # Elevation angles from 0 to 90 degrees
+city_name = "BRASILIA"
+
+# Initialize the propagation model
+random_number_gen = np.random.RandomState(101)
+propagation = PropagationP619(
+ random_number_gen=random_number_gen,
+ space_station_alt_m=space_station_alt_m,
+ earth_station_alt_m=earth_station_alt_m,
+ earth_station_lat_deg=earth_station_lat_deg,
+ earth_station_long_diff_deg=earth_station_long_diff_deg,
+ season=season,
+)
+
+# Calculate the loss for each elevation angle
+losses = []
+for elevation in apparent_elevation:
+ loss = propagation._get_atmospheric_gasses_loss(
+ frequency_MHz=frequency_MHz,
+ apparent_elevation=elevation,
+ )
+ losses.append(loss)
+
+# Save results to CSV file
+output_dir = os.path.join(os.path.dirname(__file__), 'BRASILIA')
+os.makedirs(output_dir, exist_ok=True)
+output_file = os.path.join(
+ output_dir, f'{city_name}_{int(frequency_MHz)}_{int(earth_station_alt_m)}m.csv',
+)
+
+with open(output_file, mode='w', newline='') as file:
+ writer = csv.writer(file)
+ writer.writerow(['apparent_elevation', 'loss'])
+ for elevation, loss in zip(apparent_elevation, losses):
+ writer.writerow([elevation, loss])
+
+print(f"Results saved to {output_file}")
diff --git a/sharc/propagation/Dataset/localidades.csv b/sharc/propagation/Dataset/localidades.csv
new file mode 100644
index 000000000..151eaae6f
--- /dev/null
+++ b/sharc/propagation/Dataset/localidades.csv
@@ -0,0 +1,2 @@
+Cidade,Latitude
+BRASILIA,-15.7801
diff --git a/sharc/propagation/__init__.py b/sharc/propagation/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/propagation/__init__.py
+++ b/sharc/propagation/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/propagation/atmosphere.py b/sharc/propagation/atmosphere.py
index e94065341..790745e61 100644
--- a/sharc/propagation/atmosphere.py
+++ b/sharc/propagation/atmosphere.py
@@ -5,80 +5,195 @@
@author: Andre Noll Barreto
"""
-import math
import numpy as np
+
class ReferenceAtmosphere:
"""
Implements diverse ITU recommendations on a reference atmosphere
"""
+
def __init__(self):
# Table C.1 of ITU-R P619 - Constants for the reference dry atmosphere
- self.ref_atmosphere_altitude_km = [-float('inf'), 11, 20, 32, 47, 51, 71]
+ self.ref_atmosphere_altitude_km = [
+ -float('inf'), 11, 20, 32, 47, 51, 71,
+ ]
self.ref_atmosphere_temp_grad = [-6.5, 0, 1., 2.8, 0, -2.8, -2]
- self.ref_atmosphere_temperature = [288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65]
- self.ref_atmosphere_pressure = [1013.25, 226.323, 54.750, 8.68, 1.109, 0.669, 0.04]
+ self.ref_atmosphere_temperature = [
+ 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65,
+ ]
+ self.ref_atmosphere_pressure = [
+ 1013.25, 226.323, 54.750, 8.68, 1.109, 0.669, 0.04,
+ ]
# Tables 1 and 2, Spectroscopic data for oxygen and water attenuation, in ITU-T P.676-11
self.p676_oxygen_f0 = np.array(
- [50.474214, 50.987745, 51.503360, 52.021429, 52.542418, 53.066934, 53.595775,
- 54.130025, 54.671180, 55.221384, 55.783815, 56.264774, 56.363399, 56.968211,
- 57.612486, 58.323877, 58.446588, 59.164204, 59.590983, 60.306056, 60.434778,
- 61.150562, 61.800158, 62.411220, 62.486253, 62.997984, 63.568526, 64.127775,
- 64.678910, 65.224078, 65.764779, 66.302096, 66.836834, 67.369601, 67.900868,
- 68.431006, 68.960312, 118.750334, 368.498246, 424.763020, 487.249273, 715.392902,
- 773.839490, 834.145546])
+ [
+ 50.474214, 50.987745, 51.503360, 52.021429, 52.542418, 53.066934, 53.595775,
+ 54.130025, 54.671180, 55.221384, 55.783815, 56.264774, 56.363399, 56.968211,
+ 57.612486, 58.323877, 58.446588, 59.164204, 59.590983, 60.306056, 60.434778,
+ 61.150562, 61.800158, 62.411220, 62.486253, 62.997984, 63.568526, 64.127775,
+ 64.678910, 65.224078, 65.764779, 66.302096, 66.836834, 67.369601, 67.900868,
+ 68.431006, 68.960312, 118.750334, 368.498246, 424.763020, 487.249273, 715.392902,
+ 773.839490, 834.145546,
+ ],
+ )
self.p676_a = np.array(
- [[0.975, 9.651, 6.690, 0.0, 2.566, 6.850], [2.529, 8.653, 7.170, 0.0, 2.246, 6.800],
- [6.193, 7.709, 7.640, 0.0, 1.947, 6.729], [14.320, 6.819, 8.110, 0.0, 1.667, 6.640],
- [31.240, 5.983, 8.580, 0.0, 1.388, 6.526], [64.290, 5.201, 9.060, 0.0, 1.349, 6.206],
- [124.600, 4.474, 9.550, 0.0, 2.227, 5.085], [227.300, 3.800, 9.960, 0.0, 3.170, 3.750],
- [389.700, 3.182, 10.370, 0.0, 3.558, 2.654], [627.100, 2.618, 10.890, 0.0, 2.560, 2.952],
- [945.300, 2.109, 11.340, 0.0, -1.172, 6.135], [543.400, 0.014, 17.030, 0.0, 3.525, -0.978],
- [1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], [1746.600, 1.255, 12.230, 0.0, -3.545, 6.451],
- [2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], [2363.700, 0.621, 12.950, 0.0, -1.932, 0.436],
- [1442.100, 0.083, 14.910, 0.0, 6.768, -1.273], [2379.900, 0.387, 13.530, 0.0, -6.561, 2.309],
- [2090.700, 0.207, 14.080, 0.0, 6.957, -0.776], [2103.400, 0.207, 14.150, 0.0, -6.395, 0.699],
- [2438.000, 0.386, 13.390, 0.0, 6.342, -2.825], [2479.500, 0.621, 12.920, 0.0, 1.014, -0.584],
- [2275.900, 0.910, 12.630, 0.0, 5.014, -6.619], [1915.400, 1.255, 12.170, 0.0, 3.029, -6.759],
- [1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], [1490.200, 1.654, 11.740, 0.0, 1.856, -6.675],
- [1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], [728.700, 2.617, 10.880, 0.0, -3.036, -2.895],
- [461.300, 3.181, 10.380, 0.0, -3.968, -2.590], [274.000, 3.800, 9.960, 0.0, -3.528, -3.680],
- [153.000, 4.473, 9.550, 0.0, -2.548, -5.002], [80.400, 5.200, 9.060, 0.0, -1.660, -6.091],
- [39.800, 5.982, 8.580, 0.0, -1.680, -6.393], [18.560, 6.818, 8.110, 0.0, -1.956, -6.475],
- [8.172, 7.708, 7.640, 0.0, -2.216, -6.545], [3.397, 8.652, 7.170, 0.0, -2.492, -6.600],
- [1.334, 9.650, 6.690, 0.0, -2.773, -6.650], [940.300, 0.010, 16.640, 0.0, -0.439, 0.079],
- [67.400, 0.048, 16.400, 0.0, 0.000, 0.000], [637.700, 0.044, 16.400, 0.0, 0.000, 0.000],
- [237.400, 0.049, 16.000, 0.0, 0.000, 0.000], [98.100, 0.145, 16.000, 0.0, 0.000, 0.000],
- [572.300, 0.141, 16.200, 0.0, 0.000, 0.000], [183.100, 0.145, 14.700, 0.0, 0.000, 0.000]])
-
- self.p676_vapour_f0 = np.array([ 22.235080, 67.803960, 119.995940, 183.310087, 321.225630, 325.152888,
- 336.227764, 380.197353, 390.134508, 437.346667, 439.150807, 443.018343,
- 448.001085, 470.888999, 474.689092, 488.490108, 503.568532, 504.482692,
- 547.676440, 552.020960, 556.935985, 620.700807, 645.766085, 658.005280,
- 752.033113, 841.051732, 859.965698, 899.303175, 902.611085, 906.205957,
- 916.171582, 923.112692, 970.315022, 987.926764, 1780.000000])
+ [
+ [0.975, 9.651, 6.690, 0.0, 2.566, 6.850], [2.529, 8.653, 7.170, 0.0, 2.246, 6.800],
+ [6.193, 7.709, 7.640, 0.0, 1.947, 6.729], [
+ 14.320, 6.819, 8.110, 0.0, 1.667, 6.640,
+ ],
+ [31.240, 5.983, 8.580, 0.0, 1.388, 6.526], [
+ 64.290, 5.201, 9.060, 0.0, 1.349, 6.206,
+ ],
+ [124.600, 4.474, 9.550, 0.0, 2.227, 5.085], [
+ 227.300, 3.800, 9.960, 0.0, 3.170, 3.750,
+ ],
+ [389.700, 3.182, 10.370, 0.0, 3.558, 2.654], [
+ 627.100, 2.618, 10.890, 0.0, 2.560, 2.952,
+ ],
+ [945.300, 2.109, 11.340, 0.0, -1.172, 6.135], [
+ 543.400,
+ 0.014, 17.030, 0.0, 3.525, -0.978,
+ ],
+ [1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], [
+ 1746.600,
+ 1.255, 12.230, 0.0, -3.545, 6.451,
+ ],
+ [2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], [
+ 2363.700,
+ 0.621, 12.950, 0.0, -1.932, 0.436,
+ ],
+ [
+ 1442.100, 0.083, 14.910, 0.0, 6.768,
+ - 1.273,
+ ], [2379.900, 0.387, 13.530, 0.0, -6.561, 2.309],
+ [
+ 2090.700, 0.207, 14.080, 0.0, 6.957,
+ - 0.776,
+ ], [2103.400, 0.207, 14.150, 0.0, -6.395, 0.699],
+ [
+ 2438.000, 0.386, 13.390, 0.0, 6.342,
+ - 2.825,
+ ], [2479.500, 0.621, 12.920, 0.0, 1.014, -0.584],
+ [
+ 2275.900, 0.910, 12.630, 0.0, 5.014,
+ - 6.619,
+ ], [1915.400, 1.255, 12.170, 0.0, 3.029, -6.759],
+ [1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], [
+ 1490.200,
+ 1.654, 11.740, 0.0, 1.856, -6.675,
+ ],
+ [1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], [
+ 728.700,
+ 2.617, 10.880, 0.0, -3.036, -2.895,
+ ],
+ [
+ 461.300, 3.181, 10.380, 0.0, -3.968,
+ - 2.590,
+ ], [274.000, 3.800, 9.960, 0.0, -3.528, -3.680],
+ [153.000, 4.473, 9.550, 0.0, -2.548, -5.002], [
+ 80.400,
+ 5.200, 9.060, 0.0, -1.660, -6.091,
+ ],
+ [39.800, 5.982, 8.580, 0.0, -1.680, -6.393], [
+ 18.560,
+ 6.818, 8.110, 0.0, -1.956, -6.475,
+ ],
+ [8.172, 7.708, 7.640, 0.0, -2.216, -6.545], [
+ 3.397,
+ 8.652, 7.170, 0.0, -2.492, -6.600,
+ ],
+ [1.334, 9.650, 6.690, 0.0, -2.773, -6.650], [
+ 940.300,
+ 0.010, 16.640, 0.0, -0.439, 0.079,
+ ],
+ [67.400, 0.048, 16.400, 0.0, 0.000, 0.000], [
+ 637.700, 0.044, 16.400, 0.0, 0.000, 0.000,
+ ],
+ [237.400, 0.049, 16.000, 0.0, 0.000, 0.000], [
+ 98.100, 0.145, 16.000, 0.0, 0.000, 0.000,
+ ],
+ [572.300, 0.141, 16.200, 0.0, 0.000, 0.000], [183.100, 0.145, 14.700, 0.0, 0.000, 0.000],
+ ],
+ )
+
+ self.p676_vapour_f0 = np.array([
+ 22.235080, 67.803960, 119.995940, 183.310087, 321.225630, 325.152888,
+ 336.227764, 380.197353, 390.134508, 437.346667, 439.150807, 443.018343,
+ 448.001085, 470.888999, 474.689092, 488.490108, 503.568532, 504.482692,
+ 547.676440, 552.020960, 556.935985, 620.700807, 645.766085, 658.005280,
+ 752.033113, 841.051732, 859.965698, 899.303175, 902.611085, 906.205957,
+ 916.171582, 923.112692, 970.315022, 987.926764, 1780.000000,
+ ])
self.p676_vapour_indices = np.array([0, 3, 4, 5, 7, 12, 20, 24, 34])
self.p676_b = np.array(
- [[.1079, 2.144, 26.38, .76, 5.087, 1.00], [.0011, 8.732, 28.58, .69, 4.930, .82],
- [.0007, 8.353, 29.48, .70, 4.780, .79], [2.273, .668, 29.06, .77, 5.022, .85],
- [.0470, 6.179, 24.04, .67, 4.398, .54], [1.514, 1.541, 28.23, .64, 4.893, .74],
- [.0010, 9.825, 26.93, .69, 4.740, .61], [11.67, 1.048, 28.11, .54, 5.063, .89],
- [.0045, 7.347, 21.52, .63, 4.810, .55], [.0632, 5.048, 18.45, .60, 4.230, .48],
- [.9098, 3.595, 20.07, .63, 4.483, .52], [.1920, 5.048, 15.55, .60, 5.083, .50],
- [10.41, 1.405, 25.64, .66, 5.028, .67], [.3254, 3.597, 21.34, .66, 4.506, .65],
- [1.260, 2.379, 23.20, .65, 4.804, .64], [.2529, 2.852, 25.86, .69, 5.201, .72],
- [.0372, 6.731, 16.12, .61, 3.980, .43], [.0124, 6.731, 16.12, .61, 4.010, .45],
- [.9785, .158, 26.00, .70, 4.500, 1.00], [.1840, .158, 26.00, .70, 4.500, 1.00],
- [497.0, .159, 30.86, .69, 4.552, 1.00], [5.015, 2.391, 24.38, .71, 4.856, .68],
- [.0067, 8.633, 18.00, .60, 4.000, .50], [.2732, 7.816, 32.10, .69, 4.140, 1.00],
- [243.4, .396, 30.86, .68, 4.352, .84], [.0134, 8.177, 15.90, .33, 5.760, .45],
- [.1325, 8.055, 30.60, .68, 4.090, .84], [.0547, 7.914, 29.85, .68, 4.530, .90],
- [.0386, 8.429, 28.65, .70, 5.100, .95], [.1836, 5.110, 24.08, .70, 4.700, .53],
- [8.400, 1.441, 26.73, .70, 5.150, .78], [.0079, 10.293, 29.00, .70, 5.000, .80],
- [9.009, 1.919, 25.50, .64, 4.940, .67], [134.6, .257, 29.85, .68, 4.550, .90],
- [17506., .952, 196.3, 2.00, 24.15, 5.00]])
+ [
+ [.1079, 2.144, 26.38, .76, 5.087, 1.00], [.0011, 8.732, 28.58, .69, 4.930, .82],
+ [.0007, 8.353, 29.48, .70, 4.780, .79], [
+ 2.273, .668, 29.06, .77, 5.022, .85,
+ ],
+ [.0470, 6.179, 24.04, .67, 4.398, .54], [
+ 1.514, 1.541, 28.23, .64, 4.893, .74,
+ ],
+ [.0010, 9.825, 26.93, .69, 4.740, .61], [
+ 11.67, 1.048, 28.11, .54, 5.063, .89,
+ ],
+ [.0045, 7.347, 21.52, .63, 4.810, .55], [
+ .0632,
+ 5.048, 18.45, .60, 4.230, .48,
+ ],
+ [.9098, 3.595, 20.07, .63, 4.483, .52], [
+ .1920,
+ 5.048, 15.55, .60, 5.083, .50,
+ ],
+ [10.41, 1.405, 25.64, .66, 5.028, .67], [
+ .3254,
+ 3.597, 21.34, .66, 4.506, .65,
+ ],
+ [1.260, 2.379, 23.20, .65, 4.804, .64], [
+ .2529,
+ 2.852, 25.86, .69, 5.201, .72,
+ ],
+ [.0372, 6.731, 16.12, .61, 3.980, .43], [
+ .0124,
+ 6.731, 16.12, .61, 4.010, .45,
+ ],
+ [
+ .9785, .158, 26.00, .70, 4.500,
+ 1.00,
+ ], [.1840, .158, 26.00, .70, 4.500, 1.00],
+ [497.0, .159, 30.86, .69, 4.552, 1.00], [
+ 5.015, 2.391, 24.38, .71, 4.856, .68,
+ ],
+ [.0067, 8.633, 18.00, .60, 4.000, .50], [
+ .2732,
+ 7.816, 32.10, .69, 4.140, 1.00,
+ ],
+ [243.4, .396, 30.86, .68, 4.352, .84], [
+ .0134,
+ 8.177, 15.90, .33, 5.760, .45,
+ ],
+ [.1325, 8.055, 30.60, .68, 4.090, .84], [
+ .0547,
+ 7.914, 29.85, .68, 4.530, .90,
+ ],
+ [.0386, 8.429, 28.65, .70, 5.100, .95], [
+ .1836,
+ 5.110, 24.08, .70, 4.700, .53,
+ ],
+ [8.400, 1.441, 26.73, .70, 5.150, .78], [
+ .0079,
+ 10.293, 29.00, .70, 5.000, .80,
+ ],
+ [9.009, 1.919, 25.50, .64, 4.940, .67], [
+ 134.6, .257, 29.85, .68, 4.550, .90,
+ ],
+ [17506., .952, 196.3, 2.00, 24.15, 5.00],
+ ],
+ )
def _get_specific_attenuation(self, pressure, water_vapour_pressure, temperature, frequency_MHz):
"""
@@ -101,35 +216,43 @@ def _get_specific_attenuation(self, pressure, water_vapour_pressure, temperature
# line strength
s_oxygen = self.p676_a[:, 0] * 1e-7 * pressure * theta ** 3 * \
- np.exp(self.p676_a[:, 1] * (1 - theta))
+ np.exp(self.p676_a[:, 1] * (1 - theta))
s_vapour = self.p676_b[:, 0] * 1e-1 * water_vapour_pressure * theta ** 3.5 \
- * np.exp(self.p676_b[:, 1] * (1 - theta))
+ * np.exp(self.p676_b[:, 1] * (1 - theta))
# line width
- df_oxygen = self.p676_a[:, 2] * 1e-4 * (pressure * theta ** (.8 - self.p676_a[:, 3])
- + 1.1 * water_vapour_pressure * theta)
- df_vapour = self.p676_b[:, 2] * 1e-4 * (pressure * theta ** (self.p676_b[:, 3])
- + self.p676_b[:, 4] * water_vapour_pressure * theta ** self.p676_b[:, 5])
+ df_oxygen = self.p676_a[:, 2] * 1e-4 * (
+ pressure * theta ** (.8 - self.p676_a[:, 3]) +
+ 1.1 * water_vapour_pressure * theta
+ )
+ df_vapour = self.p676_b[:, 2] * 1e-4 * (
+ pressure * theta ** (self.p676_b[:, 3]) +
+ self.p676_b[:, 4] * water_vapour_pressure * theta ** self.p676_b[:, 5]
+ )
# correction factor
delta_oxygen = (self.p676_a[:, 4] + self.p676_a[:, 5] * theta) * 1e-4 * \
(pressure + water_vapour_pressure) * theta ** .8
# line shape factor
sf_oxygen = f_GHz / self.p676_oxygen_f0 * \
- ((df_oxygen - delta_oxygen * (self.p676_oxygen_f0 - f_GHz)) /
- ((self.p676_oxygen_f0 - f_GHz) ** 2 + df_oxygen ** 2) +
- (df_oxygen - delta_oxygen * (self.p676_oxygen_f0 + f_GHz)) /
- ((self.p676_oxygen_f0 + f_GHz) ** 2 + df_oxygen ** 2))
+ ((df_oxygen - delta_oxygen * (self.p676_oxygen_f0 - f_GHz)) /
+ ((self.p676_oxygen_f0 - f_GHz) ** 2 + df_oxygen ** 2) +
+ (df_oxygen - delta_oxygen * (self.p676_oxygen_f0 + f_GHz)) /
+ ((self.p676_oxygen_f0 + f_GHz) ** 2 + df_oxygen ** 2))
sf_vapour = f_GHz / self.p676_vapour_f0 * \
- (df_vapour / ((self.p676_vapour_f0 - f_GHz) ** 2 + df_vapour ** 2) +
- df_vapour / ((self.p676_vapour_f0 + f_GHz) ** 2 + df_vapour ** 2))
+ (
+ df_vapour / ((self.p676_vapour_f0 - f_GHz) ** 2 + df_vapour ** 2) +
+ df_vapour / ((self.p676_vapour_f0 + f_GHz) ** 2 + df_vapour ** 2)
+ )
# Debye spectrum width
dw = 5.6e-4 * (pressure + water_vapour_pressure) * theta ** .8
# dry continuum due to pressure-induced nitrogen absorption and the Debye spectrum
nd = f_GHz * pressure * theta ** 2 * \
- (6.14e-5 / (dw * (1 + (f_GHz / dw) ** 2))
- + 1.4e-12 * pressure * theta ** 1.5 / (1 + 1.9e-5 * f_GHz ** 1.5))
+ (
+ 6.14e-5 / (dw * (1 + (f_GHz / dw) ** 2)) +
+ 1.4e-12 * pressure * theta ** 1.5 / (1 + 1.9e-5 * f_GHz ** 1.5)
+ )
att_oxygen = 0.182 * f_GHz * (np.sum(s_oxygen * sf_oxygen) + nd)
att_vapour = 0.182 * f_GHz * np.sum(s_vapour * sf_vapour)
@@ -156,7 +279,7 @@ def get_atmospheric_params(self, altitude_km, water_vapour_density_sea_level, f_
specific_attenuation (float): specific gaseous attenuation (dB/km)
"""
- index = np.where(altitude_km >= np.array(self.ref_atmosphere_altitude_km))[0][-1]
+ index = np.where(altitude_km >= np.array(self.ref_atmosphere_altitude_km,),)[0][-1]
Ti = self.ref_atmosphere_temperature[index]
Li = self.ref_atmosphere_temp_grad[index]
@@ -167,22 +290,30 @@ def get_atmospheric_params(self, altitude_km, water_vapour_density_sea_level, f_
temperature = Ti + Li * (altitude_km - Hi)
if Li:
- pressure = Pi * (Ti / (Ti + Li * (altitude_km - Hi))) ** (34.163 / Li)
+ pressure = Pi * \
+ (Ti / (Ti + Li * (altitude_km - Hi))) ** (34.163 / Li)
else:
pressure = Pi * np.exp(-34.163 * (altitude_km - Hi) / Ti)
- water_vapour_density = water_vapour_density_sea_level * np.exp(-altitude_km / 2)
+ water_vapour_density = water_vapour_density_sea_level * \
+ np.exp(-altitude_km / 2)
water_vapour_pressure = water_vapour_density * temperature / 216.7
- refractive_index = 1 + 1e-6 * (77.6 / temperature *
- (pressure + water_vapour_pressure +
- 4810 * water_vapour_pressure / pressure))
- specific_attenuation = self._get_specific_attenuation(pressure, water_vapour_pressure,
- temperature, f_MHz)
+ refractive_index = 1 + 1e-6 * (
+ 77.6 / temperature *
+ (
+ pressure + water_vapour_pressure +
+ 4810 * water_vapour_pressure / pressure
+ )
+ )
+ specific_attenuation = self._get_specific_attenuation(
+ pressure, water_vapour_pressure,
+ temperature, f_MHz,
+ )
return [temperature, pressure, water_vapour_pressure, refractive_index, specific_attenuation]
@staticmethod
- def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
+ def get_reference_atmosphere_p835(latitude, altitude=1000, season="summer"):
"""
Returns reference atmosphere parameters based on ITU-R P835-5
@@ -204,7 +335,7 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
if latitude <= 22:
# low latitude
if h_km < 17.:
- temperature = 300.4222 - 6.3533 * h_km + .005886 * h_km **2
+ temperature = 300.4222 - 6.3533 * h_km + .005886 * h_km ** 2
elif h_km < 47:
temperature = 194 + (h_km - 17) * 2.533
elif h_km < 52:
@@ -223,15 +354,20 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
p10 = 1012.0306 - 109.0338 * 10 + 3.6316 * 10 ** 2
pressure_hPa = p10 * np.exp(-.147 * (h_km - 10.))
elif h_km <= 100:
- p72 = ( 1012.0306 - 109.0338 * 10 + 3.6316 * 10 ** 2 ) * np.exp(-.147 * (72 - 10))
- pressure_hPa = p72 * np.exp(-.165 * (h - 72))
+ p72 = (
+ 1012.0306 - 109.0338 * 10 + 3.6316 *
+ 10 ** 2
+ ) * np.exp(-.147 * (72 - 10))
+ pressure_hPa = p72 * np.exp(-.165 * (h_km - 72))
else:
error_message = "altitude > 100km not supported"
raise ValueError(error_message)
if h_km <= 15.:
- water_vapour_density = 19.6542 * np.exp( -.2313 * h_km - .1122 * h_km ** 2
- + .01351 * h_km **3 - .0005923 * h_km ** 4)
+ water_vapour_density = 19.6542 * np.exp(
+ -.2313 * h_km - .1122 * h_km ** 2 +
+ .01351 * h_km ** 3 - .0005923 * h_km ** 4,
+ )
else:
water_vapour_density = 0
@@ -247,7 +383,7 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
elif h_km < 53.:
temperature = 275.
elif h_km < 80.:
- temperature = 275 + (1 - np.exp((h - 53.)*.06)) * 20.
+ temperature = 275 + (1 - np.exp((h_km - 53.) * .06)) * 20.
elif h_km <= 100:
temperature = 175.
else:
@@ -256,19 +392,24 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
if h_km <= 10:
pressure_hPa = 1012.8186 - 111.5569 * h_km + 3.8646 * h_km ** 2
- elif h_km <=72.:
+ elif h_km <= 72.:
p10 = 1012.8186 - 111.5569 * 10 + 3.8646 * 10 ** 2
- pressure_hPa = p10 * np.exp(-.147 * (h_km-10))
+ pressure_hPa = p10 * np.exp(-.147 * (h_km - 10))
elif h_km <= 100.:
- p72 = (1012.8186 - 111.5569 * 10 + 3.8646 * 10 ** 2) * np.exp(-.147 * (72-10))
+ p72 = (
+ 1012.8186 - 111.5569 * 10 + 3.8646 *
+ 10 ** 2
+ ) * np.exp(-.147 * (72 - 10))
pressure_hPa = p72 * np.exp(-.165 * (h_km - 72))
else:
error_message = "altitude > 100km not supported"
raise ValueError(error_message)
if h_km <= 15.:
- water_vapour_density = 14.3542 * np.exp(-.4174 * h_km - .02290 * h_km ** 2
- + .001007 * h_km ** 3)
+ water_vapour_density = 14.3542 * np.exp(
+ -.4174 * h_km - .02290 * h_km ** 2 +
+ .001007 * h_km ** 3,
+ )
else:
water_vapour_density = 0
@@ -291,19 +432,24 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
if h_km <= 10:
pressure_hPa = 1018.8627 - 124.2954 * h_km + 4.8307 * h_km ** 2
- elif h_km <=72.:
+ elif h_km <= 72.:
p10 = 1018.8627 - 124.2954 * 10 + 4.8307 * 10 ** 2
- pressure_hPa = p10 * np.exp(-.147 * (h_km-10))
+ pressure_hPa = p10 * np.exp(-.147 * (h_km - 10))
elif h_km <= 100.:
- p72 = (1018.8627 - 124.2954 * 10 + 4.8307 * 10 ** 2) * np.exp(-.147 * (72-10))
+ p72 = (
+ 1018.8627 - 124.2954 * 10 + 4.8307 *
+ 10 ** 2
+ ) * np.exp(-.147 * (72 - 10))
pressure_hPa = p72 * np.exp(-.155 * (h_km - 72))
else:
error_message = "altitude > 100km not supported"
raise ValueError(error_message)
if h_km <= 15.:
- water_vapour_density = 3.4742 * np.exp(-.2697 * h_km - .03604 * h_km ** 2
- + .0004489 * h_km ** 3)
+ water_vapour_density = 3.4742 * np.exp(
+ -.2697 * h_km - .03604 * h_km ** 2 +
+ .0004489 * h_km ** 3,
+ )
else:
water_vapour_density = 0
else:
@@ -334,22 +480,29 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
p10 = 1008.0278 - 113.2494 * 10 + 3.9408 * (10 ** 2)
pressure_hPa = p10 * np.exp(-.140 * (h_km - 10))
elif h_km <= 100.:
- p72 = (1008.0278 - 113.2494 * 10 + 3.9408 * (10 ** 2)) * np.exp(-.140 * (72 - 10))
+ p72 = (
+ 1008.0278 - 113.2494 * 10 + 3.9408 *
+ (10 ** 2)
+ ) * np.exp(-.140 * (72 - 10))
pressure_hPa = p72 * np.exp(-.165 * (h_km - 72))
else:
error_message = "altitude > 100km not supported"
raise ValueError(error_message)
if h_km <= 15.:
- water_vapour_density = 8.988 * np.exp(-.3614 * h_km - .005402 * h_km ** 2
- + .001955 * h_km ** 3)
+ water_vapour_density = 8.988 * np.exp(
+ -.3614 * h_km - .005402 * h_km ** 2 +
+ .001955 * h_km ** 3,
+ )
else:
water_vapour_density = 0
elif season == "winter":
if h_km < 8.5:
- temperature = (257.4345 + 2.3474 * h_km - .15479 * h_km ** 2
- + .08473 * h_km ** 3)
+ temperature = (
+ 257.4345 + 2.3474 * h_km - .15479 * h_km ** 2 +
+ .08473 * h_km ** 3
+ )
elif h_km < 30.:
temperature = 217.5
elif h_km < 50:
@@ -368,15 +521,20 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
p10 = 1010.8828 - 122.2411 * 10 + 4.554 * 10 ** 2
pressure_hPa = p10 * np.exp(-.147 * (h_km - 10))
elif h_km <= 100.:
- p72 = (1010.8828 - 122.2411 * 10 + 4.554 * 10 ** 2) * np.exp(-.147 * (72 - 10))
+ p72 = (
+ 1010.8828 - 122.2411 * 10 + 4.554 *
+ 10 ** 2
+ ) * np.exp(-.147 * (72 - 10))
pressure_hPa = p72 * np.exp(-.150 * (h_km - 72))
else:
error_message = "altitude > 100km not supported"
raise ValueError(error_message)
if h_km <= 15.:
- water_vapour_density = 1.2319 * np.exp(.07481 * h_km - .0981 * h_km ** 2
- + .00281 * h_km ** 3)
+ water_vapour_density = 1.2319 * np.exp(
+ .07481 * h_km - .0981 * h_km ** 2 +
+ .00281 * h_km ** 3,
+ )
else:
water_vapour_density = 0
else:
@@ -395,12 +553,6 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
propagation_path = os.getcwd()
sharc_path = os.path.dirname(propagation_path)
- param_file = os.path.join(sharc_path, "parameters", "parameters.ini")
-
- params.set_file_name(param_file)
- params.read_params()
-
- sat_params = params.fss_ss
atmosphere = ReferenceAtmosphere()
@@ -419,10 +571,12 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"):
print("Plotting specific attenuation:")
for index in range(len(f_GHz_vec)):
- specific_att[index] = atmosphere._get_specific_attenuation(pressure_hPa,
- vapour_pressure_hPa,
- temperature,
- float(f_GHz_vec[index]) * 1000)
+ specific_att[index] = atmosphere._get_specific_attenuation(
+ pressure_hPa,
+ vapour_pressure_hPa,
+ temperature,
+ float(f_GHz_vec[index]) * 1000,
+ )
plt.figure()
plt.semilogy(f_GHz_vec, specific_att)
plt.xlabel('frequency(GHz)')
diff --git a/sharc/propagation/clear_air_452_aux.py b/sharc/propagation/clear_air_452_aux.py
index 3ab1e2ff8..dac65e56b 100644
--- a/sharc/propagation/clear_air_452_aux.py
+++ b/sharc/propagation/clear_air_452_aux.py
@@ -1,5 +1,6 @@
import numpy as np
+
def inv_cum_norm(x):
if x < 0.000001:
x = 0.000001
@@ -17,133 +18,139 @@ def inv_cum_norm(x):
D3 = 0.001308
ksi = ((C2 * tx + C1) * tx + C0) / (((D3 * tx + D2) * tx + D1) * tx + 1)
- I = ksi - tx
- return I
+ return ksi - tx
+
def p676_ga(f, p, rho, T, d11):
# spectroscopic data for oxygen
# f0 a1 a2 a3 a4 a5 a6
- oxygen = [[50.474214, 0.975, 9.651, 6.690, 0.0, 2.566, 6.850],
- [50.987745, 2.529, 8.653, 7.170, 0.0, 2.246, 6.800],
- [51.503360, 6.193, 7.709, 7.640, 0.0, 1.947, 6.729],
- [52.021429, 14.320, 6.819, 8.110, 0.0, 1.667, 6.640],
- [52.542418, 31.240, 5.983, 8.580, 0.0, 1.388, 6.526],
- [53.066934, 64.290, 5.201, 9.060, 0.0, 1.349, 6.206],
- [53.595775, 124.600, 4.474, 9.550, 0.0, 2.227, 5.085],
- [54.130025, 227.300, 3.800, 9.960, 0.0, 3.170, 3.750],
- [54.671180, 389.700, 3.182, 10.370, 0.0, 3.558, 2.654],
- [55.221384, 627.100, 2.618, 10.890, 0.0, 2.560, 2.952],
- [55.783815, 945.300, 2.109, 11.340, 0.0, -1.172, 6.135],
- [56.264774, 543.400, 0.014, 17.030, 0.0, 3.525, -0.978],
- [56.363399, 1331.800, 1.654, 11.890, 0.0, -2.378, 6.547],
- [56.968211, 1746.600, 1.255, 12.230, 0.0, -3.545, 6.451],
- [57.612486, 2120.100, 0.910, 12.620, 0.0, -5.416, 6.056],
- [58.323877, 2363.700, 0.621, 12.950, 0.0, -1.932, 0.436],
- [58.446588, 1442.100, 0.083, 14.910, 0.0, 6.768, -1.273],
- [59.164204, 2379.900, 0.387, 13.530, 0.0, -6.561, 2.309],
- [59.590983, 2090.700, 0.207, 14.080, 0.0, 6.957, -0.776],
- [60.306056, 2103.400, 0.207, 14.150, 0.0, -6.395, 0.699],
- [60.434778, 2438.000, 0.386, 13.390, 0.0, 6.342, -2.825],
- [61.150562, 2479.500, 0.621, 12.920, 0.0, 1.014, -0.584],
- [61.800158, 2275.900, 0.910, 12.630, 0.0, 5.014, -6.619],
- [62.411220, 1915.400, 1.255, 12.170, 0.0, 3.029, -6.759],
- [62.486253, 1503.000, 0.083, 15.130, 0.0, -4.499, 0.844],
- [62.997984, 1490.200, 1.654, 11.740, 0.0, 1.856, -6.675],
- [63.568526, 1078.000, 2.108, 11.340, 0.0, 0.658, -6.139],
- [64.127775, 728.700, 2.617, 10.880, 0.0, -3.036, -2.895],
- [64.678910, 461.300, 3.181, 10.380, 0.0, -3.968, -2.590],
- [65.224078, 274.000, 3.800, 9.960, 0.0, -3.528, -3.680],
- [65.764779, 153.000, 4.473, 9.550, 0.0, -2.548, -5.002],
- [66.302096, 80.400, 5.200, 9.060, 0.0, -1.660, -6.091],
- [66.836834, 39.800, 5.982, 8.580, 0.0, -1.680, -6.393],
- [67.369601, 18.560, 6.818, 8.110, 0.0, -1.956, -6.475],
- [67.900868, 8.172, 7.708, 7.640, 0.0, -2.216, -6.545],
- [68.431006, 3.397, 8.652, 7.170, 0.0, -2.492, -6.600],
- [68.960312, 1.334, 9.650, 6.690, 0.0, -2.773, -6.650],
- [118.750334, 940.300, 0.010, 16.640, 0.0, -0.439, 0.079],
- [368.498246, 67.400, 0.048, 16.400, 0.0, 0.000, 0.000],
- [424.763020, 637.700, 0.044, 16.400, 0.0, 0.000, 0.000],
- [487.249273, 237.400, 0.049, 16.000, 0.0, 0.000, 0.000],
- [715.392902, 98.100, 0.145, 16.000, 0.0, 0.000, 0.000],
- [773.839490, 572.300, 0.141, 16.200, 0.0, 0.000, 0.000],
- [834.145546, 183.100, 0.145, 14.700, 0.0, 0.000, 0.000]]
+ oxygen = [
+ [50.474214, 0.975, 9.651, 6.690, 0.0, 2.566, 6.850],
+ [50.987745, 2.529, 8.653, 7.170, 0.0, 2.246, 6.800],
+ [51.503360, 6.193, 7.709, 7.640, 0.0, 1.947, 6.729],
+ [52.021429, 14.320, 6.819, 8.110, 0.0, 1.667, 6.640],
+ [52.542418, 31.240, 5.983, 8.580, 0.0, 1.388, 6.526],
+ [53.066934, 64.290, 5.201, 9.060, 0.0, 1.349, 6.206],
+ [53.595775, 124.600, 4.474, 9.550, 0.0, 2.227, 5.085],
+ [54.130025, 227.300, 3.800, 9.960, 0.0, 3.170, 3.750],
+ [54.671180, 389.700, 3.182, 10.370, 0.0, 3.558, 2.654],
+ [55.221384, 627.100, 2.618, 10.890, 0.0, 2.560, 2.952],
+ [55.783815, 945.300, 2.109, 11.340, 0.0, -1.172, 6.135],
+ [56.264774, 543.400, 0.014, 17.030, 0.0, 3.525, -0.978],
+ [56.363399, 1331.800, 1.654, 11.890, 0.0, -2.378, 6.547],
+ [56.968211, 1746.600, 1.255, 12.230, 0.0, -3.545, 6.451],
+ [57.612486, 2120.100, 0.910, 12.620, 0.0, -5.416, 6.056],
+ [58.323877, 2363.700, 0.621, 12.950, 0.0, -1.932, 0.436],
+ [58.446588, 1442.100, 0.083, 14.910, 0.0, 6.768, -1.273],
+ [59.164204, 2379.900, 0.387, 13.530, 0.0, -6.561, 2.309],
+ [59.590983, 2090.700, 0.207, 14.080, 0.0, 6.957, -0.776],
+ [60.306056, 2103.400, 0.207, 14.150, 0.0, -6.395, 0.699],
+ [60.434778, 2438.000, 0.386, 13.390, 0.0, 6.342, -2.825],
+ [61.150562, 2479.500, 0.621, 12.920, 0.0, 1.014, -0.584],
+ [61.800158, 2275.900, 0.910, 12.630, 0.0, 5.014, -6.619],
+ [62.411220, 1915.400, 1.255, 12.170, 0.0, 3.029, -6.759],
+ [62.486253, 1503.000, 0.083, 15.130, 0.0, -4.499, 0.844],
+ [62.997984, 1490.200, 1.654, 11.740, 0.0, 1.856, -6.675],
+ [63.568526, 1078.000, 2.108, 11.340, 0.0, 0.658, -6.139],
+ [64.127775, 728.700, 2.617, 10.880, 0.0, -3.036, -2.895],
+ [64.678910, 461.300, 3.181, 10.380, 0.0, -3.968, -2.590],
+ [65.224078, 274.000, 3.800, 9.960, 0.0, -3.528, -3.680],
+ [65.764779, 153.000, 4.473, 9.550, 0.0, -2.548, -5.002],
+ [66.302096, 80.400, 5.200, 9.060, 0.0, -1.660, -6.091],
+ [66.836834, 39.800, 5.982, 8.580, 0.0, -1.680, -6.393],
+ [67.369601, 18.560, 6.818, 8.110, 0.0, -1.956, -6.475],
+ [67.900868, 8.172, 7.708, 7.640, 0.0, -2.216, -6.545],
+ [68.431006, 3.397, 8.652, 7.170, 0.0, -2.492, -6.600],
+ [68.960312, 1.334, 9.650, 6.690, 0.0, -2.773, -6.650],
+ [118.750334, 940.300, 0.010, 16.640, 0.0, -0.439, 0.079],
+ [368.498246, 67.400, 0.048, 16.400, 0.0, 0.000, 0.000],
+ [424.763020, 637.700, 0.044, 16.400, 0.0, 0.000, 0.000],
+ [487.249273, 237.400, 0.049, 16.000, 0.0, 0.000, 0.000],
+ [715.392902, 98.100, 0.145, 16.000, 0.0, 0.000, 0.000],
+ [773.839490, 572.300, 0.141, 16.200, 0.0, 0.000, 0.000],
+ [834.145546, 183.100, 0.145, 14.700, 0.0, 0.000, 0.000],
+ ]
# spectroscopic data for water - vapor
# f0 b1 b2 b3 b4 b5 b6
if d11:
- vapor = [[22.235080,.1079, 2.144, 26.38, .76, 5.087, 1.00],
- [67.803960,.0011,8.732,28.58,.69, 4.930,.82],
- [119.995940,.0007,8.353, 29.48,.70, 4.780,.79],
- [183.310087, 2.273,.668, 29.06,.77, 5.022,.85],
- [321.225630, .0470,6.179, 24.04, .67, 4.398,.54],
- [325.152888,1.514, 1.541, 28.23, .64,4.893, .74],
- [336.227764,.0010,9.825, 26.93, .69, 4.740, .61],
- [380.197353, 11.67, 1.048, 28.11, .54, 5.063,.89],
- [390.134508, .0045, 7.347, 21.52, .63, 4.810,.55],
- [437.346667, .0632, 5.048, 18.45, .60, 4.230,.48],
- [439.150807, .9098, 3.595, 20.07, .63, 4.483,.52],
- [443.018343, .1920, 5.048, 15.55, .60, 5.083, .50],
- [448.001085, 10.41, 1.405, 25.64, .66, 5.028, .67],
- [470.888999, .3254, 3.597, 21.34, .66, 4.506,.65],
- [474.689092,1.260, 2.379, 23.20,.65, 4.804,.64],
- [488.490108,.2529, 2.852, 25.86,.69,5.201,.72],
- [503.568532,.0372, 6.731, 16.12,.61, 3.980,.43],
- [504.482692,.0124,6.731, 16.12,.61, 4.010,.45],
- [547.676440,.9785,.158,26.00,.70,4.500,1.00],
- [552.020960,.1840,.158,26.00,.70,4.500,1.00],
- [556.935985,497.0,.159, 30.86,.69,4.552,1.00],
- [620.700807,5.015, 2.391, 24.38,.71,4.856,.68],
- [645.766085,.0067, 8.633, 18.00,.60,4.000,.50],
- [658.005280,.2732,7.816,32.10,.69,4.140, 1.00],
- [752.033113,243.4, .396,30.86,.68,4.352,.84],
- [841.051732,.0134,8.177, 15.90, .33, 5.760,.45],
- [859.965698,.1325,8.055, 30.60,.68, 4.090,.84],
- [899.303175,.0547,7.914, 29.85,.68, 4.530,.90],
- [902.611085,.0386,8.429,28.65, .70, 5.100,.95],
- [906.205957,.1836, 5.110, 24.08,.70,4.700,.53],
- [916.171582,8.400,1.441, 26.73,.70, 5.150,.78],
- [923.112692,.0079,10.293,29.00,.70, 5.000,.80],
- [970.315022, 9.009, 1.919, 25.50,.64,4.940,.67],
- [987.926764,134.6,.257, 29.85, .68, 4.550,.90],
- [1780.000000,17506., .952, 196.3, 2.00,24.15,5.00]]
+ vapor = [
+ [22.235080, .1079, 2.144, 26.38, .76, 5.087, 1.00],
+ [67.803960, .0011, 8.732, 28.58, .69, 4.930, .82],
+ [119.995940, .0007, 8.353, 29.48, .70, 4.780, .79],
+ [183.310087, 2.273, .668, 29.06, .77, 5.022, .85],
+ [321.225630, .0470, 6.179, 24.04, .67, 4.398, .54],
+ [325.152888, 1.514, 1.541, 28.23, .64, 4.893, .74],
+ [336.227764, .0010, 9.825, 26.93, .69, 4.740, .61],
+ [380.197353, 11.67, 1.048, 28.11, .54, 5.063, .89],
+ [390.134508, .0045, 7.347, 21.52, .63, 4.810, .55],
+ [437.346667, .0632, 5.048, 18.45, .60, 4.230, .48],
+ [439.150807, .9098, 3.595, 20.07, .63, 4.483, .52],
+ [443.018343, .1920, 5.048, 15.55, .60, 5.083, .50],
+ [448.001085, 10.41, 1.405, 25.64, .66, 5.028, .67],
+ [470.888999, .3254, 3.597, 21.34, .66, 4.506, .65],
+ [474.689092, 1.260, 2.379, 23.20, .65, 4.804, .64],
+ [488.490108, .2529, 2.852, 25.86, .69, 5.201, .72],
+ [503.568532, .0372, 6.731, 16.12, .61, 3.980, .43],
+ [504.482692, .0124, 6.731, 16.12, .61, 4.010, .45],
+ [547.676440, .9785, .158, 26.00, .70, 4.500, 1.00],
+ [552.020960, .1840, .158, 26.00, .70, 4.500, 1.00],
+ [556.935985, 497.0, .159, 30.86, .69, 4.552, 1.00],
+ [620.700807, 5.015, 2.391, 24.38, .71, 4.856, .68],
+ [645.766085, .0067, 8.633, 18.00, .60, 4.000, .50],
+ [658.005280, .2732, 7.816, 32.10, .69, 4.140, 1.00],
+ [752.033113, 243.4, .396, 30.86, .68, 4.352, .84],
+ [841.051732, .0134, 8.177, 15.90, .33, 5.760, .45],
+ [859.965698, .1325, 8.055, 30.60, .68, 4.090, .84],
+ [899.303175, .0547, 7.914, 29.85, .68, 4.530, .90],
+ [902.611085, .0386, 8.429, 28.65, .70, 5.100, .95],
+ [906.205957, .1836, 5.110, 24.08, .70, 4.700, .53],
+ [916.171582, 8.400, 1.441, 26.73, .70, 5.150, .78],
+ [923.112692, .0079, 10.293, 29.00, .70, 5.000, .80],
+ [970.315022, 9.009, 1.919, 25.50, .64, 4.940, .67],
+ [987.926764, 134.6, .257, 29.85, .68, 4.550, .90],
+ [1780.000000, 17506., .952, 196.3, 2.00, 24.15, 5.00],
+ ]
else:
- vapor = [[22.235080, 0.1130, 2.143, 28.11, .69, 4.800, 1.00],
- [ 67.803960,0.0012, 8.735, 28.58, .69, 4.930, .82],
- [119.995940, 0.0008, 8.356, 29.48, .70, 4.780, .79],
- [183.310091, 2.4200, .668, 30.50, .64, 5.300,.85],
- [321.225644, 0.0483, 6.181, 23.03,.67, 4.690,.54],
- [325.152919,1.4990,1.540, 27.83, .68, 4.850,.74],
- [336.222601,0.0011, 9.829, 26.93, .69, 4.740,.61],
- [380.197372, 11.5200,1.048, 28.73,.54,5.380,.89],
- [390.134508,0.0046,7.350, 21.52,.63, 4.810,.55],
- [437.346667,0.0650,5.050,18.45,.60,4.230,.48],
- [439.150812,0.9218,3.596,21.00,.63, 4.290,.52],
- [443.018295,0.1976,5.050,18.60,.60,4.230,.50],
- [448.001075,10.3200,1.405,26.32,.66,4.840,.67],
- [470.888947,0.3297,3.599,21.52,.66,4.570,.65],
- [474.689127,1.2620,2.381,23.55,.65,4.650,.64],
- [488.491133,0.2520,2.853,26.02,.69,5.040,.72],
- [503.568532,0.0390,6.733,16.12,.61,3.980,.43],
- [504.482692,0.0130,6.733,16.12,.61,4.010,.45],
- [547.676440,9.7010,.114,26.00,.70,4.500,1.00],
- [552.020960,14.7700,.114,26.00,.70,4.500,1.00],
- [556.936002,487.4000,.159,32.10,.69,4.110,1.00],
- [620.700807,5.0120,2.200,24.38,.71,4.680,.68],
- [645.866155,0.0713,8.580,18.00,.60,4.000,.50],
- [658.005280,0.3022,7.820,32.10,.69,4.140,1.00],
- [752.033227,239.6000,.396,30.60,.68,4.090,.84],
- [841.053973,0.0140,8.180,15.90,.33,5.760,.45],
- [859.962313,0.1472,7.989,30.60,.68,4.090,.84],
- [899.306675,0.0605,7.917,29.85,.68,4.530,.90],
- [902.616173,0.0426, 8.432,28.65,.70,5.100,.95],
- [906.207325,0.1876,5.111,24.08,.70,4.700,.53],
- [916.171582,8.3400,1.442,26.70,.70,4.780,.78],
- [923.118427,0.0869,10.220,29.00,.70,5.000,.80],
- [970.315022,8.9720,1.920,25.50,.64,4.940,.67],
- [987.926764,132.1000,.258,29.85,.68,4.550,.90],
- [1780.000000,22300.0000,.952,176.20,.50,30.500,5.00]]
+ vapor = [
+ [22.235080, 0.1130, 2.143, 28.11, .69, 4.800, 1.00],
+ [67.803960, 0.0012, 8.735, 28.58, .69, 4.930, .82],
+ [119.995940, 0.0008, 8.356, 29.48, .70, 4.780, .79],
+ [183.310091, 2.4200, .668, 30.50, .64, 5.300, .85],
+ [321.225644, 0.0483, 6.181, 23.03, .67, 4.690, .54],
+ [325.152919, 1.4990, 1.540, 27.83, .68, 4.850, .74],
+ [336.222601, 0.0011, 9.829, 26.93, .69, 4.740, .61],
+ [380.197372, 11.5200, 1.048, 28.73, .54, 5.380, .89],
+ [390.134508, 0.0046, 7.350, 21.52, .63, 4.810, .55],
+ [437.346667, 0.0650, 5.050, 18.45, .60, 4.230, .48],
+ [439.150812, 0.9218, 3.596, 21.00, .63, 4.290, .52],
+ [443.018295, 0.1976, 5.050, 18.60, .60, 4.230, .50],
+ [448.001075, 10.3200, 1.405, 26.32, .66, 4.840, .67],
+ [470.888947, 0.3297, 3.599, 21.52, .66, 4.570, .65],
+ [474.689127, 1.2620, 2.381, 23.55, .65, 4.650, .64],
+ [488.491133, 0.2520, 2.853, 26.02, .69, 5.040, .72],
+ [503.568532, 0.0390, 6.733, 16.12, .61, 3.980, .43],
+ [504.482692, 0.0130, 6.733, 16.12, .61, 4.010, .45],
+ [547.676440, 9.7010, .114, 26.00, .70, 4.500, 1.00],
+ [552.020960, 14.7700, .114, 26.00, .70, 4.500, 1.00],
+ [556.936002, 487.4000, .159, 32.10, .69, 4.110, 1.00],
+ [620.700807, 5.0120, 2.200, 24.38, .71, 4.680, .68],
+ [645.866155, 0.0713, 8.580, 18.00, .60, 4.000, .50],
+ [658.005280, 0.3022, 7.820, 32.10, .69, 4.140, 1.00],
+ [752.033227, 239.6000, .396, 30.60, .68, 4.090, .84],
+ [841.053973, 0.0140, 8.180, 15.90, .33, 5.760, .45],
+ [859.962313, 0.1472, 7.989, 30.60, .68, 4.090, .84],
+ [899.306675, 0.0605, 7.917, 29.85, .68, 4.530, .90],
+ [902.616173, 0.0426, 8.432, 28.65, .70, 5.100, .95],
+ [906.207325, 0.1876, 5.111, 24.08, .70, 4.700, .53],
+ [916.171582, 8.3400, 1.442, 26.70, .70, 4.780, .78],
+ [923.118427, 0.0869, 10.220, 29.00, .70, 5.000, .80],
+ [970.315022, 8.9720, 1.920, 25.50, .64, 4.940, .67],
+ [987.926764, 132.1000, .258, 29.85, .68, 4.550, .90],
+ [1780.000000, 22300.0000, .952, 176.20, .50, 30.500, 5.00],
+ ]
oxygen = np.array(oxygen)
vapor = np.array(vapor)
@@ -179,13 +186,15 @@ def p676_ga(f, p, rho, T, d11):
delta = (a5 + a6 * theta) * 1e-4 * (p + e) * theta ** (0.8)
- Fi = f / fi* ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) + \
- (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2))
+ Fi = f / fi * ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) +
+ (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2))
d = 5.6e-4 * (p + e) * theta ** (0.8)
- Ndf = f * p * theta ** 2 * (6.14e-5 / (d * (1 + (f / d) ** 2)) + \
- 1.4e-12 * p * theta ** (1.5) / (1 + 1.9e-5 * f ** (1.5)) )
+ Ndf = f * p * theta ** 2 * (
+ 6.14e-5 / (d * (1 + (f / d) ** 2)) +
+ 1.4e-12 * p * theta ** (1.5) / (1 + 1.9e-5 * f ** (1.5))
+ )
# specific attenuation due to dry air(oxygen, pressureinducednitrogen
# and non - resonant Debye attenuation), equations(1 - 2)
@@ -196,7 +205,7 @@ def p676_ga(f, p, rho, T, d11):
if f <= 118.750343:
g_0 = 0.182 * f * (sum(Si * Fi) + Ndf)
else:
- g_0 = 0.182 * f * (sum(Si[37:-1]*Fi[37:-1]) + Ndf)
+ g_0 = 0.182 * f * (sum(Si[37:-1] * Fi[37:-1]) + Ndf)
# vapor computation
fi = vapor[:, 0]
@@ -212,10 +221,10 @@ def p676_ga(f, p, rho, T, d11):
delta = 0
Fi = f / fi * ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) +
- (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2))
+ (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2))
# specific attenuation due to watervapour, equations(1 - 2)
- g_w = 0.182 * f * np.sum(Si* Fi)
+ g_w = 0.182 * f * np.sum(Si * Fi)
return g_0, g_w
diff --git a/sharc/propagation/propagation.py b/sharc/propagation/propagation.py
index 4c0a0f7fe..d72ca7b74 100644
--- a/sharc/propagation/propagation.py
+++ b/sharc/propagation/propagation.py
@@ -8,6 +8,10 @@
from abc import ABC, abstractmethod
import numpy as np
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
+
+
class Propagation(ABC):
"""
Abstract base class for propagation models
@@ -15,7 +19,33 @@ class Propagation(ABC):
def __init__(self, random_number_gen: np.random.RandomState):
self.random_number_gen = random_number_gen
+ # Inicates whether this propagation model is for links between earth and space
+ self.is_earth_space_model = False
@abstractmethod
- def get_loss(self, *args, **kwargs) -> np.array:
- pass
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing station_a
+ station_b : StationManager
+ StationManager container representing station_a
+ params : Parameters
+ Simulation parameters needed for the propagation class.
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
diff --git a/sharc/propagation/propagation_abg.py b/sharc/propagation/propagation_abg.py
index 88128fe2e..4e4628870 100644
--- a/sharc/propagation/propagation_abg.py
+++ b/sharc/propagation/propagation_abg.py
@@ -5,9 +5,12 @@
@author: LeticiaValle_Mac
"""
-from sharc.propagation.propagation import Propagation
-
import numpy as np
+from multipledispatch import dispatch
+
+from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
class PropagationABG(Propagation):
@@ -16,16 +19,73 @@ class PropagationABG(Propagation):
Loss Models for 5G Urban Microand Macro-Cellular Scenarios"
"""
- def __init__(self, random_number_gen: np.random.RandomState):
+ def __init__(
+ self, random_number_gen: np.random.RandomState,
+ alpha=3.4, beta=19.2, gamma=2.3, building_loss=20, shadowing_sigma_dB=6.5,
+ ):
super().__init__(random_number_gen)
- self.alpha = 3.4
- self.beta = 19.2
- self.gamma = 2.3
+ self.alpha = alpha
+ self.beta = beta
+ self.gamma = gamma
self.building_loss = 20
self.shadowing_sigma_dB = 6.5
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the get_loss method
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing IMT UE station - Station_type.IMT_UE
+ station_b : StationManager
+ StationManager container representing IMT BS stattion
+ params : Parameters
+ Simulation parameters needed for the propagation class - Station_type.IMT_BS
- def get_loss(self, *args, **kwargs) -> np.array:
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ wrap_around_enabled = False
+ if params.imt.topology.type == "MACROCELL":
+ wrap_around_enabled = params.imt.topology.macrocell.wrap_around \
+ and params.imt.topology.macrocell.num_clusters == 1
+ if params.imt.topology.type == "HOTSPOT":
+ wrap_around_enabled = params.imt.topology.hotspot.wrap_around \
+ and params.imt.topology.hotspot.num_clusters == 1
+
+ if wrap_around_enabled:
+ _, distances_3d, _, _ = \
+ station_a.get_dist_angles_wrap_around(station_b)
+ else:
+ distances_3d = station_a.get_3d_distance_to(station_b)
+
+ indoor_stations = station_a.indoor
+
+ loss = \
+ self.get_loss(
+ distances_3d,
+ frequency * np.ones(distances_3d.shape),
+ indoor_stations,
+ params.imt.shadowing,
+ )
+
+ return loss
+
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, bool)
+ def get_loss(self, distance: np.array, frequency: np.array, indoor_stations: np.array, shadowing: bool) -> np.array:
"""
Calculates path loss for LOS and NLOS cases with respective shadowing
(if shadowing is to be added)
@@ -45,40 +105,21 @@ def get_loss(self, *args, **kwargs) -> np.array:
array with path loss values with dimensions of distance_2D
"""
- f = kwargs["frequency"]
- indoor_stations = kwargs["indoor_stations"]
-
- if "distance_3D" in kwargs:
- d = kwargs["distance_3D"]
- else:
- d = kwargs["distance_2D"]
-
- if "alpha" in kwargs:
- self.alpha = kwargs["alpha"]
-
- if "beta" in kwargs:
- self.beta = kwargs["beta"]
-
- if "gamma" in kwargs:
- self.gamma = kwargs["gamma"]
-
- if "shadowing" in kwargs:
- std = kwargs["shadowing"]
- else:
- std = False
-
- if std:
- shadowing = self.random_number_gen.normal(0, self.shadowing_sigma_dB, d.shape)
+ if shadowing:
+ shadowing = self.random_number_gen.normal(
+ 0, self.shadowing_sigma_dB, distance.shape,
+ )
else:
shadowing = 0
- building_loss = self.building_loss*indoor_stations
+ building_loss = self.building_loss * np.tile(indoor_stations, (distance.shape[1], 1)).transpose()
- loss = 10*self.alpha*np.log10(d) + self.beta + 10*self.gamma*np.log10(f*1e-3) + \
- shadowing + building_loss
+ loss = 10 * self.alpha * np.log10(distance) + self.beta + 10 * self.gamma * np.log10(frequency * 1e-3) + \
+ shadowing + building_loss
return loss
+
if __name__ == '__main__':
###########################################################################
@@ -90,30 +131,38 @@ def get_loss(self, *args, **kwargs) -> np.array:
import matplotlib.pyplot as plt
shadowing_std = 0
- distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis]
- freq = 26000*np.ones(distance_2D.shape)
- h_bs = 25*np.ones(len(distance_2D[:,0]))
- h_ue = 1.5*np.ones(len(distance_2D[0,:]))
+ distance_2D = np.linspace(1, 1000, num=1000)[:, np.newaxis]
+ freq = 26000 * np.ones(distance_2D.shape)
+ h_bs = 25 * np.ones(len(distance_2D[:, 0]))
+ h_ue = 1.5 * np.ones(len(distance_2D[0, :]))
h_e = np.zeros(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2)
random_number_gen = np.random.RandomState(101)
-
+
uma = PropagationUMa(random_number_gen)
- umi = PropagationUMi(random_number_gen)
+ umi = PropagationUMi(random_number_gen, 18)
abg = PropagationABG(random_number_gen)
freespace = PropagationFreeSpace(random_number_gen)
- uma_los = uma.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- uma_nlos = uma.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- umi_los = umi.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- umi_nlos = umi.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- fs = freespace.get_loss(distance_2D=distance_2D, frequency=freq)
- abg_los = abg.get_loss(distance_2D=distance_2D, frequency=freq, indoor_stations=0)
-
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ uma_los = uma.get_loss_los(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ uma_nlos = uma.get_loss_nlos(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ umi_los = umi.get_loss_los(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ umi_nlos = umi.get_loss_nlos(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ fs = freespace.get_loss(distance_2D, freq)
+ abg_los = abg.get_loss(distance_2D, freq, np.zeros(shape=distance_2D.shape, dtype=bool,), False,)
+
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
- #ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
+ # ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
ax.semilogx(distance_2D, uma_los, "-r", label="UMa LOS")
ax.semilogx(distance_2D, uma_nlos, "--r", label="UMa NLOS")
@@ -125,8 +174,8 @@ def get_loss(self, *args, **kwargs) -> np.array:
plt.title("Path loss models")
plt.xlabel("distance [m]")
plt.ylabel("path loss [dB]")
- plt.xlim((0, distance_2D[-1,0]))
- #plt.ylim((0, 1.1))
+ plt.xlim((0, distance_2D[-1, 0]))
+ # plt.ylim((0, 1.1))
plt.legend(loc="upper left")
plt.tight_layout()
plt.grid()
diff --git a/sharc/propagation/propagation_building_entry_loss.py b/sharc/propagation/propagation_building_entry_loss.py
index 1b1ee1112..02a78ff27 100644
--- a/sharc/propagation/propagation_building_entry_loss.py
+++ b/sharc/propagation/propagation_building_entry_loss.py
@@ -16,8 +16,10 @@ class PropagationBuildingEntryLoss(Propagation):
Implements the building entry loss according to ITU-R P.2109-0 (Prediction of Building Entry Loss)
"""
- def get_loss(self, frequency_MHz, elevation, prob="RANDOM",
- building_class="TRADITIONAL", test = False) -> np.array:
+ def get_loss(
+ self, frequency_MHz, elevation, prob="RANDOM",
+ building_class="TRADITIONAL", test=False,
+ ) -> np.array:
"""
Calculates building loss
@@ -86,22 +88,33 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM",
return loss
+
if __name__ == '__main__':
entry_loss = PropagationBuildingEntryLoss(np.random.RandomState())
- freq_GHz_log = np.arange(-1,2.1,.1)
+ freq_GHz_log = np.arange(-1, 2.1, .1)
freq_GHz = 10 ** freq_GHz_log
freq_MHz = freq_GHz * 1000
# Plot median BLE mu_1, for comparison with ITU-R P2109-0
plt.figure()
- median_loss_traditional = entry_loss.get_loss( freq_MHz, 0, prob=.5,
- building_class="TRADITIONAL", test=True)
- median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 0, prob=.5,
- building_class="THERMALLY_EFFICIENT", test=True)
- plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 0deg")
- plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 0deg")
+ median_loss_traditional = entry_loss.get_loss(
+ freq_MHz, 0, prob=.5,
+ building_class="TRADITIONAL", test=True,
+ )
+ median_loss_therm_eff = entry_loss.get_loss(
+ freq_MHz, 0, prob=.5,
+ building_class="THERMALLY_EFFICIENT", test=True,
+ )
+ plt.semilogx(
+ freq_GHz, median_loss_traditional,
+ '-', label="TRADITIONAL, 0deg",
+ )
+ plt.semilogx(
+ freq_GHz, median_loss_therm_eff, '--',
+ label="THERMALLY_EFFICIENT, 0deg",
+ )
plt.legend(title="Building Type, elevation")
plt.grid()
@@ -112,31 +125,61 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM",
# Plot median loss at different angles,
# 0 degrees
plt.figure()
- median_loss_traditional = entry_loss.get_loss( freq_MHz, 0, prob=.5,
- building_class="TRADITIONAL")
- median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 0, prob=.5,
- building_class="THERMALLY_EFFICIENT")
-
- plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 0deg")
- plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 0deg")
+ median_loss_traditional = entry_loss.get_loss(
+ freq_MHz, 0, prob=.5,
+ building_class="TRADITIONAL",
+ )
+ median_loss_therm_eff = entry_loss.get_loss(
+ freq_MHz, 0, prob=.5,
+ building_class="THERMALLY_EFFICIENT",
+ )
+
+ plt.semilogx(
+ freq_GHz, median_loss_traditional,
+ '-', label="TRADITIONAL, 0deg",
+ )
+ plt.semilogx(
+ freq_GHz, median_loss_therm_eff, '--',
+ label="THERMALLY_EFFICIENT, 0deg",
+ )
# 45 degrees
- median_loss_traditional = entry_loss.get_loss( freq_MHz, 45, prob=.5,
- building_class="TRADITIONAL")
- median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 45, prob=.5,
- building_class="THERMALLY_EFFICIENT")
-
- plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 45deg")
- plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 45deg")
+ median_loss_traditional = entry_loss.get_loss(
+ freq_MHz, 45, prob=.5,
+ building_class="TRADITIONAL",
+ )
+ median_loss_therm_eff = entry_loss.get_loss(
+ freq_MHz, 45, prob=.5,
+ building_class="THERMALLY_EFFICIENT",
+ )
+
+ plt.semilogx(
+ freq_GHz, median_loss_traditional,
+ '-', label="TRADITIONAL, 45deg",
+ )
+ plt.semilogx(
+ freq_GHz, median_loss_therm_eff, '--',
+ label="THERMALLY_EFFICIENT, 45deg",
+ )
# 90 deg
- median_loss_traditional = entry_loss.get_loss( freq_MHz, 90, prob=.5,
- building_class="TRADITIONAL")
- median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 90, prob=.5,
- building_class="THERMALLY_EFFICIENT")
-
- plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 90deg")
- plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 90deg")
+ median_loss_traditional = entry_loss.get_loss(
+ freq_MHz, 90, prob=.5,
+ building_class="TRADITIONAL",
+ )
+ median_loss_therm_eff = entry_loss.get_loss(
+ freq_MHz, 90, prob=.5,
+ building_class="THERMALLY_EFFICIENT",
+ )
+
+ plt.semilogx(
+ freq_GHz, median_loss_traditional,
+ '-', label="TRADITIONAL, 90deg",
+ )
+ plt.semilogx(
+ freq_GHz, median_loss_therm_eff, '--',
+ label="THERMALLY_EFFICIENT, 90deg",
+ )
plt.legend(title="Building Type, elevation")
plt.grid()
@@ -146,30 +189,29 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM",
plt.show()
plt.close()
-
+
# parameters
freq_MHz = 40e3
- probability = np.linspace(0,1,num=1000)
+ probability = np.linspace(0, 1, num=1000)
elevations = np.array([0, 45, 90])
- loss = np.zeros((len(elevations),len(probability)))
-
+ loss = np.zeros((len(elevations), len(probability)))
+
# calculate loss
- for n,el in enumerate(elevations):
- for m,pb in enumerate(probability):
- loss[n,m] = entry_loss.get_loss( freq_MHz, el, prob=pb,
- building_class="TRADITIONAL")
-
- for n,el in enumerate(elevations):
+ for n, el in enumerate(elevations):
+ for m, pb in enumerate(probability):
+ loss[n, m] = entry_loss.get_loss(
+ freq_MHz, el, prob=pb,
+ building_class="TRADITIONAL",
+ )
+
+ for n, el in enumerate(elevations):
lbl = str(el) + " deg"
- plt.plot(loss[n],probability,label=lbl)
-
+ plt.plot(loss[n], probability, label=lbl)
+
plt.xlabel("Building Entry Loss [dB]")
plt.ylabel("Probability that loss is exceeded")
- plt.ylim((0,1))
+ plt.ylim((0, 1))
# plt.xlim((0,65))
plt.grid()
plt.legend()
plt.show()
-
-
-
diff --git a/sharc/propagation/propagation_clear_air_452.py b/sharc/propagation/propagation_clear_air_452.py
index 0aebd71f1..68ff9d583 100644
--- a/sharc/propagation/propagation_clear_air_452.py
+++ b/sharc/propagation/propagation_clear_air_452.py
@@ -1,32 +1,37 @@
# -*- coding: utf-8 -*-
-
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
Created on Mon May 22 15:10:11 2017
"""
-from sharc.propagation.propagation import Propagation
+import numpy as np
+from multipledispatch import dispatch
+from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
+from sharc.parameters.parameters_p452 import ParametersP452
from sharc.propagation.clear_air_452_aux import p676_ga
from sharc.propagation.clear_air_452_aux import inv_cum_norm
from sharc.support.enumerations import StationType
from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
-import numpy as np
class PropagationClearAir(Propagation):
"""
Basic transmission loss due to free-space propagation and attenuation by atmospheric gases
"""
- def __init__(self, random_number_gen: np.random.RandomState):
+ # pylint: disable=function-redefined
+ # pylint: disable=arguments-renamed
+
+ def __init__(self, random_number_gen: np.random.RandomState, model_params: ParametersP452):
super().__init__(random_number_gen)
self.clutter = PropagationClutterLoss(random_number_gen)
- self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen)
-
+ self.building_entry = PropagationBuildingEntryLoss(
+ self.random_number_gen,
+ )
self.building_loss = 20
-
+ self.model_params = model_params
@staticmethod
def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r):
@@ -45,10 +50,10 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r):
if ha > htg:
- Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a)
- Aht = 10.25 * Ffc * np.exp(-dk) * (1 - np.tanh(6 * (htg / ha - 0.625))) - 0.33 # (57)
-
- flagAht = 1
+ Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a)
+ Aht = 10.25 * Ffc * \
+ np.exp(-dk) * (1 - np.tanh(6 * (htg / ha - 0.625))) - \
+ 0.33 # (57)
kk = np.nonzero(d >= dk)
@@ -63,10 +68,10 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r):
dk = dk_r
if ha > hrg:
- Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a)
- Ahr = 10.25 * Ffc * np.exp(-dk) * (1 - np.tanh(6 * (hrg / ha - 0.625))) - 0.33 # (57)
-
- flagAhr = 1
+ Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a)
+ Ahr = 10.25 * Ffc * \
+ np.exp(-dk) * (1 - np.tanh(6 * (hrg / ha - 0.625))) - \
+ 0.33 # (57)
kk = np.nonzero(d <= d[-1] - dk)
if kk.size:
@@ -78,15 +83,15 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r):
# Modify the path
- if (index2 - index1 < 3): # at least two points between the clutter at Tx and Rx sides
+ if (index2 - index1 < 3): # at least two points between the clutter at Tx and Rx sides
error_message = "tl_p452: closs_corr: the sum of clutter nominal distances is larger than the path length."
raise ValueError(error_message)
- dc = d[index1-1:index2] - d[index1-1]
- hc = h[index1-1:index2]
- zonec = zone[index1-1:index2]
+ dc = d[index1 - 1:index2] - d[index1 - 1]
+ hc = h[index1 - 1:index2]
+ zonec = zone[index1 - 1:index2]
- return dc, hc, zonec,htgc, hrgc, Aht, Ahr
+ return dc, hc, zonec, htgc, hrgc, Aht, Ahr
@staticmethod
def longest_cont_dist(d, zone, zone_r):
@@ -97,10 +102,10 @@ def longest_cont_dist(d, zone, zone_r):
else:
aux = zone == zone_r
- aux = np.append(0,np.append(aux,0))
+ aux = np.append(0, np.append(aux, 0))
aux = np.diff(aux)
- start = np.where(aux==1)[0]
- stop = np.where(aux==-1)[0] - 1
+ start = np.where(aux == 1)[0]
+ stop = np.where(aux == -1)[0] - 1
start = np.atleast_1d(start)
stop = np.atleast_1d(stop)
@@ -121,9 +126,12 @@ def longest_cont_dist(d, zone, zone_r):
@staticmethod
def beta0(phi, dtm, dlm):
- tau = 1 - np.exp(-(4.12 * 1e-4 * dlm ** 2.41)) # (3a)
+ tau = 1 - np.exp(-(4.12 * 1e-4 * dlm ** 2.41)) # (3a)
- mu1 = ( 10 ** (-dtm / (16 - 6.6 * tau)) + 10 ** (-5 * (0.496 + 0.354 * tau)))** 0.2
+ mu1 = (
+ 10 ** (-dtm / (16 - 6.6 * tau)) + 10 **
+ (-5 * (0.496 + 0.354 * tau))
+ ) ** 0.2
indices = np.nonzero(mu1 > 1)
mu1[indices] = 1
@@ -159,26 +167,31 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f):
# Section 5.1.6.2
v1 = 0
- for ii in range (1,n):
- v1 = v1 + (d[ii] - d[ii - 1]) * (h[ii] + h[ii - 1]) # Eq(161)
+ for ii in range(1, n):
+ v1 = v1 + (d[ii] - d[ii - 1]) * (h[ii] + h[ii - 1]) # Eq(161)
v2 = 0
for ii in range(2, n):
- v2 = v2 + (d[ii] - d[ii - 1]) * (h[ii] * (2 * d[ii] + d[ii - 1]) + h[ii - 1] * (d[ii] + 2 * d[ii - 1])) # Eq(162)
+ v2 = v2 + (d[ii] - d[ii - 1]) * (
+ h[ii] * (
+ 2 * d[ii] + d[ii - 1]
+ # Eq(162)
+ ) + h[ii - 1] * (d[ii] + 2 * d[ii - 1])
+ )
- hst = (2 * v1 * dtot - v2) / dtot ** 2 # Eq(163)
- hsr = (v2 - v1 * dtot) / dtot ** 2 # Eq(164)
+ hst = (2 * v1 * dtot - v2) / dtot ** 2 # Eq(163)
+ hsr = (v2 - v1 * dtot) / dtot ** 2 # Eq(164)
# Section 5.1.6.3
- HH = h - (hts * (dtot - d) + hrs * d) / dtot # Eq(165d)
- hobs = max(HH[1:n - 1]) # Eq(165a)
+ HH = h - (hts * (dtot - d) + hrs * d) / dtot # Eq(165d)
+ hobs = max(HH[1:n - 1]) # Eq(165a)
- alpha_obt = max(HH[1:n - 1]/ d[1:n - 1]) # Eq(165b)
- alpha_obr = max(HH[1:n - 1]/ (dtot - d[1:n - 1])) # Eq(165c)
+ alpha_obt = max(HH[1:n - 1] / d[1:n - 1]) # Eq(165b)
+ alpha_obr = max(HH[1:n - 1] / (dtot - d[1:n - 1])) # Eq(165c)
# Calculate provisional values for the Tx and Rx smooth surface heights
- gt = alpha_obt / (alpha_obt + alpha_obr) # Eq(166e)
- gr = alpha_obr / (alpha_obt + alpha_obr) # Eq(166f)
+ gt = alpha_obt / (alpha_obt + alpha_obr) # Eq(166e)
+ gr = alpha_obr / (alpha_obt + alpha_obr) # Eq(166f)
if hobs <= 0:
hstp = hst
@@ -201,19 +214,22 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f):
# Interfering antenna horizon elevation angle and distance
ii = np.arange(1, n - 1)
- theta = 1000 * np.arctan((h[ii] - hts)/ (1000 * d[ii]) - d[ii] / (2 * ae))
+ theta = 1000 * np.arctan((h[ii] - hts) /
+ (1000 * d[ii]) - d[ii] / (2 * ae))
- #theta(theta < 0) = 0; % condition below equation(152)
+ # theta(theta < 0) = 0; % condition below equation(152)
theta_t = max(theta)
- theta_td = 1000 * np.arctan((hrs - hts)/ (1000 * dtot) - dtot / (2 * ae))
- theta_rd = 1000 * np.arctan((hts - hrs)/ (1000 * dtot) - dtot / (2 * ae))
+ theta_td = 1000 * np.arctan((hrs - hts) /
+ (1000 * dtot) - dtot / (2 * ae))
+ theta_rd = 1000 * np.arctan((hts - hrs) /
+ (1000 * dtot) - dtot / (2 * ae))
if theta_t > theta_td:
- pathtype = 2 # transhorizon
+ pathtype = 2 # transhorizon
else:
- pathtype = 1 # los
+ pathtype = 1 # los
kindex = np.nonzero(theta == theta_t)
@@ -223,7 +239,9 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f):
# Interfered-with antenna horizon elevation angle and distance
- theta = 1000 * np.arctan((h[ii] - hrs)/(1000 * (dtot - d[ii])) - (dtot - d[ii])/(2 * ae))
+ theta = 1000 * \
+ np.arctan((h[ii] - hrs) / (1000 * (dtot - d[ii])) -
+ (dtot - d[ii]) / (2 * ae))
# theta(theta < 0) = 0;
@@ -238,27 +256,27 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f):
theta_t = theta_td
theta_r = theta_rd
- ii = np.arange(1,n - 1)
+ ii = np.arange(1, n - 1)
lamb = 0.3 / f
Ce = 1 / ae
- nu = (h[ii] + 500 * Ce * d[ii] * (dtot-d[ii])- (hts * (dtot- d[ii]) + hrs * d[ii]) / dtot)* \
- np.sqrt(0.002 * dtot/( lamb * d[ii]*(dtot-d[ii])))
+ nu = (h[ii] + 500 * Ce * d[ii] * (dtot - d[ii]) - (hts * (dtot - d[ii]) + hrs * d[ii]) / dtot) * \
+ np.sqrt(0.002 * dtot / (lamb * d[ii] * (dtot - d[ii])))
numax = max(nu)
kindex = np.nonzero(nu == numax)
lt = kindex[-1] + 1
dlt = d[lt]
dlr = dtot - dlt
- kindex = np.nonzero(dlr <= dtot -d[ii])
+ kindex = np.nonzero(dlr <= dtot - d[ii])
lr = kindex[0][-1] + 1
# Angular distance
theta_tot = 1e3 * dtot / ae + theta_t + theta_r
- #Section 5.1.6.4 Ducting / layer-reflection model
+ # Section 5.1.6.4 Ducting / layer-reflection model
# Calculate the smooth-Earth heights at transmitter and receiver as
# required for the roughness factor
@@ -269,11 +287,11 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f):
# Slope of the smooth - Earth surface
m = (hsr - hst) / dtot
- #The terminal effective heigts for the ducting / layer - reflection model
+ # The terminal effective heigts for the ducting / layer - reflection model
hte = htg + h[0] - hst
hre = hrg + h[-1] - hsr
- ii = np.arange(lt,lr+1)
+ ii = np.arange(lt, lr + 1)
hm = max(h[ii] - (hst + m * d[ii]))
return hst, hsr, hstd, hsrd, hte, hre, hm, dlt, dlr, theta_t, theta_r, theta_tot, pathtype
@@ -283,7 +301,7 @@ def path_fraction(d, zone, zone_r):
dm = 0
aux = np.nonzero(zone == zone_r)
- start = aux[0] # actually find_intervals
+ start = aux[0] # actually find_intervals
stop = aux[-1]
start = np.atleast_1d(start)
stop = np.atleast_1d(stop)
@@ -334,7 +352,7 @@ def pl_los(d, f, p, b0, w, T, press, dlt, dlr):
return Lbfsg, Lb0p, Lb0b
@staticmethod
- def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr ):
+ def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr):
# Frequency dependent loss
@@ -357,12 +375,15 @@ def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr ):
# percentage p, below 50
# is given
- Lbs = 190 + Lf + 20 * np.log10(dtot) + 0.573 * theta - 0.15 * N0 + Lc + Ag - 10.1 * (-np.log10(p / 50)) ** (0.7)
+ Lbs = 190 + Lf + 20 * np.log10(dtot) + 0.573 * theta - \
+ 0.15 * N0 + Lc + Ag - 10.1 * (-np.log10(p / 50)) ** (0.7)
return Lbs
@staticmethod
- def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, theta_r, f, p, T, press,
- omega, ae, b0):
+ def tl_anomalous(
+ dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, theta_r, f, p, T, press,
+ omega, ae, b0,
+ ):
Alf = 0
if f < 0.5:
@@ -378,10 +399,16 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t,
Asr = 0
if theta_t1 > 0:
- Ast = 20 * np.log10(1 + 0.361 * theta_t1 * np.sqrt(f * dlt)) + 0.264 * theta_t1 * f ** (1 / 3)
+ Ast = 20 * np.log10(
+ 1 + 0.361 * theta_t1 *
+ np.sqrt(f * dlt),
+ ) + 0.264 * theta_t1 * f ** (1 / 3)
if theta_r1 > 0:
- Asr = 20 * np.log10(1 + 0.361 * theta_r1 * np.sqrt(f * dlr)) + 0.264 * theta_r1 * f ** (1 / 3)
+ Asr = 20 * np.log10(
+ 1 + 0.361 * theta_r1 *
+ np.sqrt(f * dlr),
+ ) + 0.264 * theta_r1 * f ** (1 / 3)
# over - sea surface duct coupling correction for the interfering and
# interfered-with stations(49) and (49a)
@@ -391,12 +418,14 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t,
if dct <= 5:
if dct <= dlt:
if omega >= 0.75:
- Act = -3 * np.exp(-0.25 * dct * dct) * (1 + np.tanh(0.07 * (50 - hts)))
+ Act = -3 * np.exp(-0.25 * dct * dct) * \
+ (1 + np.tanh(0.07 * (50 - hts)))
if dcr <= 5:
if dcr <= dlr:
if omega >= 0.75:
- Acr = -3 * np.exp(-0.25 * dcr * dcr) * (1 + np.tanh(0.07 * (50 - hrs)))
+ Acr = -3 * np.exp(-0.25 * dcr * dcr) * \
+ (1 + np.tanh(0.07 * (50 - hrs)))
# specific attenuation(51)
gamma_d = 5e-5 * ae * f ** (1 / 3)
@@ -437,13 +466,15 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t,
beta = b0 * mu2 * mu3
- #beta = max(beta, eps); % to avoid division by zero
+ # beta = max(beta, eps); % to avoid division by zero
Gamma = 1.076 / (2.0058 - np.log10(beta)) ** 1.012 * \
- np.exp(-(9.51 - 4.8 * np.log10(beta) + 0.198 * (np.log10(beta)) ** 2) * 1e-6 * dtot ** (1.13))
+ np.exp(
+ -(9.51 - 4.8 * np.log10(beta) + 0.198 * (np.log10(beta)) ** 2) * 1e-6 * dtot ** (1.13),)
# time percentage variablity(cumulative distribution):
- Ap = -12 + (1.2 + 3.7e-3 * dtot) * np.log10(p / beta) + 12 * (p / beta) ** Gamma
+ Ap = -12 + (1.2 + 3.7e-3 * dtot) * \
+ np.log10(p / beta) + 12 * (p / beta) ** Gamma
# time percentage and angular - distance dependent losses within the
# anomalous propagation mechanism
@@ -462,17 +493,18 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t,
# total of fixed coupling losses(except for local clutter losses) between
# the antennas and the anomalous propagation structure within the atmosphere (47)
- Af = 102.45 + 20 * np.log10(f) + 20 * np.log10(dlt + dlr) + Alf + Ast + Asr + Act + Acr;
+ Af = 102.45 + 20 * \
+ np.log10(f) + 20 * np.log10(dlt + dlr) + \
+ Alf + Ast + Asr + Act + Acr
# total basic transmission loss occuring during periods of anomalaous
# propagation
- Lba = Af + Adp + Ag;
+ Lba = Af + Adp + Ag
return Lba
@staticmethod
-
def dl_bull(d, h, hts, hrs, ap, f):
# Effective Earth curvature Ce(km ^ -1)
@@ -482,7 +514,7 @@ def dl_bull(d, h, hts, hrs, ap, f):
lamb = 0.3 / f
# Complete path length
- dtot = d[-1]-d[0]
+ dtot = d[-1] - d[0]
# Find the intermediate profile point with the highest slope of the line
# from the transmitter to the point
@@ -496,33 +528,41 @@ def dl_bull(d, h, hts, hrs, ap, f):
# LoS path
Str = (hrs - hts) / dtot
- if Stim < Str: #Case 1, Path is LoS
+ if Stim < Str: # Case 1, Path is LoS
# Find the intermediate profile point with the highest diffraction parameter nu:
numax = np.max(
- (hi + 500 * Ce * di* (dtot - di) - (hts * (dtot - di) + hrs * di) / dtot)*
- np.sqrt(0.002 * dtot/ (lamb *di * (dtot - di))))
+ (
+ hi + 500 * Ce * di * (dtot - di) -
+ (hts * (dtot - di) + hrs * di) / dtot
+ ) *
+ np.sqrt(0.002 * dtot / (lamb * di * (dtot - di))),
+ )
Luc = 0
if numax > -0.78:
- Luc = 6.9 + 20 * np.log10(np.sqrt((numax - 0.1) ** 2 + 1) + numax - 0.1)
+ Luc = 6.9 + 20 * \
+ np.log10(np.sqrt((numax - 0.1) ** 2 + 1) + numax - 0.1)
else:
# Path is transhorizon
# Find the intermediate profile pointwith the highest slope of the
# line from the receiver to the point
- Srim = np.max((hi + 500 * Ce * di * (dtot - di) - hrs) / (dtot - di))
+ Srim = np.max(
+ (hi + 500 * Ce * di * (dtot - di) - hrs) / (dtot - di),
+ )
# Calculate the distance of the Bullington point from the transmitter:
dbp = (hrs - hts + Srim * dtot) / (Stim + Srim)
# Calculate the diffraction parameter, nub, for the Bullington point
nub = (hts + Stim * dbp - (hts * (dtot - dbp) + hrs * dbp) / dtot) * \
- np.sqrt(0.002 * dtot / (lamb *dbp*(dtot - dbp)))
+ np.sqrt(0.002 * dtot / (lamb * dbp * (dtot - dbp)))
# The knife - edge loss for the Bullington point is given by
Luc = 0
if nub > -0.78:
- Luc = 6.9 + 20 * np.log10(np.sqrt((nub - 0.1) ** 2 + 1) + nub - 0.1)
+ Luc = 6.9 + 20 * \
+ np.log10(np.sqrt((nub - 0.1) ** 2 + 1) + nub - 0.1)
# For Luc calculated using either(17) or (21), Bullington diffraction loss
# for the path is given by
@@ -533,18 +573,22 @@ def dl_bull(d, h, hts, hrs, ap, f):
def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f):
# Normalized factor for surface admittance for horizontal (1) and vertical
# (2) polarizations
- K = np.empty(2)
- K[0] = 0.036 * (adft * f)** (-1/3) * ((epsr - 1) ** 2 + (18 * sigma / f)** 2)** (-1 / 4)
- K[1] = K[0] * (epsr** 2 + (18 * sigma / f)** 2)** (1/2)
+ K = np.empty(2)
+ K[0] = 0.036 * (adft * f) ** (-1 / 3) * (
+ (epsr - 1) **
+ 2 + (18 * sigma / f) ** 2
+ ) ** (-1 / 4)
+ K[1] = K[0] * (epsr ** 2 + (18 * sigma / f) ** 2) ** (1 / 2)
# Earth ground / polarization parameter
- beta_dft = (1 + 1.6 * K** 2 + 0.67 * K**4)/(1 + 4.5 * K** 2 + 1.53 * K** 4)
+ beta_dft = (1 + 1.6 * K ** 2 + 0.67 * K**4) / \
+ (1 + 4.5 * K ** 2 + 1.53 * K ** 4)
# Normalized distance
- X = 21.88 * beta_dft * (f/ adft ** 2)** (1 / 3) * d
+ X = 21.88 * beta_dft * (f / adft ** 2) ** (1 / 3) * d
# Normalized transmitter and receiver heights
- Yt = 0.9575 * beta_dft * (f** 2 / adft) ** (1 / 3) * hte
+ Yt = 0.9575 * beta_dft * (f ** 2 / adft) ** (1 / 3) * hte
Yr = 0.9575 * beta_dft * (f ** 2 / adft) ** (1 / 3) * hre
# Calculate the distance term given by:
@@ -553,7 +597,7 @@ def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f):
if X[ii] >= 1.6:
Fx[ii] = 11 + 10 * np.log10(X[ii]) - 17.6 * X[ii]
else:
- Fx[ii] = -20 * np.log10(X[ii]) - 5.6488 * (X[ii])** 1.425
+ Fx[ii] = -20 * np.log10(X[ii]) - 5.6488 * (X[ii]) ** 1.425
Bt = beta_dft * Yt
Br = beta_dft * Yr
@@ -563,12 +607,14 @@ def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f):
for ii in range(2):
if Bt[ii] > 2:
- GYt[ii] = 17.6 * (Bt[ii] - 1.1) ** 0.5 - 5 * np.log10(Bt[ii] - 1.1) - 8
+ GYt[ii] = 17.6 * (Bt[ii] - 1.1) ** 0.5 - 5 * \
+ np.log10(Bt[ii] - 1.1) - 8
else:
GYt[ii] = 20 * np.log10(Bt[ii] + 0.1 * Bt[ii] ** 3)
if Br[ii] > 2:
- GYr[ii] = 17.6 * (Br[ii] - 1.1)** 0.5 - 5 * np.log10(Br[ii] - 1.1) - 8
+ GYr[ii] = 17.6 * (Br[ii] - 1.1) ** 0.5 - 5 * \
+ np.log10(Br[ii] - 1.1) - 8
else:
GYr[ii] = 20 * np.log10(Br[ii] + 0.1 * Br[ii] ** 3)
@@ -588,20 +634,23 @@ def dl_se_ft(d, hte, hre, adft, f, omega):
epsr = 22
sigma = 0.003
- Ldft_land = PropagationClearAir.dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f)
+ Ldft_land = PropagationClearAir.dl_se_ft_inner(
+ epsr, sigma, d, hte, hre, adft, f,
+ )
# First - term part of the spherical - Earth diffraction loss over sea
epsr = 80
sigma = 5
- Ldft_sea = PropagationClearAir.dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f)
+ Ldft_sea = PropagationClearAir.dl_se_ft_inner(
+ epsr, sigma, d, hte, hre, adft, f,
+ )
# First - term spherical diffraction loss
Ldft = omega * Ldft_sea + (1 - omega) * Ldft_land
return Ldft
-
@staticmethod
def dl_se(d, hte, hre, ap, f, omega):
# Wavelength in meters
@@ -611,28 +660,32 @@ def dl_se(d, hte, hre, ap, f, omega):
dlos = np.sqrt(2 * ap) * (np.sqrt(0.001 * hte) + np.sqrt(0.001 * hre))
if d >= dlos:
- #calculate diffraction loss Ldft using the method in Sec.4.2.2.1 for
+ # calculate diffraction loss Ldft using the method in Sec.4.2.2.1 for
# adft = ap and set Ldsph to Ldft
Ldsph = PropagationClearAir.dl_se_ft(d, hte, hre, ap, f, omega)
else:
- #calculate the smallest clearance between the curved - Earth path and
- #the ray between the antennas, hse
+ # calculate the smallest clearance between the curved - Earth path and
+ # the ray between the antennas, hse
c = (hte - hre) / (hte + hre)
m = 250 * d * d / (ap * (hte + hre))
- b = 2 * np.sqrt((m + 1) / (3*m)) * np.cos(np.pi / 3 + 1 / 3 * np.arccos(3*c / 2 * np.sqrt(3*m/(m+1)** 3)))
+ b = 2 * np.sqrt((m + 1) / (3 * m)) * np.cos(
+ np.pi / 3 +
+ 1 / 3 * np.arccos(3 * c / 2 * np.sqrt(3 * m / (m + 1) ** 3)),
+ )
dse1 = d / 2 * (1 + b)
dse2 = d - dse1
- hse = (hte - 500 * dse1 * dse1 / ap) * dse2 + (hre - 500 * dse2 * dse2 / ap) * dse1
+ hse = (hte - 500 * dse1 * dse1 / ap) * dse2 + \
+ (hre - 500 * dse2 * dse2 / ap) * dse1
hse = hse / d
# Calculate the required clearance for zero diffraction loss
hreq = 17.456 * np.sqrt(dse1 * dse2 * lamb / d)
if hse > hreq:
- Ldsph =np.array([0,0])
+ Ldsph = np.array([0, 0])
else:
# calculate the modified effective Earth radius aem, which gives
# marginal LoS at distance d
@@ -642,7 +695,7 @@ def dl_se(d, hte, hre, ap, f, omega):
Ldft = PropagationClearAir.dl_se_ft(d, hte, hre, aem, f, omega)
if (Ldft < 0).any():
- Ldsph =np.array([0,0])
+ Ldsph = np.array([0, 0])
else:
Ldsph = (1 - hse / hreq) * Ldft
@@ -681,7 +734,7 @@ def dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega):
return Ld
@staticmethod
- def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ):
+ def dl_p(d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN):
# Use the method in 4.2.3 to calculate diffraction loss Ld for effective
# Earth radius ap = ae as given by equation(6a). Set median diffractino
# loss to Ldp50
@@ -690,7 +743,9 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ):
ap = ae
- Ld50 = PropagationClearAir.dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega)
+ Ld50 = PropagationClearAir.dl_delta_bull(
+ d, h, hts, hrs, hstd, hsrd, ap, f, omega,
+ )
if p == 50:
Ldp = Ld50
@@ -700,7 +755,9 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ):
# not exceeded for beta0 % time Ldb = Ld
ap = ab
- Ldb = PropagationClearAir.dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega);
+ Ldb = PropagationClearAir.dl_delta_bull(
+ d, h, hts, hrs, hstd, hsrd, ap, f, omega,
+ )
# Compute the interpolation factor Fi
if p > b0:
@@ -713,47 +770,126 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ):
return Ldp, Ld50
-
- def get_loss(self, *args, **kwargs) -> np.array:
-
- d_km = np.asarray(kwargs["distance_3D"])*(1e-3) #Km
- f = np.asarray(kwargs["frequency"])*(1e-3) #GHz
- number_of_sectors = kwargs.pop("number_of_sectors",1)
- indoor_stations = kwargs.pop("indoor_stations",1)
- elevation = kwargs["elevation"]
-
- f = np.unique(f)
- if len(f) > 1:
- error_message = "different frequencies not supported in P619"
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the get_loss method to fit the Propagation ABC class interface
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ params : Parameters
+ Simulation parameters needed for the propagation class
+ frequency: float
+ Center frequency
+ station_a : StationManager
+ StationManager container representing the system station
+ station_b : StationManager
+ StationManager container representing the IMT station
+ station_a_gains: np.ndarray defaults to None
+ System antenna gains
+ station_b_gains: np.ndarray defaults to None
+ IMT antenna gains
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ distance = station_a.get_3d_distance_to(
+ station_b,
+ ) * (1e-3) # P.452 expects Kms
+ frequency_array = frequency * \
+ np.ones(distance.shape) * (1e-3) # P.452 expects GHz
+ indoor_stations = np.tile(
+ station_b.indoor, (station_a.num_stations, 1),
+ )
+ elevation = station_b.get_elevation(station_a)
+ if params.imt.interfered_with:
+ tx_gain = station_a_gains
+ rx_gain = station_b_gains
+ else:
+ tx_gain = station_b_gains
+ rx_gain = station_a_gains
+
+ return self.get_loss(
+ distance,
+ frequency_array,
+ indoor_stations,
+ elevation,
+ tx_gain,
+ rx_gain,
+ )
+
+ # pylint: disable=arguments-differ
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray)
+ def get_loss(
+ self, distance: np.ndarray, frequency: np.ndarray,
+ indoor_stations: np.ndarray, elevation: np.ndarray,
+ tx_gain: np.ndarray, rx_gain: np.ndarray,
+ ) -> np.array:
+ """Calculates the loss according to P.452
+
+ Parameters
+ ----------
+ distance : np.ndarray
+ Distance array between stations in KMs
+ frequency : np.ndarray
+ Frequency array for the links
+ indoor_stations : np.ndarray
+ Whether the rx stations are indoor
+ elevation : np.ndarray
+ elevation angle between stations
+ tx_gain: np.ndarray
+ transmitter antenna gains
+ rx_gain: np.ndarray
+ receiver antenna gains
+
+ Returns
+ -------
+ np.array
+ array of losses
+ """
+ frequency = np.unique(frequency)
+ if len(frequency) > 1:
+ error_message = "different frequencies not supported in P.452"
raise ValueError(error_message)
- es_params =kwargs["es_params"]
- Ph = np.asarray(es_params.atmospheric_pressure)
- T = np.asarray(es_params.air_temperature)
- Dct = np.asarray(es_params.Dct)
- Dcr = np.asarray(es_params.Dcr)
- Hte = np.asarray(es_params.Hte)
- Hre = np.asarray(es_params.Hre)
- N0 = np.asarray(es_params.N0)
- deltaN = np.asarray(es_params.delta_N)
- if es_params.percentage_p == 'RANDOM':
- p = 50*self.random_number_gen.rand(d_km.size)
+ # TODO: Remove unecessary assignments
+ Ph = np.asarray(self.model_params.atmospheric_pressure)
+ T = np.asarray(self.model_params.air_temperature)
+ Dct = np.asarray(self.model_params.Dct)
+ Dcr = np.asarray(self.model_params.Dcr)
+ Hte = np.asarray(self.model_params.Hte)
+ Hre = np.asarray(self.model_params.Hre)
+ N0 = np.asarray(self.model_params.N0)
+ deltaN = np.asarray(self.model_params.delta_N)
+ if self.model_params.percentage_p == 'RANDOM':
+ p = 50 * self.random_number_gen.rand(distance.size)
else:
- p = float(es_params.percentage_p)*np.ones(d_km.size)
+ p = float(self.model_params.percentage_p) * np.ones(distance.size)
- tx_lat = es_params.tx_lat
- rx_lat = es_params.rx_lat
+ tx_lat = self.model_params.tx_lat
+ rx_lat = self.model_params.rx_lat
- Gt = np.ravel(np.asarray(kwargs["tx_gain"]))
- Gr = np.ravel(np.asarray(kwargs["rx_gain"]))
+ tx_gain = np.ravel(tx_gain)
+ rx_gain = np.ravel(rx_gain)
# Modify the path according to Section 4.5.4, Step 1 and compute clutter losses
# consider no obstacles profile
profile_length = 100
- num_dists = d_km.size
+ num_dists = distance.size
d = np.empty([num_dists, profile_length])
for ii in range(num_dists):
- d[ii, :] = np.linspace(0,d_km[0][ii],profile_length)
+ d[ii, :] = np.linspace(0, distance[0][ii], profile_length)
h = np.zeros(d.shape)
@@ -776,12 +912,12 @@ def get_loss(self, *args, **kwargs) -> np.array:
for index in range(num_dists):
zone_r = 12
- dtm[index] = self.longest_cont_dist(d[index,:], zone, zone_r)
+ dtm[index] = self.longest_cont_dist(d[index, :], zone, zone_r)
zone_r = 2
- dlm[index] = self.longest_cont_dist(d[index,:], zone, zone_r)
+ dlm[index] = self.longest_cont_dist(d[index, :], zone, zone_r)
- #compute beta0
+ # compute beta0
b0 = self.beta0(phi_path, dtm, dlm)
[ae, ab] = self.earth_rad_eff(deltaN)
@@ -790,15 +926,14 @@ def get_loss(self, *args, **kwargs) -> np.array:
# Modify the path according to Section 4.5.4, Step 1 and compute clutter losses
# only if not isempty ha_t and ha_r
- #[dc, hc, zonec, htgc, hrgc, Aht, Ahr] = self.closs_corr(f, d, h, zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r)
+ # [dc, hc, zonec, htgc, hrgc, Aht, Ahr] = self.closs_corr(f, d, h, zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r)
- Lb = np.empty([1,num_dists])
+ Lb = np.empty([1, num_dists])
# Effective Earth curvature Ce(km ^ -1)
Ce = 1 / ae
# Wavelength in meters
- lamb = 0.3 / f
# Calculate an interpolation factor Fj to take account of the path angular
# distance(58)
@@ -806,14 +941,18 @@ def get_loss(self, *args, **kwargs) -> np.array:
KSI = 0.8
for ii in range(num_dists):
- [dc, hc, zonec, htg, hrg, Aht, Ahr] = self.closs_corr(f, d[ii,:], h[ii,:], zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r)
- d[ii,:] = dc
- h[ii,:] = hc
+ [dc, hc, zonec, htg, hrg, Aht, Ahr] = self.closs_corr(
+ frequency, d[ii, :], h[ii, :], zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r,
+ )
+ d[ii, :] = dc
+ h[ii, :] = hc
- [hst, hsr, hstd, hsrd, hte,hre, hm, dlt,
- dlr, theta_t, theta_r, theta, pathtype] = self.smooth_earth_heights(d[ii,:], h[ii,:], htg, hrg, ae, f)
+ [
+ hst, hsr, hstd, hsrd, hte, hre, hm, dlt,
+ dlr, theta_t, theta_r, theta, pathtype,
+ ] = self.smooth_earth_heights(d[ii, :], h[ii, :], htg, hrg, ae, frequency)
- dtot = d[ii,-1] - d[ii,0]
+ dtot = d[ii, -1] - d[ii, 0]
# Tx and Rx antenna heights above mean sea level amsl(m)
hts = hc[0] + htg
@@ -826,8 +965,8 @@ def get_loss(self, *args, **kwargs) -> np.array:
error_message = "tl_p452: path profile requires at least 4 points."
raise ValueError(error_message)
- di = d[ii,1: -1]
- hi = h[ii,1: -1]
+ di = d[ii, 1: -1]
+ hi = h[ii, 1: -1]
Stim = max((hi + 500 * Ce * di * (dtot - di) - hts) / di)
@@ -846,9 +985,13 @@ def get_loss(self, *args, **kwargs) -> np.array:
Fk = 1.0 - 0.5 * (1.0 + np.tanh(3.0 * kappa * (dtot - dsw) / dsw))
- [Lbfsg, Lb0p, Lb0b] = self.pl_los(dtot, f, p[ii], b0[ii], omega[ii], T, Ph, dlt, dlr)
+ [Lbfsg, Lb0p, Lb0b] = self.pl_los(
+ dtot, frequency, p[ii], b0[ii], omega[ii], T, Ph, dlt, dlr,
+ )
- [Ldp, Ld50] = self.dl_p(d[ii], h[ii], hts, hrs, hstd, hsrd, f, omega[ii], p[ii], b0[ii], deltaN)
+ [Ldp, Ld50] = self.dl_p(
+ d[ii], h[ii], hts, hrs, hstd, hsrd, frequency, omega[ii], p[ii], b0[ii], deltaN,
+ )
# The median basic transmission loss associated with diffraction Eq (43)
Lbd50 = Lbfsg + Ld50
@@ -868,8 +1011,11 @@ def get_loss(self, *args, **kwargs) -> np.array:
# and transhorizon signal enhancements
eta = 2.5
- Lba = self.tl_anomalous(dtot, dlt, dlr, Dct, Dcr, dlm[ii], hts, hrs, hte, hre, hm, theta_t, theta_r, f, p[ii], T, Ph,
- omega[ii], ae, b0[ii])
+ Lba = self.tl_anomalous(
+ dtot, dlt, dlr, Dct, Dcr, dlm[ii], hts, hrs, hte, hre, hm, theta_t, theta_r,
+ frequency, p[ii], T, Ph,
+ omega[ii], ae, b0[ii],
+ )
Lminbap = eta * np.log(np.exp(Lba / eta) + np.exp(Lb0p / eta))
@@ -885,36 +1031,40 @@ def get_loss(self, *args, **kwargs) -> np.array:
# Calculate the basic transmission loss due to troposcatter not exceeded
# for any time percantage p
- Lbs = self.tl_tropo(dtot, theta, f, p[ii], T, Ph, N0, Gt[ii], Gr[ii])
+ Lbs = self.tl_tropo(
+ dtot, theta, frequency,
+ p[ii], T, Ph, N0, tx_gain[ii], rx_gain[ii],
+ )
# Calculate the final transmission loss not exceeded for p % time
- Lb_pol = -5 * np.log10(10 ** (-0.2 * Lbs) + 10** (-0.2 * Lbam)) + Aht + Ahr
-
- if (es_params.polarization).lower() == "horizontal":
- Lb[0,ii] = Lb_pol[0]
- elif (es_params.polarization).lower() == "vertical":
- Lb[0,ii] = Lb_pol[1]
+ Lb_pol = -5 * np.log10(
+ 10 ** (-0.2 * Lbs) +
+ 10 ** (-0.2 * Lbam),
+ ) + Aht + Ahr
+
+ if (self.model_params.polarization).lower() == "horizontal":
+ Lb[0, ii] = Lb_pol[0]
+ elif (self.model_params.polarization).lower() == "vertical":
+ Lb[0, ii] = Lb_pol[1]
else:
error_message = "invalid polarization"
raise ValueError(error_message)
- if es_params.clutter_loss:
- clutter_loss = self.clutter.get_loss(frequency=f * 1000,
- distance=d_km * 1000,
- station_type=StationType.FSS_ES)
+ if self.model_params.clutter_loss:
+ clutter_loss = self.clutter.get_loss(
+ frequency=frequency * 1000,
+ distance=distance * 1000,
+ station_type=StationType.FSS_ES,
+ clutter_type=self.model_params.clutter_type
+ )
else:
- clutter_loss = np.zeros(d_km.shape)
+ clutter_loss = np.zeros(distance.shape)
# building_loss = self.building_loss * indoor_stations
- b_loss = np.transpose(self.building_entry.get_loss(f, elevation))
+ b_loss = np.transpose(
+ self.building_entry.get_loss(frequency, elevation),
+ )
building_loss = b_loss * indoor_stations
+ lb_new = Lb + clutter_loss + building_loss
- if number_of_sectors > 1:
- Lb = np.repeat(Lb, number_of_sectors, 1)
- clutter_loss = np.repeat(clutter_loss, number_of_sectors, 1)
- building_loss = np.repeat(building_loss, number_of_sectors, 1)
-
- Lb_new = Lb + clutter_loss + building_loss
-
- return Lb_new
-
+ return lb_new
diff --git a/sharc/propagation/propagation_clutter_loss.py b/sharc/propagation/propagation_clutter_loss.py
index e35029fce..d2c977734 100644
--- a/sharc/propagation/propagation_clutter_loss.py
+++ b/sharc/propagation/propagation_clutter_loss.py
@@ -1,25 +1,58 @@
# -*- coding: utf-8 -*-
-"""
-Created on Mon May 15 12:51:48 2017
-
-@author: LeticiaValle_Mac
-"""
from sharc.propagation.propagation import Propagation
from sharc.support.enumerations import StationType
import numpy as np
import scipy
-import math
-from scipy import special
+import matplotlib.pyplot as plt
class PropagationClutterLoss(Propagation):
+ """
+ This Recommendation (ITU_R_P_2108-1/2021) provides methods for
+ estimating loss through clutter at frequencies between 30 MHz and 100 GHz.
+
+ The ITU Radiocommunication Assembly,
+ considering
+ a) that, for system planning and interference assessment it may be
+ necessary to account for the attenuation suffered by radio waves in
+ passing over or between buildings;
+ b) that, where a terrestrial station may be shielded by buildings a
+ detailed calculation for a general case can be difficult to formulate
+ and losses due to clutter must be considered dependant on the
+ deployment scenario;
+ c) that, where terrestrial stations are in motion the clutter envir-
+ onment of the radio path will be variable,
+ recognizing
+ a) that Recommendation ITU-R P.1411 contains data and models for
+ short-range radio system, mainly within an urban environment from
+ 300 MHz to 100 GHz;
+ b) that Recommendation ITU-R P.2040 contains basic expressions for
+ reflection from and penetration through building materials, and a
+ harmonised representation of building material electrical properties
+ above about 100 MHz;
+ c) that Recommendation ITU-R P.452 contains a prediction method for
+ the evaluation of interference between stations on the surface of
+ the Earth at frequencies from about 0.1 GHz to 50 GHz, accounting
+ for both clear-air and hydrometeor scattering interference mechanisms;
+ d) that Recommendation ITU-R P.1812 describes a propagation predict-
+ ion method suitable for terrestrial point-to-area services in the
+ frequency range 30 MHz to 6 000 MHz;
+ e) that Recommendation ITU-R P.833 presents several models to enable
+ the user to evaluate the effect of vegetation on radiowave signals
+ between 30 MHz and 60 GHz;
+ f) that Recommendation ITU-R P.2109 provides a statistical model
+ for building entry loss for frequencies between about 80 MHz and 100 GHz,
+ recommends
+ that the material in Recomendation ITU-R P.2108-1/2021 be used to
+ estimate clutter loss.
+ """
def get_loss(self, *args, **kwargs) -> np.array:
"""
- Calculates clutter loss.
+ Calculates clutter loss according to Recommendation P.2108-0
Parameters
----------
@@ -39,105 +72,232 @@ def get_loss(self, *args, **kwargs) -> np.array:
"""
f = kwargs["frequency"]
- loc_per = kwargs.pop("loc_percentage","RANDOM")
+ loc_per = kwargs.pop("loc_percentage", "RANDOM")
type = kwargs["station_type"]
-
d = kwargs["distance"]
if f.size == 1:
f = f * np.ones(d.shape)
if isinstance(loc_per, str) and loc_per.upper() == "RANDOM":
- p = self.random_number_gen.random_sample(d.shape)
+ p1 = self.random_number_gen.random_sample(d.shape)
+ p2 = self.random_number_gen.random_sample(d.shape)
else:
- p = loc_per*np.ones(d.shape)
+ p1 = loc_per * np.ones(d.shape)
+ p2 = loc_per * np.ones(d.shape)
if type is StationType.IMT_BS or type is StationType.IMT_UE or type is StationType.FSS_ES:
- loss = self.get_terrestrial_clutter_loss(f, d, p)
+ clutter_type = kwargs["clutter_type"]
+ if clutter_type == 'one_end':
+ loss = self.get_terrestrial_clutter_loss(f, d, p1, True)
+ else:
+ loss = self.get_terrestrial_clutter_loss(f, d, p1, True) + self.get_terrestrial_clutter_loss(f, d, p2, False)
else:
theta = kwargs["elevation"]
- loss = self.get_spacial_clutter_loss(f, theta, p)
+ earth_station_height = kwargs["earth_station_height"]
+ mean_clutter_height = kwargs["mean_clutter_height"]
+ below_rooftop = kwargs["below_rooftop"]
+ loss = self.get_spacial_clutter_loss(f, theta, p1, earth_station_height, mean_clutter_height)
+ mult_1 = np.zeros(d.shape)
+ num_ones = int(np.round(mult_1.size * below_rooftop / 100))
+ indices = np.random.choice(mult_1.size, size=num_ones, replace=False)
+ mult_1.flat[indices] = 1
+ loss *= mult_1
return loss
- def get_spacial_clutter_loss(self, frequency : float,
- elevation_angle : float,
- loc_percentage):
+ def get_spacial_clutter_loss(
+ self, frequency: float,
+ elevation_angle: float,
+ loc_percentage,
+ earth_station_height,
+ mean_clutter_height
+ ):
"""
- This method models the calculation of the statistical distribution of
- clutter loss where one end of the interference path is within man-made
- clutter, and the other is a satellite, aeroplane, or other platform
- above the surface of the Earth. This model is applicable to urban and
- suburban environments.
+ Computes clutter loss according to ITU-R P.2108-2 ยง3.3 for Earth-space and aeronautical paths.
- Parameters
- ----
- frequency : center frequency [MHz]
- elevation_angle : elevation angle [degrees]
- loc_percentage : percentage of locations [0,1[
+ Parameters:
+ - frequency: Frequency (GHz), 0.5 <= frequency <= 100
+ - elevation_angle: Elevation angle (degrees), 0 <= elevation_angle <= 90
+ - loc_percentage: Percentage of locations (%), 0 < loc_percentage < 100
+ - earth_station_height: Ground station height (m), >= 1
+ - mean_clutter_height: Median clutter height (m): Low-rise: <= 8; Mid-rise: 8 < ... <= 20; High-rise: > 20
- Returns
- -------
- loss : The clutter loss not exceeded for p% of locations for the
- terrestrial to terrestrial path
+ Returns:
+ - Lces: Clutter loss according to P.2108 ยง3.3 (dB)
"""
- k1 = 93*(frequency*1e-3)**0.175
- A1 = 0.05
-
- y = np.sin(A1*(1 - (elevation_angle/90)) + math.pi*(elevation_angle/180))
- y1 = np.cos(A1*(1 - (elevation_angle/90)) + math.pi*(elevation_angle/180))
-
- cot = (y1/y)
- Q = np.sqrt(2)*scipy.special.erfcinv(2*loc_percentage)
- loss = (-k1*(np.log(1 - loc_percentage))*cot)**(0.5*(90 - elevation_angle)/90) - 1 - 0.6*Q
-
- return loss
-
-
- def get_terrestrial_clutter_loss(self,
- frequency: float,
- distance: float,
- loc_percentage: float,
- apply_both_ends = True):
+ # Converting to GHz
+ frequency = frequency / 1000
+ # --- Table 7: plos parameters ---
+ if mean_clutter_height == "Low":
+ ak, bk, ck = 4.9, 6.7, 2.6
+ aC, bC = 0.19, 0
+ aV, bV = 1.4, 74
+ elif mean_clutter_height == "Mid":
+ ak, bk, ck = -2.6, 6.6, 2.0
+ aC, bC = 0.42, -6.7
+ aV, bV = 0.15, 97
+ else:
+ ak, bk, ck = 2.4, 7.0, 1.0
+ aC, bC = 0.19, -2.7
+ aV, bV = 0.15, 98
+
+ # --- Table 8: p_fclo_los parameters ---
+ if mean_clutter_height == "Low":
+ akp, bkp = 6.0, 0.07
+ aCp, bC1p, bC2p = 0.15, 5.4, -0.3
+ bC3p, bC4p = 3.2, 0.07
+ cCp, aVp, bVp = -27, 1.6, -17
+ elif mean_clutter_height == "Mid":
+ akp, bkp = 3.6, 0.05
+ aCp, bC1p, bC2p = 0.17, 13, -0.2
+ bC3p, bC4p = 3.7, 0.05
+ cCp, aVp, bVp = -41, 1, -21
+ else:
+ akp, bkp = 5.0, 0.003
+ aCp, bC1p, bC2p = 0.17, 32.6, 0.012
+ bC3p, bC4p = -23.9, -0.07
+ cCp, aVp, bVp = -41, 1, -18
+
+ # --- LoS probability (eqs. 7-8) ---
+ Vmax = np.minimum(aV * earth_station_height + bV, 100)
+ Ce = aC * earth_station_height + bC
+ k = ((earth_station_height + ak) / bk) ** ck
+ num = 1 - np.exp(-k * (elevation_angle + Ce) / 90)
+ den = 1 - np.exp(-k * (90 + Ce) / 90)
+ pLoS = np.maximum(0, Vmax * (num / den))
+
+ # --- Conditional probability of Fresnel clearance (eqs. 9-10) ---
+ Vmaxp = np.minimum(aVp * earth_station_height + bVp, 0) * frequency ** (-0.55) + 100
+ Cep = (frequency * 1e9) ** aCp + bC1p * np.exp(bC2p * earth_station_height) + bC3p * np.exp(bC4p *
+ earth_station_height) + cCp
+ kp = akp * np.exp(bkp * earth_station_height)
+ nump = 1 - np.exp(-kp * (elevation_angle + Cep) / 90)
+ denp = 1 - np.exp(-kp * (90 + Cep) / 90)
+ pFcLoS_LoS = np.maximum(0, Vmaxp * (nump / denp))
+ pFcLoS = pLoS * pFcLoS_LoS / 100
+
+ # Table 9 constants
+ alpha1, alpha2 = 8.54, 0.056
+ beta1, beta2 = 17.57, 6.32
+ gamma1, gamma2 = 0.63, 0.19
+
+ mu = alpha1 + beta1 * np.log(1 + (90 - elevation_angle) / 90) + frequency ** gamma1
+ sigma = alpha2 + beta2 * np.log(1 + (90 - elevation_angle) / 90) + frequency ** gamma2
+
+ # Table 10 constants
+ theta1, theta2 = -3.542, -155.1
+ sigma1, sigma2 = -1.06, -6.342
+
+ # Prepare output array
+ Lces = np.zeros_like(frequency, dtype=float)
+
+ # Branch logic: mask arrays for each regime
+ mask_nlos = loc_percentage > pLoS
+ mask_fclo = loc_percentage <= pFcLoS
+ mask_between = (~mask_nlos) & (~mask_fclo)
+
+ # NLoS branch
+ if np.any(mask_nlos):
+ pp = (loc_percentage[mask_nlos] - pLoS[mask_nlos]) / (100 - pLoS[mask_nlos])
+ Finv = -np.sqrt(2) * scipy.special.erfcinv(2 * pp)
+ Lces_nlos = mu[mask_nlos] + sigma[mask_nlos] * Finv
+ Lces[mask_nlos] = np.maximum(Lces_nlos, 6)
+
+ # Fresnel-clear branch
+ if np.any(mask_fclo):
+ ratio = loc_percentage[mask_fclo] / pFcLoS[mask_fclo]
+ Lces[mask_fclo] = (theta1 * np.exp(theta2 * ratio) + sigma1 * np.exp(sigma2 * ratio))
+
+ # Between
+ if np.any(mask_between):
+ Lces[mask_between] = (
+ 6 * (loc_percentage[mask_between] - pFcLoS[mask_between]) /
+ (pLoS[mask_between] - pFcLoS[mask_between])
+ )
+
+ return Lces
+
+ def get_terrestrial_clutter_loss(
+ self,
+ frequency: float,
+ distance: float,
+ loc_percentage: float,
+ apply_both_ends=True,
+ ):
"""
- This method gives models the statistical distribution of clutter loss.
- The model can be applied for urban and suburban clutter loss modelling.
+ This method gives a statistical distribution of clutter loss. The model
+ can be applied for urban and suburban clutter loss modelling. An
+ additional "loss" is calculated which can be added to the transmission
+ loss or basic transmission loss. Clutter loss will vary depending on
+ clutter type, location within the clutter and movement in the clutter.
+ If the transmission loss or basic transmission loss has been calculated
+ using a model (e.g. Recommendation ITU-R P.1411) that inherently
+ accounts for clutter over the entire path then the method below should
+ not be applied. The clutter loss not exceeded for loc_percentage% of
+ locations for the terrestrial to terrestrial path, "loss"", is given
+ by this method. The clutter loss must not exceed a maximum value
+ calculated for ๐ = 2 ๐m (loss_2km)
+.
Parameters
----
- frequency : center frequency [MHz]
- distance : distance [m]
- loc_percentage : percentage of locations [0,1]
- apply_both_ends : if correction will be applied at both ends of the path
+ frequency : center frequency [MHz] - Frequency range: 0.5 to 67 GHz
+ distance : distance [m] - Minimum path length: 0.25 km (for the
+ correction to be applied at only one end of the path)
+ 1.0 km (for the
+ correction to be applied at both ends of the path)
+ loc_percentage : percentage of locations [0,1] - Percentage
+ locations range: 0 < p < 100
+
+ apply_both_ends : if correction will be applied at both ends of the
+ path
Returns
-------
- loss : The clutter loss not exceeded for p% of locations for the
- terrestrial to terrestrial path
+ loss : The clutter loss not exceeded for loc_percentage% of
+ locations for the terrestrial to terrestrial path
"""
- d = distance.reshape((-1, 1))
+ d = distance.copy()
+ d = d.reshape((-1, 1))
f = frequency.reshape((-1, 1))
p = loc_percentage.reshape((-1, 1))
- loss = np.zeros(d.shape)
-
- # minimum path length for the correction to be applied at only one end of the path
- id_1 = np.where(d >= 250)[0]
-
- if len(id_1):
- Lt = 23.5 + 9.6 * np.log10(f[id_1] * 1e-3)
- Ls = 32.98 + 23.9 * np.log10(d[id_1] * 1e-3) + 3 * np.log10(f[id_1] * 1e-3)
- Q = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_1]))
- loss[id_1] = -5 * np.log10(10 ** (-0.2 * Lt) + 10 ** (-0.2 * Ls)) - 6 * Q
+ sigma_l = 4.0
+ sigma_s = 6.0
- # minimum path length for the correction to be applied at only one end of the path
- id_2 = np.where(d >= 1000)[0]
+ loss = np.zeros(d.shape)
+ loss_2km = np.zeros(d.shape)
- if apply_both_ends and len(id_2):
- Lt = 23.5 + 9.6 * np.log10(f[id_2] * 1e-3)
- Ls = 32.98 + 23.9 * np.log10(d[id_2] * 1e-3) + 3 * np.log10(f[id_2] * 1e-3)
- Q = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_2]))
- loss[id_2] = loss[id_2] + (-5 * np.log10(10 ** (-0.2 * Lt) + 10 ** (-0.2 * Ls)) - 6 * Q)
+ if apply_both_ends:
+ # minimum path length for the correction to be applied at only one end of the path
+ id_d = np.where(d >= 1000)[0]
+ else:
+ # minimum path length for the correction to be applied at both ends of the path
+ id_d = np.where(d >= 250)[0]
+
+ if len(id_d):
+ Ll = -2.0 * \
+ np.log10(
+ 10 ** (-5.0 * np.log10(f[id_d] * 1e-3) - 12.5) + 10 ** (-16.5))
+ Ls_temp = 32.98 + 3.0 * np.log10(f[id_d] * 1e-3)
+ Ls = 23.9 * np.log10(d[id_d] * 1e-3) + Ls_temp
+ invQ = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_d]))
+ sigma_cb = np.sqrt(((sigma_l**(2.0)) * (10.0**(-0.2 * Ll)) + (sigma_s**(2.0)) *
+ (10.0**(-0.2 * Ls))) / (10.0**(-0.2 * Ll) + 10.0**(-0.2 * Ls)))
+ loss[id_d] = -5.0 * \
+ np.log10(10 ** (-0.2 * Ll) + 10 **
+ (-0.2 * Ls)) - sigma_cb * invQ
+
+ # The clutter loss must not exceed a maximum value calculated for ๐ = 2 ๐m (loss_2km)
+ Ls_2km = 23.9 * np.log10(2) + Ls_temp
+ sigma_cb_2km = np.sqrt(((sigma_l**(2.0)) * (10.0**(-0.2 * Ll)) + (sigma_s**(2.0)) *
+ (10.0**(-0.2 * Ls_2km))) / (10.0**(-0.2 * Ll) + 10.0**(-0.2 * Ls_2km)))
+ loss_2km[id_d] = -5.0 * \
+ np.log10(10 ** (-0.2 * Ll) + 10 ** (-0.2 * Ls_2km)) - \
+ sigma_cb_2km * invQ
+ id_max = np.where(loss >= loss_2km)[0]
+ loss[id_max] = loss_2km[id_max]
loss = loss.reshape(distance.shape)
@@ -145,32 +305,36 @@ def get_terrestrial_clutter_loss(self,
if __name__ == '__main__':
- import matplotlib.pyplot as plt
elevation_angle = np.array([90, 80, 70, 60, 50, 40, 30, 20, 15, 10, 5, 0])
- loc_percentage = np.linspace(0.01, 0.99, 1000)
- frequency = 27250 * np.ones(elevation_angle.shape)
-
+ loc_percentage = np.linspace(0.1, 99.9, 1001)
+ frequency = 3000 * np.ones(loc_percentage.shape)
+ earth_station_height = 5 * np.ones(loc_percentage.shape)
random_number_gen = np.random.RandomState(101)
cl = PropagationClutterLoss(random_number_gen)
clutter_loss = np.empty([len(elevation_angle), len(loc_percentage)])
- for i in range(len(loc_percentage)):
- clutter_loss[:, i] = cl.get_spacial_clutter_loss(frequency,
- elevation_angle,
- loc_percentage[i])
+ for i in range(len(elevation_angle)):
+ clutter_loss[i, :] = cl.get_spacial_clutter_loss(
+ frequency,
+ elevation_angle[i],
+ loc_percentage,
+ earth_station_height,
+ 'Low',
+ )
fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
for j in range(len(elevation_angle)):
- ax.plot(clutter_loss[j, :], 100 * loc_percentage, label="%i deg" % elevation_angle[j], linewidth=1)
+ ax.plot(clutter_loss[j, :], loc_percentage,
+ label="%i deg" % elevation_angle[j], linewidth=1)
- plt.title("Cumulative distribution of clutter loss not exceeded for 27 GHz")
+ plt.title("Cumulative distribution of clutter loss not exceeded for 30 GHz")
plt.xlabel("clutter loss [dB]")
plt.ylabel("percent of locations [%]")
- plt.xlim((-10, 70))
+ plt.xlim((-5, 30))
plt.ylim((0, 100))
plt.legend(loc="lower right")
plt.tight_layout()
@@ -178,7 +342,7 @@ def get_terrestrial_clutter_loss(self,
plt.show()
distance = np.linspace(250, 100000, 100000)
- frequency = np.array([2, 3, 6, 16, 40, 67]) * 1e3
+ frequency = np.array([1, 2, 4, 8, 16, 32, 67]) * 1e3
loc_percentage = 0.5 * np.ones(distance.shape)
apply_both_ends = False
@@ -186,10 +350,12 @@ def get_terrestrial_clutter_loss(self,
clutter_loss_ter = np.empty([len(frequency), len(distance)])
for i in range(len(frequency)):
- clutter_loss_ter[i, :] = cl.get_terrestrial_clutter_loss(frequency[i] * np.ones(distance.shape),
- distance,
- loc_percentage,
- apply_both_ends)
+ clutter_loss_ter[i, :] = cl.get_terrestrial_clutter_loss(
+ frequency[i] * np.ones(distance.shape),
+ distance,
+ loc_percentage,
+ apply_both_ends,
+ )
fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
@@ -197,8 +363,14 @@ def get_terrestrial_clutter_loss(self,
for j in range(len(frequency)):
freq = frequency[j] * 1e-3
- ax.semilogx(distance * 1e-3, clutter_loss_ter[j, :], label="%i GHz" % freq, linewidth=1)
+ ax.semilogx(distance * 1e-3,
+ clutter_loss_ter[j, :], label="%i GHz" % freq, linewidth=1)
plt.title("Median clutter loss for terrestrial paths")
plt.xlabel("Distance [km]")
+ plt.xlim((0.1, 100.0))
+ plt.ylim((15.0, 70.0))
+ plt.legend(loc="lower right")
+ plt.tight_layout()
+ plt.grid()
plt.show()
diff --git a/sharc/propagation/propagation_factory.py b/sharc/propagation/propagation_factory.py
index 1a37bf575..f2af72cd5 100644
--- a/sharc/propagation/propagation_factory.py
+++ b/sharc/propagation/propagation_factory.py
@@ -7,7 +7,8 @@
import sys
import numpy.random as rnd
-
+from sharc.parameters.parameters_base import ParametersBase
+from sharc.parameters.imt.parameters_imt import ParametersImt
from sharc.parameters.parameters import Parameters
from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_free_space import PropagationFreeSpace
@@ -22,10 +23,39 @@
from sharc.propagation.propagation_indoor import PropagationIndoor
from sharc.propagation.propagation_hdfss import PropagationHDFSS
+
class PropagationFactory(object):
@staticmethod
- def create_propagation(channel_model: str, param: Parameters, random_number_gen: rnd.RandomState) -> Propagation:
+ def create_propagation(
+ channel_model: str,
+ param: Parameters,
+ param_system: ParametersBase,
+ random_number_gen: rnd.RandomState,
+ ) -> Propagation:
+ """Creates a propagation model object
+
+ Parameters
+ ----------
+ channel_model : str
+ The channel model
+ param : Parameters
+ The simulation paramters.
+ param_system : ParametersBase
+ Specific system paramters. It can be either ParametersIMT or other system parameters.
+ random_number_gen : rnd.RandomState
+ Random number generator
+
+ Returns
+ -------
+ Propagation
+ Propagation object
+
+ Raises
+ ------
+ ValueError
+ Raises ValueError if the channel model is not implemented.
+ """
if channel_model == "FSPL":
return PropagationFreeSpace(random_number_gen)
elif channel_model == "ABG":
@@ -39,18 +69,47 @@ def create_propagation(channel_model: str, param: Parameters, random_number_gen:
elif channel_model == "TerrestrialSimple":
return PropagationTerSimple(random_number_gen)
elif channel_model == "P619":
- return PropagationP619(random_number_gen)
+ if isinstance(param_system, ParametersImt):
+ if param_system.topology.type != "NTN":
+ raise ValueError(
+ f"PropagationFactory: Channel model P.619 is invalid for topolgy {param.imt.topology.type}",
+ )
+ else:
+ # P.619 model is used only for space-to-earth links
+ if param.imt.topology.type != "NTN" and not param_system.is_space_to_earth:
+ raise ValueError((
+ "PropagationFactory: Channel model P.619 is invalid"
+ f"for system {param.general.system} and IMT "
+ f"topology {param.imt.topology.type}"
+ ))
+ return PropagationP619(
+ random_number_gen=random_number_gen,
+ space_station_alt_m=param_system.param_p619.space_station_alt_m,
+ earth_station_alt_m=param_system.param_p619.earth_station_alt_m,
+ earth_station_lat_deg=param_system.param_p619.earth_station_lat_deg,
+ earth_station_long_diff_deg=param_system.param_p619.earth_station_lat_deg,
+ season=param_system.season,
+ mean_clutter_height=param_system.param_p619.mean_clutter_height,
+ below_rooftop=param_system.param_p619.below_rooftop
+ )
elif channel_model == "P452":
- return PropagationClearAir(random_number_gen)
+ return PropagationClearAir(random_number_gen, param_system.param_p452)
elif channel_model == "TVRO-URBAN":
return PropagationTvro(random_number_gen, "URBAN")
elif channel_model == "TVRO-SUBURBAN":
return PropagationTvro(random_number_gen, "SUBURBAN")
elif channel_model == "HDFSS":
- return PropagationHDFSS(param.fss_es,random_number_gen)
+ if param.general.system == "FSS_ES":
+ # TODO: use param_hdfss in fss_es as well
+ return PropagationHDFSS(param.fss_es, random_number_gen)
+ else:
+ return PropagationHDFSS(param_system.param_hdfss, random_number_gen)
elif channel_model == "INDOOR":
- return PropagationIndoor(random_number_gen, param.indoor,
- param.imt.ue_k*param.imt.ue_k_m)
+ return PropagationIndoor(
+ random_number_gen,
+ param.imt.topology.indoor,
+ param.imt.ue.k * param.imt.ue.k_m,
+ )
else:
sys.stderr.write("ERROR\nInvalid channel_model: " + channel_model)
sys.exit(1)
diff --git a/sharc/propagation/propagation_free_space.py b/sharc/propagation/propagation_free_space.py
index 2ab3557f1..408b53b61 100644
--- a/sharc/propagation/propagation_free_space.py
+++ b/sharc/propagation/propagation_free_space.py
@@ -4,29 +4,71 @@
@author: edgar
"""
+import numpy as np
+from multipledispatch import dispatch
from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
-import numpy as np
class PropagationFreeSpace(Propagation):
"""
Implements the Free Space propagation model.
- Frequency in MHz and distance in meters
+
+ Frequency in MHz and distance are in meters
"""
- def get_loss(self, *args, **kwargs) -> np.array:
- if "distance_2D" in kwargs:
- d = kwargs["distance_2D"]
- else:
- d = kwargs["distance_3D"]
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper for the calculation loss between station_a and station_b.
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing station_a
+ station_b : StationManager
+ StationManager container representing station_a
+ params : Parameters
+ Simulation parameters needed for the propagation class.
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ distance_3d = station_a.get_3d_distance_to(station_b)
+ loss = self.get_free_space_loss(frequency=frequency, distance=distance_3d)
+
+ return loss
- f = kwargs["frequency"]
- number_of_sectors = kwargs.pop("number_of_sectors",1)
+ @dispatch(np.ndarray, np.ndarray)
+ def get_loss(self, distance_3D: np.array, frequency: float) -> np.array:
+ return self.get_free_space_loss(np.unique(frequency), distance_3D)
- loss = 20*np.log10(d) + 20*np.log10(f) - 27.55
+ def get_free_space_loss(self, frequency: float, distance: np.array) -> np.array:
+ """Calculates the free-space loss for the given distance and frequency
- if number_of_sectors > 1:
- loss = np.repeat(loss, number_of_sectors, 1)
+ Parameters
+ ----------
+ distance : float
+ 3D distance array between stations
+ frequency : float
+ wave frequency
+ Returns
+ -------
+ np.array
+ returns the path loss array with shape distance.shape
+ """
+ loss = 20 * np.log10(distance) + 20 * np.log10(frequency) - 27.55
return loss
diff --git a/sharc/propagation/propagation_hdfss.py b/sharc/propagation/propagation_hdfss.py
index 557513bad..48a70e385 100644
--- a/sharc/propagation/propagation_hdfss.py
+++ b/sharc/propagation/propagation_hdfss.py
@@ -10,57 +10,78 @@
from sharc.parameters.parameters_fss_es import ParametersFssEs
from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
from sharc.propagation.propagation_hdfss_roof_top import PropagationHDFSSRoofTop
from sharc.propagation.propagation_hdfss_building_side import PropagationHDFSSBuildingSide
+
class PropagationHDFSS(Propagation):
"""
-
+ High-Density Fixed Satellite System Propagation Model
+ This is a compoposition of HDFSS Rooftop and Indoor models using during simulation run-time.
"""
+
def __init__(self, param: ParametersFssEs, rnd_num_gen: np.random.RandomState):
- """
-
- """
super().__init__(rnd_num_gen)
-
+
if param.es_position == "ROOFTOP":
- self.propagation = PropagationHDFSSRoofTop(param,rnd_num_gen)
+ self.propagation = PropagationHDFSSRoofTop(param, rnd_num_gen)
elif param.es_position == "BUILDINGSIDE":
- self.propagation = PropagationHDFSSBuildingSide(param,rnd_num_gen)
+ self.propagation = PropagationHDFSSBuildingSide(param, rnd_num_gen)
else:
- sys.stderr.write("ERROR\nInvalid es_position: " + param.es_position)
+ sys.stderr.write(
+ "ERROR\nInvalid es_position: " + param.es_position,
+ )
sys.exit(1)
-
- def get_loss(self, *args, **kwargs) -> np.array:
- """
-
+
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the get_loss method to fit the Propagation ABC class interface
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ params : Parameters
+ Simulation parameters needed for the propagation class
+ frequency: float
+ Center frequency
+ station_a : StationManager
+ StationManager container representing the system station
+ station_b : StationManager
+ StationManager container representing the IMT station
+ station_a_gains: np.ndarray defaults to None
+ System antenna gains
+ station_b_gains: np.ndarray defaults to None
+ IMT antenna gains
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
"""
- if "distance_3D" in kwargs:
- d = kwargs["distance_3D"]
- else:
- d = kwargs["distance_2D"]
-
- ele = kwargs["elevation"]
- sta_type = kwargs["imt_sta_type"]
- f = kwargs["frequency"]
- num_sec = kwargs.pop("number_of_sectors",1)
-
- i_x = kwargs['imt_x']
- i_y = kwargs['imt_y']
- i_z = kwargs['imt_z']
- e_x = kwargs["es_x"]
- e_y = kwargs["es_y"]
- e_z = kwargs["es_z"]
-
- return self.propagation.get_loss(distance_3D = d,
- elevation = ele,
- imt_sta_type = sta_type,
- frequency = f,
- number_of_sectors = num_sec,
- imt_x = i_x,
- imt_y = i_y,
- imt_z = i_z,
- es_x = e_x,
- es_y = e_y,
- es_z = e_z)
-
\ No newline at end of file
+ distance = station_a.get_3d_distance_to(station_b) # P.452 expects Kms
+ frequency_array = frequency * \
+ np.ones(distance.shape) # P.452 expects GHz
+ elevation = station_b.get_elevation(station_a)
+
+ return self.propagation.get_loss(
+ distance_3D=distance,
+ elevation=elevation,
+ imt_sta_type=station_b.station_type,
+ frequency=frequency_array,
+ imt_x=station_b.x,
+ imt_y=station_b.y,
+ imt_z=station_b.height,
+ es_x=station_a.x,
+ es_y=station_a.y,
+ es_z=station_a.height,
+ )
diff --git a/sharc/propagation/propagation_hdfss_building_side.py b/sharc/propagation/propagation_hdfss_building_side.py
index c80da936c..0abba8648 100644
--- a/sharc/propagation/propagation_hdfss_building_side.py
+++ b/sharc/propagation/propagation_hdfss_building_side.py
@@ -8,165 +8,200 @@
import numpy as np
import sys
-from shapely.geometry import LineString, Polygon, Point
-from sharc.parameters.parameters_fss_es import ParametersFssEs
from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_p1411 import PropagationP1411
from sharc.propagation.propagation_free_space import PropagationFreeSpace
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
from sharc.support.enumerations import StationType
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
+
class PropagationHDFSSBuildingSide(Propagation):
"""
-
+
"""
- def __init__(self, param: ParametersFssEs, random_number_gen: np.random.RandomState):
+
+ def __init__(self, param: ParametersHDFSS, random_number_gen: np.random.RandomState):
super().__init__(random_number_gen)
-
+
self.param = param
-
+
# Building dimentions
self.b_w = 120
self.b_d = 50
self.b_h = 3
self.s_w = 30
self.b_tol = 0.05
-
+
self.HIGH_LOSS = 4000
self.LOSS_PER_FLOOR = 50
-
+
self.propagation_fspl = PropagationFreeSpace(random_number_gen)
- self.propagation_p1411 = PropagationP1411(random_number_gen,
- above_clutter = False)
- self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen)
-
+ self.propagation_p1411 = PropagationP1411(
+ random_number_gen,
+ above_clutter=False,
+ )
+ self.building_entry = PropagationBuildingEntryLoss(
+ self.random_number_gen,
+ )
+
def get_loss(self, *args, **kwargs) -> np.array:
"""
-
+
"""
# Parse entries
if "distance_3D" in kwargs:
d = kwargs["distance_3D"]
else:
d = kwargs["distance_2D"]
-
+
elevation = np.transpose(kwargs["elevation"])
imt_sta_type = kwargs["imt_sta_type"]
f = kwargs["frequency"]
- number_of_sectors = kwargs.pop("number_of_sectors",1)
-
+ number_of_sectors = kwargs.pop("number_of_sectors", 1)
+
imt_x = kwargs['imt_x']
imt_y = kwargs['imt_y']
- imt_z = kwargs['imt_z']
es_x = kwargs["es_x"]
es_y = kwargs["es_y"]
- es_z = kwargs["es_z"]
-
+
# Define which stations are on the same building
- same_build = self.is_same_building(imt_x,imt_y,
- es_x, es_y)
+ same_build = self.is_same_building(
+ imt_x, imt_y,
+ es_x, es_y,
+ )
not_same_build = np.logical_not(same_build)
-
+
# Define which stations are on the building in front
- next_build = self.is_next_building(imt_x,imt_y,
- es_x, es_y)
+ next_build = self.is_next_building(
+ imt_x, imt_y,
+ es_x, es_y,
+ )
not_next_build = np.logical_not(next_build)
-
+
# Define which stations are in other buildings
- other_build = np.logical_and(not_same_build,not_next_build)
-
+ other_build = np.logical_and(not_same_build, not_next_build)
+
# Path loss
loss = np.zeros_like(d)
-
+
# # Use a loss per floor
# loss[:,same_build] += self.get_same_build_loss(imt_z[same_build],
# es_z)
if not self.param.same_building_enabled:
- loss[:,same_build] += self.HIGH_LOSS
- loss[:,same_build] += self.propagation_fspl.get_loss(distance_3D=d[:,same_build],
- frequency=f[:,same_build])
-
- loss[:,next_build] += self.propagation_p1411.get_loss(distance_3D=d[:,next_build],
- frequency=f[:,next_build],
- los=True,
- shadow=self.param.shadow_enabled)
-
- loss[:,other_build] += self.propagation_p1411.get_loss(distance_3D=d[:,other_build],
- frequency=f[:,other_build],
- los=False,
- shadow=self.param.shadow_enabled)
-
+ loss[:, same_build] += self.HIGH_LOSS
+ loss[:, same_build] += self.propagation_fspl.get_loss(
+ d[:, same_build],
+ f[:, same_build],
+ )
+
+ loss[:, next_build] += self.propagation_p1411.get_loss(
+ distance_3D=d[:, next_build],
+ frequency=f[
+ :,
+ next_build,
+ ],
+ los=True,
+ shadow=self.param.shadow_enabled,
+ )
+
+ loss[:, other_build] += self.propagation_p1411.get_loss(
+ distance_3D=d[:, other_build],
+ frequency=f[
+ :,
+ other_build,
+ ],
+ los=False,
+ shadow=self.param.shadow_enabled,
+ )
+
# Building entry loss
if self.param.building_loss_enabled:
- build_loss = self.get_building_loss(imt_sta_type,
- f,
- elevation)
+ build_loss = self.get_building_loss(imt_sta_type, f, elevation)
else:
build_loss = 0.0
-
+
# Diffraction loss
diff_loss = np.zeros_like(loss)
-
+
# Compute final loss
loss = loss + build_loss + diff_loss
-
+
if number_of_sectors > 1:
loss = np.repeat(loss, number_of_sectors, 1)
-
+
return loss, build_loss, diff_loss
-
-
- def get_building_loss(self,imt_sta_type,f,elevation):
+
+ def get_building_loss(self, imt_sta_type, f, elevation):
if imt_sta_type is StationType.IMT_UE:
build_loss = self.building_entry.get_loss(f, elevation)
elif imt_sta_type is StationType.IMT_BS:
if self.param.bs_building_entry_loss_type == 'P2109_RANDOM':
build_loss = self.building_entry.get_loss(f, elevation)
elif self.param.bs_building_entry_loss_type == 'P2109_FIXED':
- build_loss = self.building_entry.get_loss(f, elevation, prob=self.param.bs_building_entry_loss_prob)
+ build_loss = self.building_entry.get_loss(
+ f, elevation, prob=self.param.bs_building_entry_loss_prob,
+ )
elif self.param.bs_building_entry_loss_type == 'FIXED_VALUE':
build_loss = self.param.bs_building_entry_loss_value
else:
- sys.stderr.write("ERROR\nBuilding entry loss type: " +
- self.param.bs_building_entry_loss_type)
+ sys.stderr.write(
+ "ERROR\nBuilding entry loss type: " +
+ self.param.bs_building_entry_loss_type,
+ )
sys.exit(1)
-
+
return build_loss
-
- def is_same_building(self,imt_x,imt_y, es_x, es_y):
-
- building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2])
- building_y_range = (es_y - self.b_d/2) + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2])
-
- is_in_x = np.logical_and(imt_x >= building_x_range[0],imt_x <= building_x_range[1])
- is_in_y = np.logical_and(imt_y >= building_y_range[0],imt_y <= building_y_range[1])
-
- is_in_building = np.logical_and(is_in_x,is_in_y)
-
+
+ def is_same_building(self, imt_x, imt_y, es_x, es_y):
+
+ building_x_range = es_x + (1 + self.b_tol) * \
+ np.array([-self.b_w / 2, +self.b_w / 2])
+ building_y_range = (es_y - self.b_d / 2) + \
+ (1 + self.b_tol) * np.array([-self.b_d / 2, +self.b_d / 2])
+
+ is_in_x = np.logical_and(
+ imt_x >= building_x_range[0], imt_x <= building_x_range[1],
+ )
+ is_in_y = np.logical_and(
+ imt_y >= building_y_range[0], imt_y <= building_y_range[1],
+ )
+
+ is_in_building = np.logical_and(is_in_x, is_in_y)
+
return is_in_building
-
- def get_same_build_loss(self,imt_z,es_z):
+
+ def get_same_build_loss(self, imt_z, es_z):
floor_number = imt_z - es_z
- floor_number[floor_number >= 0] = np.floor(floor_number[floor_number >= 0]/self.b_h)
- floor_number[floor_number < 0] = np.ceil(floor_number[floor_number < 0]/self.b_h)
-
- loss = self.LOSS_PER_FLOOR*floor_number
-
+ floor_number[floor_number >= 0] = np.floor(
+ floor_number[floor_number >= 0] / self.b_h,
+ )
+ floor_number[floor_number < 0] = np.ceil(
+ floor_number[floor_number < 0] / self.b_h,
+ )
+
+ loss = self.LOSS_PER_FLOOR * floor_number
+
return loss
-
- def is_next_building(self,imt_x,imt_y, es_x, es_y):
- same_building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2])
- same_building_y_range = (es_y - self.b_d/2) + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2])
-
+
+ def is_next_building(self, imt_x, imt_y, es_x, es_y):
+ same_building_x_range = es_x + \
+ (1 + self.b_tol) * np.array([-self.b_w / 2, +self.b_w / 2])
+ same_building_y_range = (
+ es_y - self.b_d / 2
+ ) + (1 + self.b_tol) * np.array([-self.b_d / 2, +self.b_d / 2])
+
next_building_x_range = same_building_x_range
next_building_y_range = same_building_y_range + self.b_d + self.s_w
-
- is_in_x = np.logical_and(imt_x >= next_building_x_range[0],imt_x <= next_building_x_range[1])
- is_in_y = np.logical_and(imt_y >= next_building_y_range[0],imt_y <= next_building_y_range[1])
-
- is_in_next_building = np.logical_and(is_in_x,is_in_y)
-
+
+ is_in_x = np.logical_and(
+ imt_x >= next_building_x_range[0], imt_x <= next_building_x_range[1],
+ )
+ is_in_y = np.logical_and(
+ imt_y >= next_building_y_range[0], imt_y <= next_building_y_range[1],
+ )
+
+ is_in_next_building = np.logical_and(is_in_x, is_in_y)
+
return is_in_next_building
-
-
\ No newline at end of file
diff --git a/sharc/propagation/propagation_hdfss_roof_top.py b/sharc/propagation/propagation_hdfss_roof_top.py
index 7481963be..2b47e2c35 100644
--- a/sharc/propagation/propagation_hdfss_roof_top.py
+++ b/sharc/propagation/propagation_hdfss_roof_top.py
@@ -8,7 +8,9 @@
import numpy as np
import sys
from shapely.geometry import LineString, Polygon, Point
+from sharc.parameters.constants import SPEED_OF_LIGHT
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
from sharc.parameters.parameters_fss_es import ParametersFssEs
from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_p1411 import PropagationP1411
@@ -16,6 +18,7 @@
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
from sharc.support.enumerations import StationType
+
class PropagationHDFSSRoofTop(Propagation):
"""
This is a wrapper class which can be used for indoor simulations. It
@@ -25,30 +28,33 @@ class PropagationHDFSSRoofTop(Propagation):
P.1411 LOS for distances 55m < distance < 260m
P.1411 NLOS for distances distance > 260m
"""
- def __init__(self, param: ParametersFssEs, random_number_gen: np.random.RandomState):
+
+ def __init__(self, param: ParametersHDFSS, random_number_gen: np.random.RandomState):
super().__init__(random_number_gen)
-
+
self.param = param
-
+
self.fspl_dist = 35
self.fspl_to_los_dist = 55
self.los_dist = 100
self.los_to_nlos_dist = 260
-
+
# Building dimentions
self.b_w = 120
self.b_d = 50
self.b_tol = 0.05
-
+
self.HIGH_LOSS = 4000
self.LOSS_PER_FLOOR = 50
-
+
self.propagation_fspl = PropagationFreeSpace(random_number_gen)
self.propagation_p1411 = PropagationP1411(random_number_gen)
- self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen)
-
- self.SPEED_OF_LIGHT = 299792458.0
-
+ self.building_entry = PropagationBuildingEntryLoss(
+ self.random_number_gen,
+ )
+
+ self.SPEED_OF_LIGHT = SPEED_OF_LIGHT
+
def get_loss(self, *args, **kwargs) -> np.array:
"""
Calculates path loss for given distances and frequencies
@@ -72,38 +78,46 @@ def get_loss(self, *args, **kwargs) -> np.array:
d = kwargs["distance_3D"]
else:
d = kwargs["distance_2D"]
-
+
elevation = np.transpose(kwargs["elevation"])
imt_sta_type = kwargs["imt_sta_type"]
f = kwargs["frequency"]
- number_of_sectors = kwargs.pop("number_of_sectors",1)
-
+ number_of_sectors = kwargs.pop("number_of_sectors", 1)
+
imt_x = kwargs['imt_x']
imt_y = kwargs['imt_y']
imt_z = kwargs['imt_z']
es_x = kwargs["es_x"]
es_y = kwargs["es_y"]
es_z = kwargs["es_z"]
-
+
# Define which stations are on the same building
- same_build = self.is_same_building(imt_x,imt_y,
- es_x, es_y)
+ same_build = self.is_same_building(
+ imt_x, imt_y,
+ es_x, es_y,
+ )
not_same_build = np.logical_not(same_build)
-
+
# Define boolean ranges
fspl_bool = d <= self.fspl_dist
-
- fspl_to_los_bool = np.logical_and(d > self.fspl_dist,
- d <= self.fspl_to_los_dist)
-
- los_bool = np.logical_and(d > self.fspl_to_los_dist,
- d <= self.los_dist)
-
- los_to_nlos_bool = np.logical_and(d > self.los_dist,
- d <= self.los_to_nlos_dist)
-
+
+ fspl_to_los_bool = np.logical_and(
+ d > self.fspl_dist,
+ d <= self.fspl_to_los_dist,
+ )
+
+ los_bool = np.logical_and(
+ d > self.fspl_to_los_dist,
+ d <= self.los_dist,
+ )
+
+ los_to_nlos_bool = np.logical_and(
+ d > self.los_dist,
+ d <= self.los_to_nlos_dist,
+ )
+
nlos_bool = d > self.los_to_nlos_dist
-
+
# Define indexes
same_build_idx = np.where(same_build)[0]
fspl_idx = np.where(fspl_bool)[1]
@@ -111,183 +125,234 @@ def get_loss(self, *args, **kwargs) -> np.array:
los_idx = np.where(los_bool)[1]
los_to_nlos_idx = np.where(los_to_nlos_bool)[1]
nlos_idx = np.where(nlos_bool)[1]
-
+
# Path loss
loss = np.zeros_like(d)
-
+
if not self.param.same_building_enabled:
- loss[:,same_build_idx] += self.HIGH_LOSS
+ loss[:, same_build_idx] += self.HIGH_LOSS
else:
- loss[:,same_build_idx] += self.get_same_build_loss(imt_z[same_build_idx],
- es_z)
-
- loss[:,fspl_idx] += self.propagation_fspl.get_loss(distance_3D=d[:,fspl_idx],
- frequency=f[:,fspl_idx])
- loss[:,fspl_to_los_idx] += self.interpolate_fspl_to_los(d[:,fspl_to_los_idx],
- f[:,fspl_to_los_idx],
- self.param.shadow_enabled)
- loss[:,los_idx] += self.propagation_p1411.get_loss(distance_3D=d[:,los_idx],
- frequency=f[:,los_idx],
- los=True,
- shadow=self.param.shadow_enabled)
- loss[:,los_to_nlos_idx] += self.interpolate_los_to_nlos(d[:,los_to_nlos_idx],
- f[:,los_to_nlos_idx],
- self.param.shadow_enabled)
- loss[:,nlos_idx] += self.propagation_p1411.get_loss(distance_3D=d[:,nlos_idx],
- frequency=f[:,nlos_idx],
- los=False,
- shadow=self.param.shadow_enabled)
-
+ loss[:, same_build_idx] += self.get_same_build_loss(
+ imt_z[same_build_idx],
+ es_z,
+ )
+
+ loss[:, fspl_idx] += self.propagation_fspl.get_free_space_loss(
+ distance=d[:, fspl_idx], frequency=f[:, fspl_idx],
+ )
+ loss[:, fspl_to_los_idx] += self.interpolate_fspl_to_los(
+ d[:, fspl_to_los_idx],
+ f[:, fspl_to_los_idx],
+ self.param.shadow_enabled,
+ )
+ loss[:, los_idx] += self.propagation_p1411.get_loss(
+ distance_3D=d[:, los_idx],
+ frequency=f[
+ :,
+ los_idx,
+ ],
+ los=True,
+ shadow=self.param.shadow_enabled,
+ )
+ loss[:, los_to_nlos_idx] += self.interpolate_los_to_nlos(
+ d[:, los_to_nlos_idx],
+ f[:, los_to_nlos_idx],
+ self.param.shadow_enabled,
+ )
+ loss[:, nlos_idx] += self.propagation_p1411.get_loss(
+ distance_3D=d[:, nlos_idx],
+ frequency=f[
+ :,
+ nlos_idx,
+ ],
+ los=False,
+ shadow=self.param.shadow_enabled,
+ )
+
# Building entry loss
if self.param.building_loss_enabled:
build_loss = np.zeros_like(loss)
- build_loss[0,not_same_build] = self.get_building_loss(imt_sta_type,
- f[:,not_same_build],
- elevation[:,not_same_build])
+ build_loss[0, not_same_build] = self.get_building_loss(
+ imt_sta_type,
+ f[:, not_same_build],
+ elevation[:, not_same_build],
+ )
else:
build_loss = 0.0
-
+
# Diffraction loss
diff_loss = np.zeros_like(loss)
if self.param.diffraction_enabled:
- h, d1, d2 = self.get_diff_distances(imt_x,imt_y, imt_z,
- es_x, es_y, es_z)
+ h, d1, d2 = self.get_diff_distances(
+ imt_x, imt_y, imt_z,
+ es_x, es_y, es_z,
+ )
diff_loss = np.zeros_like(loss)
- diff_loss[0,not_same_build] = self.get_diffraction_loss(h[not_same_build],
- d1[not_same_build],
- d2[not_same_build],
- f[:,not_same_build])
-
+ diff_loss[0, not_same_build] = self.get_diffraction_loss(
+ h[not_same_build],
+ d1[not_same_build],
+ d2[not_same_build],
+ f[:, not_same_build],
+ )
+
# Compute final loss
loss = loss + build_loss + diff_loss
-
+
if number_of_sectors > 1:
loss = np.repeat(loss, number_of_sectors, 1)
-
+
return loss, build_loss, diff_loss
-
- def interpolate_fspl_to_los(self,dist,freq,shad):
- fspl_loss = self.propagation_fspl.get_loss(distance_3D=self.fspl_dist,
- frequency=freq)
- los_loss = self.propagation_p1411.get_loss(distance_3D=self.fspl_to_los_dist,
- frequency=freq,
- los=True,
- shadow=False)
-
- loss = (dist - self.fspl_dist)*(los_loss - fspl_loss)/(self.fspl_to_los_dist - self.fspl_dist) + fspl_loss
-
+
+ def interpolate_fspl_to_los(self, dist, freq, shad):
+ fspl_loss = self.propagation_fspl.get_free_space_loss(
+ distance=self.fspl_dist,
+ frequency=freq,
+ )
+ los_loss = self.propagation_p1411.get_loss(
+ distance_3D=self.fspl_to_los_dist,
+ frequency=freq,
+ los=True,
+ shadow=False,
+ )
+
+ loss = (dist - self.fspl_dist) * (los_loss - fspl_loss) / \
+ (self.fspl_to_los_dist - self.fspl_dist) + fspl_loss
+
if shad:
- interp_sigma = (dist - self.fspl_dist)*(self.propagation_p1411.los_sigma)/(self.fspl_to_los_dist - self.fspl_dist)
- loss = loss + self.random_number_gen.normal(0.0,interp_sigma)
-
+ interp_sigma = (dist - self.fspl_dist) * (self.propagation_p1411.los_sigma) / \
+ (self.fspl_to_los_dist - self.fspl_dist)
+ loss = loss + self.random_number_gen.normal(0.0, interp_sigma)
+
return loss
-
- def interpolate_los_to_nlos(self,dist,freq,shad):
- los_loss = self.propagation_p1411.get_loss(distance_3D=self.los_dist,
- frequency=freq,
- los=True,
- shadow=False)
- nlos_loss = self.propagation_p1411.get_loss(distance_3D=self.los_to_nlos_dist,
- frequency=freq,
- los=False,
- shadow=False)
-
- loss = (dist-self.los_dist)*(nlos_loss - los_loss)/(self.los_to_nlos_dist - self.los_dist) + los_loss
-
+
+ def interpolate_los_to_nlos(self, dist, freq, shad):
+ los_loss = self.propagation_p1411.get_loss(
+ distance_3D=self.los_dist,
+ frequency=freq,
+ los=True,
+ shadow=False,
+ )
+ nlos_loss = self.propagation_p1411.get_loss(
+ distance_3D=self.los_to_nlos_dist,
+ frequency=freq,
+ los=False,
+ shadow=False,
+ )
+
+ loss = (dist - self.los_dist) * (nlos_loss - los_loss) / \
+ (self.los_to_nlos_dist - self.los_dist) + los_loss
+
if shad:
- interp_sigma = (dist-self.los_dist)*(self.propagation_p1411.nlos_sigma - self.propagation_p1411.los_sigma)/(self.los_to_nlos_dist - self.los_dist) +\
- self.propagation_p1411.los_sigma
- loss = loss + self.random_number_gen.normal(0.0,interp_sigma)
-
+ interp_sigma = (dist - self.los_dist) * \
+ (self.propagation_p1411.nlos_sigma - self.propagation_p1411.los_sigma) / \
+ (self.los_to_nlos_dist - self.los_dist) + \
+ self.propagation_p1411.los_sigma
+ loss = loss + self.random_number_gen.normal(0.0, interp_sigma)
+
return loss
-
- def get_building_loss(self,imt_sta_type,f,elevation):
+
+ def get_building_loss(self, imt_sta_type, f, elevation):
if imt_sta_type is StationType.IMT_UE:
build_loss = self.building_entry.get_loss(f, elevation)
elif imt_sta_type is StationType.IMT_BS:
if self.param.bs_building_entry_loss_type == 'P2109_RANDOM':
build_loss = self.building_entry.get_loss(f, elevation)
elif self.param.bs_building_entry_loss_type == 'P2109_FIXED':
- build_loss = self.building_entry.get_loss(f, elevation, prob=self.param.bs_building_entry_loss_prob)
+ build_loss = self.building_entry.get_loss(
+ f, elevation, prob=self.param.bs_building_entry_loss_prob,
+ )
elif self.param.bs_building_entry_loss_type == 'FIXED_VALUE':
build_loss = self.param.bs_building_entry_loss_value
else:
- sys.stderr.write("ERROR\nBuilding entry loss type: " +
- self.param.bs_building_entry_loss_type)
+ sys.stderr.write(
+ "ERROR\nBuilding entry loss type: " +
+ self.param.bs_building_entry_loss_type,
+ )
sys.exit(1)
-
+
return build_loss
-
- def is_same_building(self,imt_x,imt_y, es_x, es_y):
-
- building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2])
- building_y_range = es_y + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2])
-
- is_in_x = np.logical_and(imt_x >= building_x_range[0],imt_x <= building_x_range[1])
- is_in_y = np.logical_and(imt_y >= building_y_range[0],imt_y <= building_y_range[1])
-
- is_in_building = np.logical_and(is_in_x,is_in_y)
-
+
+ def is_same_building(self, imt_x, imt_y, es_x, es_y):
+
+ building_x_range = es_x + (1 + self.b_tol) * \
+ np.array([-self.b_w / 2, +self.b_w / 2])
+ building_y_range = es_y + (1 + self.b_tol) * \
+ np.array([-self.b_d / 2, +self.b_d / 2])
+
+ is_in_x = np.logical_and(
+ imt_x >= building_x_range[0], imt_x <= building_x_range[1],
+ )
+ is_in_y = np.logical_and(
+ imt_y >= building_y_range[0], imt_y <= building_y_range[1],
+ )
+
+ is_in_building = np.logical_and(is_in_x, is_in_y)
+
return is_in_building
-
- def get_same_build_loss(self,imt_z,es_z):
- floor_number = np.floor_divide((es_z - imt_z),3) + 1
-
- loss = self.LOSS_PER_FLOOR*floor_number
-
+
+ def get_same_build_loss(self, imt_z, es_z):
+ floor_number = np.floor_divide((es_z - imt_z), 3) + 1
+
+ loss = self.LOSS_PER_FLOOR * floor_number
+
return loss
-
- def get_diff_distances(self,imt_x,imt_y, imt_z, es_x, es_y, es_z, dist_2D=False):
-
- build_poly = Polygon([[es_x + self.b_w/2, es_y + self.b_d/2],
- [es_x - self.b_w/2, es_y + self.b_d/2],
- [es_x - self.b_w/2, es_y - self.b_d/2],
- [es_x + self.b_w/2, es_y - self.b_d/2]])
- es_point = Point([es_x,es_y])
-
+
+ def get_diff_distances(self, imt_x, imt_y, imt_z, es_x, es_y, es_z, dist_2D=False):
+
+ build_poly = Polygon([
+ [es_x + self.b_w / 2, es_y + self.b_d / 2],
+ [es_x - self.b_w / 2, es_y + self.b_d / 2],
+ [es_x - self.b_w / 2, es_y - self.b_d / 2],
+ [es_x + self.b_w / 2, es_y - self.b_d / 2],
+ ])
+ es_point = Point([es_x, es_y])
+
d1_2D = np.zeros_like(imt_x)
d2_2D = np.zeros_like(imt_x)
dist = np.zeros_like(imt_x)
- for k,(x,y) in enumerate(zip(imt_x,imt_y)):
- line = LineString([[es_x,es_y],[x,y]])
+ for k, (x, y) in enumerate(zip(imt_x, imt_y)):
+ line = LineString([[es_x, es_y], [x, y]])
intersection_line = line.intersection(build_poly)
d1_2D[k] = intersection_line.length
-
- imt_point = Point([x,y])
+
+ imt_point = Point([x, y])
dist[k] = es_point.distance(imt_point)
d2_2D[k] = dist[k] - d1_2D[k]
-
+
if dist_2D:
return d1_2D, d2_2D
-
+
build_height = es_z - 1
- z_in_build = imt_z + (es_z - imt_z)*d2_2D/dist
+ z_in_build = imt_z + (es_z - imt_z) * d2_2D / dist
h = build_height - z_in_build
-
- d1 = np.sqrt(1**2 + np.power(d1_2D,2))
- d2 = np.sqrt(np.power((build_height - imt_z),2) + np.power(d2_2D,2))
-
+
+ d1 = np.sqrt(1**2 + np.power(d1_2D, 2))
+ d2 = np.sqrt(np.power((build_height - imt_z), 2) + np.power(d2_2D, 2))
+
return h, d1, d2
-
- def get_diffraction_loss(self,h, d1, d2, f):
-
- wavelength = self.SPEED_OF_LIGHT/(f*1e6)
-
- v = h*np.sqrt((2/wavelength)*(1/d1 + 1/d2))
-
+
+ def get_diffraction_loss(self, h, d1, d2, f):
+
+ wavelength = self.SPEED_OF_LIGHT / (f * 1e6)
+
+ v = h * np.sqrt((2 / wavelength) * (1 / d1 + 1 / d2))
+
loss = np.zeros_like(v)
-
+
v_idx = v > -0.7
-
- loss[v_idx] = 6.9 + 20*np.log10(np.sqrt(np.power((v[v_idx] - 0.1), 2) + 1)\
- + v[v_idx] - 0.1)
+
+ loss[v_idx] = 6.9 + 20 * np.log10(
+ np.sqrt(np.power((v[v_idx] - 0.1), 2) + 1) +
+ v[v_idx] - 0.1,
+ )
return loss
-
+
+
if __name__ == '__main__':
-
+
from sharc.propagation.propagation_hdfss import PropagationHDFSS
import matplotlib.pyplot as plt
-
+
rnd = np.random.RandomState(101)
par = ParametersFssEs()
par.building_loss_enabled = False
@@ -297,50 +362,54 @@ def get_diffraction_loss(self,h, d1, d2, f):
par.bs_building_entry_loss_prob = 0.5
par.bs_building_entry_loss_value = 50
par.es_position = "ROOFTOP"
- prop = PropagationHDFSS(par,rnd)
-
- d = np.linspace(5,1000,num=2000)
+ prop = PropagationHDFSS(par, rnd)
+
+ d = np.linspace(5, 1000, num=2000)
d = np.array([list(d)])
- f = 40e3*np.ones_like(d)
+ f = 40e3 * np.ones_like(d)
ele = np.transpose(np.zeros_like(d))
sta_type = StationType.IMT_BS
-
+
# Without shadow
- loss_interp = prop.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=sta_type)
+ loss_interp = prop.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=sta_type,
+ )
prop.fspl_dist = prop.fspl_to_los_dist
prop.los_dist = prop.los_to_nlos_dist
- loss_no_interp = prop.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=sta_type)
-
+ loss_no_interp = prop.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=sta_type,
+ )
+
ravel_d = np.ravel(d)
ravel_loss_interp = np.ravel(loss_interp)
ravel_loss_no_interp = np.ravel(loss_no_interp)
- plt.plot(ravel_d,ravel_loss_interp,'k-',label='Interpolated')
- plt.plot(ravel_d,ravel_loss_no_interp,'k--',label='Not Interpolated')
+ plt.plot(ravel_d, ravel_loss_interp, 'k-', label='Interpolated')
+ plt.plot(ravel_d, ravel_loss_no_interp, 'k--', label='Not Interpolated')
plt.legend()
plt.xlabel("Distance [m]")
plt.ylabel("Path Loss [dB]")
plt.grid()
plt.show()
-
+
# With shadow
par.shadow_enabled = True
- prop = PropagationHDFSS(par,rnd)
- loss = prop.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=sta_type)
-
+ prop = PropagationHDFSS(par, rnd)
+ loss = prop.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=sta_type,
+ )
+
ravel_loss = np.ravel(loss)
- plt.plot(ravel_d,ravel_loss)
+ plt.plot(ravel_d, ravel_loss)
plt.xlabel("Distance [m]")
plt.ylabel("Path Loss [dB]")
plt.grid()
plt.show()
-
-
\ No newline at end of file
diff --git a/sharc/propagation/propagation_indoor.py b/sharc/propagation/propagation_indoor.py
index 7cc377444..fb47b86b3 100644
--- a/sharc/propagation/propagation_indoor.py
+++ b/sharc/propagation/propagation_indoor.py
@@ -4,17 +4,18 @@
@author: edgar
"""
+from multipledispatch import dispatch
+import sys
+import numpy as np
-from sharc.parameters.parameters_indoor import ParametersIndoor
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
+from sharc.parameters.imt.parameters_indoor import ParametersIndoor
from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_free_space import PropagationFreeSpace
from sharc.propagation.propagation_inh_office import PropagationInhOffice
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
-import sys
-import numpy as np
-import matplotlib.pyplot as plt
-from cycler import cycler
class PropagationIndoor(Propagation):
"""
@@ -31,7 +32,7 @@ class PropagationIndoor(Propagation):
# interference is much higher than inter-building interference
HIGH_PATH_LOSS = 400
- def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIndoor,ue_per_cell):
+ def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIndoor, ue_per_cell):
super().__init__(random_number_gen)
if param.basic_path_loss == "FSPL":
@@ -39,15 +40,87 @@ def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIn
elif param.basic_path_loss == "INH_OFFICE":
self.bpl = PropagationInhOffice(random_number_gen)
else:
- sys.stderr.write("ERROR\nInvalid indoor basic path loss model: " + param.basic_path_loss)
+ sys.stderr.write(
+ "ERROR\nInvalid indoor basic path loss model: " + param.basic_path_loss,
+ )
sys.exit(1)
self.bel = PropagationBuildingEntryLoss(random_number_gen)
self.building_class = param.building_class
self.bs_per_building = param.num_cells
- self.ue_per_building = ue_per_cell*param.num_cells
+ self.ue_per_building = ue_per_cell * param.num_cells
+
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the get_loss method to fit the Propagation ABC class interface
+ Calculates the loss between station_a and station_b
- def get_loss(self, *args, **kwargs) -> np.array:
+ Parameters
+ ----------
+ params : Parameters
+ Simulation parameters needed for the propagation class
+ frequency: float
+ Center frequency
+ station_a : StationManager
+ StationManager container
+ station_b : StationManager
+ StationManager container
+ station_a_gains: np.ndarray defaults to None
+ Not used
+ station_b_gains: np.ndarray defaults to None
+ Not used
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ wrap_around_enabled = False
+ if params.imt.topology.type == "MACROCELL":
+ wrap_around_enabled = params.imt.topology.macrocell.wrap_around \
+ and params.imt.topology.macrocell.num_clusters == 1
+ if params.imt.topology.type == "HOTSPOT":
+ wrap_around_enabled = params.imt.topology.hotspot.wrap_around \
+ and params.imt.topology.hotspot.num_clusters == 1
+
+ if wrap_around_enabled:
+ bs_to_ue_dist_2d, bs_to_ue_dist_3d, _, _ = \
+ station_b.get_dist_angles_wrap_around(station_a)
+ else:
+ bs_to_ue_dist_2d = station_b.get_distance_to(station_a)
+ bs_to_ue_dist_3d = station_b.get_3d_distance_to(station_a)
+
+ frequency_array = frequency * np.ones(bs_to_ue_dist_2d.shape)
+ indoor_stations = np.tile(
+ station_a.indoor, (station_b.num_stations, 1),
+ )
+ elevation = np.transpose(station_a.get_elevation(station_b))
+
+ return self.get_loss(
+ bs_to_ue_dist_3d,
+ bs_to_ue_dist_2d,
+ frequency_array,
+ elevation,
+ indoor_stations,
+ params.imt.shadowing,
+ )
+
+ # pylint: disable=function-redefined
+ # pylint: disable=arguments-renamed
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool)
+ def get_loss(
+ self, distance_3D: np.ndarray, distance_2D: np.ndarray, frequency: float,
+ elevation: np.ndarray, indoor_stations: np.ndarray, shadowing_flag: bool,
+ ) -> np.array:
"""
Calculates path loss for LOS and NLOS cases with respective shadowing
(if shadowing has to be added)
@@ -66,33 +139,30 @@ def get_loss(self, *args, **kwargs) -> np.array:
array with path loss values with dimensions of distance_2D
"""
- distance_3D = kwargs["distance_3D"]
- distance_2D = kwargs["distance_2D"]
- elevation = kwargs["elevation"]
- frequency = kwargs["frequency"]
- indoor = kwargs["indoor_stations"]
- shadowing = kwargs["shadowing"]
-
- loss = PropagationIndoor.HIGH_PATH_LOSS*np.ones(frequency.shape)
- iter = int(frequency.shape[0]/self.bs_per_building)
+ loss = PropagationIndoor.HIGH_PATH_LOSS * np.ones(frequency.shape)
+ iter = int(frequency.shape[0] / self.bs_per_building)
for i in range(iter):
- bi = int(self.bs_per_building*i)
- bf = int(self.bs_per_building*(i+1))
- ui = int(self.ue_per_building*i)
- uf = int(self.ue_per_building*(i+1))
+ bi = int(self.bs_per_building * i)
+ bf = int(self.bs_per_building * (i + 1))
+ ui = int(self.ue_per_building * i)
+ uf = int(self.ue_per_building * (i + 1))
# calculate basic path loss
- loss[bi:bf,ui:uf] = self.bpl.get_loss(distance_3D = distance_3D[bi:bf,ui:uf],
- distance_2D = distance_2D[bi:bf,ui:uf],
- frequency = frequency[bi:bf,ui:uf],
- indoor = indoor[0,ui:uf],
- shadowing = shadowing)
+ loss[bi:bf, ui:uf] = self.bpl.get_loss(
+ distance_3D=distance_3D[bi:bf, ui:uf],
+ distance_2D=distance_2D[bi:bf, ui:uf],
+ frequency=frequency[bi:bf, ui:uf],
+ indoor=indoor_stations[0, ui:uf],
+ shadowing=shadowing_flag,
+ )
# calculates the additional building entry loss for outdoor UE's
# that are served by indoor BS's
- bel = (~ indoor[0,ui:uf]) * self.bel.get_loss(frequency[bi:bf,ui:uf], elevation[bi:bf,ui:uf], "RANDOM", self.building_class)
+ bel = (~ indoor_stations[0, ui:uf]) * self.bel.get_loss(
+ frequency[bi:bf, ui:uf], elevation[bi:bf, ui:uf], "RANDOM", self.building_class,
+ )
- loss[bi:bf,ui:uf] = loss[bi:bf,ui:uf] + bel
+ loss[bi:bf, ui:uf] = loss[bi:bf, ui:uf] + bel
return loss
@@ -110,24 +180,27 @@ def get_loss(self, *args, **kwargs) -> np.array:
bs_per_building = 3
ue_per_bs = 3
- num_bs = bs_per_building*params.n_rows*params.n_colums
- num_ue = num_bs*ue_per_bs
- distance_2D = 150*np.random.random((num_bs, num_ue))
- frequency = 27000*np.ones(distance_2D.shape)
- indoor = np.random.rand(num_bs) < params.ue_indoor_percent
- h_bs = 3*np.ones(num_bs)
- h_ue = 1.5*np.ones(num_ue)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
+ num_bs = bs_per_building * params.n_rows * params.n_colums
+ num_ue = num_bs * ue_per_bs
+ distance_2D = 150 * np.random.random((num_bs, num_ue))
+ frequency = 27000 * np.ones(distance_2D.shape)
+ indoor = np.random.rand(1, num_ue) < params.ue_indoor_percent
+ indoor = np.tile(indoor, (num_bs, 1))
+ h_bs = 3 * np.ones(num_bs)
+ h_ue = 1.5 * np.ones(num_ue)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2)
height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1))
- elevation = np.degrees(np.arctan(height_diff/distance_2D))
-
- propagation_indoor = PropagationIndoor(np.random.RandomState(),params,ue_per_bs)
- loss_indoor = propagation_indoor.get_loss(distance_3D = distance_3D,
- distance_2D = distance_2D,
- elevation = elevation,
- frequency = frequency,
- indoor_stations = indoor,
- shadowing = False)
-
-
-
+ elevation = np.degrees(np.arctan(height_diff / distance_2D))
+
+ propagation_indoor = PropagationIndoor(
+ np.random.RandomState(), params, ue_per_bs,
+ )
+ loss_indoor = propagation_indoor.get_loss(
+ distance_3D,
+ distance_2D,
+ frequency,
+ elevation,
+ indoor,
+ False,
+ )
+ print(loss_indoor)
diff --git a/sharc/propagation/propagation_inh_office.py b/sharc/propagation/propagation_inh_office.py
index 256edee9c..16c56761b 100644
--- a/sharc/propagation/propagation_inh_office.py
+++ b/sharc/propagation/propagation_inh_office.py
@@ -11,13 +11,13 @@
import matplotlib.pyplot as plt
from cycler import cycler
+
class PropagationInhOffice(Propagation):
"""
Implements the Indoor Hotspot - Office path loss model with LOS probability
according to 3GPP TR 38.900 v14.2.0.
"""
-
def get_loss(self, *args, **kwargs) -> np.array:
"""
Calculates path loss for LOS and NLOS cases with respective shadowing
@@ -58,18 +58,23 @@ def get_loss(self, *args, **kwargs) -> np.array:
loss = np.empty(d_2D.shape)
if len(i_los[0]):
- loss[i_los] = self.get_loss_los(d_3D[i_los], f[i_los], shadowing_los)
+ loss[i_los] = self.get_loss_los(
+ d_3D[i_los], f[i_los], shadowing_los,
+ )
if len(i_nlos[0]):
- loss[i_nlos] = self.get_loss_nlos(d_3D[i_nlos], f[i_nlos], shadowing_nlos)
+ loss[i_nlos] = self.get_loss_nlos(
+ d_3D[i_nlos], f[i_nlos], shadowing_nlos,
+ )
return loss
-
- def get_loss_los(self,
- distance_3D: np.array,
- frequency: np.array,
- shadowing_std: float):
+ def get_loss_los(
+ self,
+ distance_3D: np.array,
+ frequency: np.array,
+ shadowing_std: float,
+ ):
"""
Calculates path loss for the LOS (line-of-sight) case.
@@ -84,20 +89,24 @@ def get_loss_los(self,
array with path loss values with dimensions of distance_3D
"""
- loss = 32.4 + 17.3*np.log10(distance_3D) + 20*np.log10(frequency/1e3)
+ loss = 32.4 + 17.3 * np.log10(distance_3D) + \
+ 20 * np.log10(frequency / 1e3)
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_3D.shape,
+ )
else:
shadowing = 0
return loss + shadowing
-
- def get_loss_nlos(self,
- distance_3D: np.array,
- frequency: np.array,
- shadowing_std: float):
+ def get_loss_nlos(
+ self,
+ distance_3D: np.array,
+ frequency: np.array,
+ shadowing_std: float,
+ ):
"""
Calculates path loss for the NLOS (non line-of-sight) case.
@@ -112,19 +121,21 @@ def get_loss_nlos(self,
array with path loss values with dimensions of distance_3D
"""
- loss_nlos = 17.3 + 38.3*np.log10(distance_3D) + 24.9*np.log10(frequency/1e3)
+ loss_nlos = 17.3 + 38.3 * \
+ np.log10(distance_3D) + 24.9 * np.log10(frequency / 1e3)
loss_los = self.get_loss_los(distance_3D, frequency, 0)
loss = np.maximum(loss_los, loss_nlos)
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_3D.shape,
+ )
else:
shadowing = 0
return loss + shadowing
-
def get_los_condition(self, p_los: np.array, indoor: np.array) -> np.array:
"""
Evaluates if user equipments are LOS (True) of NLOS (False). If UE is
@@ -140,11 +151,12 @@ def get_los_condition(self, p_los: np.array, indoor: np.array) -> np.array:
An array with True or False if user equipments are in LOS of NLOS
condition, respectively.
"""
- los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los
+ los_condition = self.random_number_gen.random_sample(
+ p_los.shape,
+ ) < p_los
los_condition = los_condition & indoor
return los_condition
-
def get_los_probability(self, distance_2D: np.array) -> np.array:
"""
Returns the line-of-sight (LOS) probability
@@ -161,9 +173,9 @@ def get_los_probability(self, distance_2D: np.array) -> np.array:
p_los = np.ones(distance_2D.shape)
id1 = np.where((distance_2D > 1.2) & (distance_2D < 6.5))
- p_los[id1] = np.exp(-(distance_2D[id1] - 1.2)/4.7)
+ p_los[id1] = np.exp(-(distance_2D[id1] - 1.2) / 4.7)
id2 = np.where(distance_2D >= 6.5)
- p_los[id2] = np.exp(-(distance_2D[id2] - 6.5)/32.6)*0.32
+ p_los[id2] = np.exp(-(distance_2D[id2] - 6.5) / 32.6) * 0.32
return p_los
@@ -172,18 +184,18 @@ def get_los_probability(self, distance_2D: np.array) -> np.array:
###########################################################################
# Print LOS probability
- distance_2D = np.linspace(0.1, 150, num=1000)[:,np.newaxis]
+ distance_2D = np.linspace(0.1, 150, num=1000)[:, np.newaxis]
inh = PropagationInhOffice(np.random.RandomState())
los_probability = inh.get_los_probability(distance_2D)
- plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
plt.loglog(distance_2D, los_probability)
plt.title("InH Office - LOS probability")
plt.xlabel("distance [m]")
plt.ylabel("probability")
- plt.xlim((0, distance_2D[-1,0]))
+ plt.xlim((0, distance_2D[-1, 0]))
plt.ylim((0, 1.1))
plt.tight_layout()
plt.grid()
@@ -193,33 +205,37 @@ def get_los_probability(self, distance_2D: np.array) -> np.array:
# Print path loss for InH-LOS, InH-NLOS and Free Space
from sharc.propagation.propagation_free_space import PropagationFreeSpace
shadowing = 0
- distance_2D = np.linspace(1, 150, num=1000)[:,np.newaxis]
- frequency = 27000*np.ones(distance_2D.shape)
- h_bs = 3*np.ones(len(distance_2D[:,0]))
- h_ue = 1.5*np.ones(len(distance_2D[0,:]))
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- indoor = np.ones(len(distance_2D[0,:]), dtype = bool)
-
- loss_fs = PropagationFreeSpace(np.random.RandomState()).get_loss(distance_3D=distance_3D, frequency=frequency)
-
- loss_inh = inh.get_loss(distance_3D = distance_3D,
- distance_2D = distance_2D,
- frequency = frequency,
- indoor = indoor,
- shadowing = shadowing)
-
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ distance_2D = np.linspace(1, 150, num=1000)[:, np.newaxis]
+ frequency = 27000 * np.ones(distance_2D.shape)
+ h_bs = 3 * np.ones(len(distance_2D[:, 0]))
+ h_ue = 1.5 * np.ones(len(distance_2D[0, :]))
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2)
+ indoor = np.ones(len(distance_2D[0, :]), dtype=bool)
+
+ loss_fs = PropagationFreeSpace(np.random.RandomState()).get_loss(
+ distance_3D=distance_3D, frequency=frequency,
+ )
+
+ loss_inh = inh.get_loss(
+ distance_3D=distance_3D,
+ distance_2D=distance_2D,
+ frequency=frequency,
+ indoor=indoor,
+ shadowing=shadowing,
+ )
+
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.set_prop_cycle( cycler('color', ['r', 'b', 'g', 'y']) )
+ ax.set_prop_cycle(cycler('color', ['r', 'b', 'g', 'y']))
- ax.scatter(distance_2D, loss_inh, label = "InH-Office")
- ax.plot(distance_2D, loss_fs, "-b", label = "free space")
+ ax.scatter(distance_2D, loss_inh, label="InH-Office")
+ ax.plot(distance_2D, loss_fs, "-b", label="free space")
ax.set_xscale('log')
plt.title("InH-Office - path loss")
plt.xlabel("distance [m]")
plt.ylabel("path loss [dB]")
- plt.xlim((0, distance_2D[-1,0]))
+ plt.xlim((0, distance_2D[-1, 0]))
plt.legend(loc="upper left")
plt.tight_layout()
diff --git a/sharc/propagation/propagation_p1411.py b/sharc/propagation/propagation_p1411.py
index c74d209b2..758f9d4b5 100644
--- a/sharc/propagation/propagation_p1411.py
+++ b/sharc/propagation/propagation_p1411.py
@@ -9,64 +9,67 @@
import numpy as np
+
class PropagationP1411(Propagation):
"""
Implements the propagation model described in ITU-R P.1411-9, section 4.2
-
+
Frequency in MHz and distance in meters!
"""
- def __init__(self, random_number_gen: np.random.RandomState, above_clutter = True):
+
+ def __init__(self, random_number_gen: np.random.RandomState, above_clutter=True):
super().__init__(random_number_gen)
-
+
if above_clutter:
- self.los_alpha = 2.29
- self.los_beta = 28.6
- self.los_gamma = 1.96
+ self.los_alpha = 2.29
+ self.los_beta = 28.6
+ self.los_gamma = 1.96
self.los_sigma = 3.48
-
+
self.nlos_alpha = 4.39
self.nlos_beta = -6.27
self.nlos_gamma = 2.30
self.nlos_sigma = 6.89
else:
- self.los_alpha = 2.12
+ self.los_alpha = 2.12
self.los_beta = 29.2
- self.los_gamma = 2.11
+ self.los_gamma = 2.11
self.los_sigma = 5.06
-
+
self.nlos_alpha = 4.00
self.nlos_beta = 10.20
self.nlos_gamma = 2.36
self.nlos_sigma = 7.60
-
+
def get_loss(self, *args, **kwargs) -> np.array:
if "distance_3D" in kwargs:
d = kwargs["distance_3D"]
else:
d = kwargs["distance_2D"]
- f = kwargs["frequency"]/1e3
- los = kwargs.pop("los",True)
- shadow = kwargs.pop("shadow",True)
- number_of_sectors = kwargs.pop("number_of_sectors",1)
-
+ f = kwargs["frequency"] / 1e3
+ los = kwargs.pop("los", True)
+ shadow = kwargs.pop("shadow", True)
+ number_of_sectors = kwargs.pop("number_of_sectors", 1)
+
if los:
- alpha = self.los_alpha
- beta = self.los_beta
- gamma = self.los_gamma
+ alpha = self.los_alpha
+ beta = self.los_beta
+ gamma = self.los_gamma
sigma = self.los_sigma
else:
alpha = self.nlos_alpha
beta = self.nlos_beta
gamma = self.nlos_gamma
sigma = self.nlos_sigma
-
+
if shadow:
- shadow_loss = self.random_number_gen.normal(0.0,sigma,d.shape)
+ shadow_loss = self.random_number_gen.normal(0.0, sigma, d.shape)
else:
shadow_loss = 0.0
- loss = 10*alpha*np.log10(d) + 10*gamma*np.log10(f) + beta + shadow_loss
+ loss = 10 * alpha * np.log10(d) + 10 * \
+ gamma * np.log10(f) + beta + shadow_loss
if number_of_sectors > 1:
loss = np.repeat(loss, number_of_sectors, 1)
diff --git a/sharc/propagation/propagation_p619.py b/sharc/propagation/propagation_p619.py
index 9d5ed3b8d..8b31f2683 100644
--- a/sharc/propagation/propagation_p619.py
+++ b/sharc/propagation/propagation_p619.py
@@ -5,16 +5,22 @@
@author: andre barreto
"""
-from sharc.propagation.propagation import Propagation
-import math
+import os
+import csv
+from scipy.interpolate import interp1d
import numpy as np
+from multipledispatch import dispatch
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
+from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_free_space import PropagationFreeSpace
from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
from sharc.propagation.atmosphere import ReferenceAtmosphere
from sharc.support.enumerations import StationType
from sharc.propagation.scintillation import Scintillation
+from sharc.parameters.constants import EARTH_RADIUS
class PropagationP619(Propagation):
@@ -25,23 +31,82 @@ class PropagationP619(Propagation):
get_loss: Calculates path loss for earth-space link
"""
- def __init__(self, random_number_gen: np.random.RandomState):
+ def __init__(
+ self,
+ random_number_gen: np.random.RandomState,
+ space_station_alt_m: float,
+ earth_station_alt_m: float,
+ earth_station_lat_deg: float,
+ earth_station_long_diff_deg: float,
+ season: str,
+ mean_clutter_height: str,
+ below_rooftop: float
+ ):
+ """Implements the earth-to-space channel model from ITU-R P.619
+
+ Parameters
+ ----------
+ random_number_gen : np.random.RandomState
+ randon number generator
+ space_station_alt_m : float
+ The space station altitude in meters
+ earth_station_alt_m : float
+ The Earth station altitude in meters
+ earth_station_lat_deg : float
+ The Earth station latitude in degrees
+ earth_station_long_diff_deg : float
+ The difference between longitudes of earth-station and and space-sation.
+ (positive if space-station is to the East of earth-station)
+ season: str
+ Possible values are "SUMMER" or "WINTER".
+ """
+
super().__init__(random_number_gen)
+ self.is_earth_space_model = True
self.clutter = PropagationClutterLoss(self.random_number_gen)
self.free_space = PropagationFreeSpace(self.random_number_gen)
- self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen)
+ self.building_entry = PropagationBuildingEntryLoss(
+ self.random_number_gen,
+ )
self.scintillation = Scintillation(self.random_number_gen)
self.atmosphere = ReferenceAtmosphere()
- self.depolarization_loss = 0 # 1.5
- self.polarization_mismatch_loss = 0 # 3
+ self.depolarization_loss = 0
+ self.polarization_mismatch_loss = 0
self.elevation_has_atmospheric_loss = []
self.freq_has_atmospheric_loss = []
self.surf_water_dens_has_atmospheric_loss = []
self.atmospheric_loss = []
self.elevation_delta = .01
+ self.space_station_alt_m = space_station_alt_m
+ self.earth_station_alt_m = earth_station_alt_m
+ self.earth_station_lat_deg = earth_station_lat_deg
+ self.earth_station_long_diff_deg = earth_station_long_diff_deg
+ self.mean_clutter_height = mean_clutter_height
+ self.below_rooftop = below_rooftop
+ if season.upper() not in ["SUMMER", "WINTER"]:
+ raise ValueError(
+ f"PropagationP619: Invalid value for parameter season - {season}. Possible values are \"SUMMER\", \"WINTER\".",
+ )
+ self.season = season
+ # Load city name based on latitude
+ self.lookup_table = False
+ self.city_name = self._get_city_name_by_latitude()
+ if self.city_name != "Unknown":
+ self.lookup_table = True
+
+ def _get_city_name_by_latitude(self):
+ localidades_file = os.path.join(
+ os.path.dirname(__file__), 'Dataset/localidades.csv',
+ )
+ with open(localidades_file, mode='r') as file:
+ reader = csv.DictReader(file)
+ for row in reader:
+ if abs(float(row['Latitude']) - self.earth_station_lat_deg) < 1e-5:
+ return row['Cidade']
+ return 'Unknown'
def _get_atmospheric_gasses_loss(self, *args, **kwargs) -> float:
"""
@@ -50,85 +115,122 @@ def _get_atmospheric_gasses_loss(self, *args, **kwargs) -> float:
Parameters
----------
frequency_MHz (float) : center frequencies [MHz]
- apparent_elevation (float) : apparent elevation angle (degrees)
- sat_params (ParametersFss) : parameters of satellite system
-
+ apparent_elevation (float) : apparent elevation angle at the Earth-based station (degrees)
Returns
-------
path_loss (float): scalar with atmospheric loss
"""
frequency_MHz = kwargs["frequency_MHz"]
apparent_elevation = kwargs["apparent_elevation"]
- sat_params = kwargs["sat_params"]
-
- surf_water_vapour_density = kwargs.pop("surf_water_vapour_density", False)
-
- earth_radius_km = sat_params.EARTH_RADIUS/1000
- a_acc = 0. # accumulated attenuation (in dB)
- h = sat_params.imt_altitude/1000 # ray altitude in km
- beta = (90-abs(apparent_elevation)) * np.pi / 180. # incidence angle
+ lookupTable = kwargs.pop("lookupTable", True)
+ surf_water_vapour_density = kwargs.pop(
+ "surf_water_vapour_density", False,
+ )
+
+ if lookupTable and self.city_name != 'Unknown':
+ # Define the path to the CSV file
+ output_dir = os.path.join(os.path.dirname(__file__), 'Dataset')
+ csv_file = os.path.join(
+ output_dir, f'{self.city_name}_{int(frequency_MHz)}_{int(self.earth_station_alt_m)}m.csv',
+ )
+ if os.path.exists(csv_file):
+ elevations = []
+ losses = []
+ with open(csv_file, mode='r') as file:
+ reader = csv.reader(file)
+ next(reader) # Skip header
+ for row in reader:
+ elevations.append(float(row[0]))
+ losses.append(float(row[1]))
+ interpolation_function = interp1d(
+ elevations, losses, kind='linear', fill_value='extrapolate',
+ )
+ return interpolation_function(apparent_elevation)
+
+ earth_radius_km = EARTH_RADIUS / 1000
+ a_acc = 0. # accumulated attenuation (in dB)
+ h = self.earth_station_alt_m / 1000 # ray altitude in km
+ beta = (90 - abs(apparent_elevation)) * np.pi / 180. # incidence angle
if not surf_water_vapour_density:
- dummy, dummy, surf_water_vapour_density = \
- self.atmosphere.get_reference_atmosphere_p835(sat_params.imt_lat_deg,
- 0, season = sat_params.season)
+ _, _, surf_water_vapour_density = self.atmosphere.get_reference_atmosphere_p835(
+ self.earth_station_lat_deg, 0, season=self.season,
+ )
- # first, check if atmospheric loss was already calculated
if len(self.elevation_has_atmospheric_loss):
- elevation_diff = np.abs(apparent_elevation - np.array(self.elevation_has_atmospheric_loss))
+ elevation_diff = np.abs(
+ apparent_elevation - np.array(self.elevation_has_atmospheric_loss),
+ )
+ elevation_diff = np.abs(
+ apparent_elevation - np.array(self.elevation_has_atmospheric_loss),
+ )
indices = np.where(elevation_diff <= self.elevation_delta)
if indices[0].size:
index = np.argmin(elevation_diff)
if self.freq_has_atmospheric_loss[index] == frequency_MHz and \
self.surf_water_dens_has_atmospheric_loss[index] == surf_water_vapour_density:
- loss = self.atmospheric_loss[index]
- return loss
+ return self.atmospheric_loss[index]
- rho_s = surf_water_vapour_density * np.exp(h/2) # water vapour density at h
+ rho_s = surf_water_vapour_density * \
+ np.exp(h / 2) # water vapour density at h
if apparent_elevation < 0:
- # get temperature (t), dry-air pressure (p), water-vapour pressure (e),
- # refractive index (n) and specific attenuation (gamma)
- t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz)
- delta = .0001 + 0.01 * max(h, 0) # layer thickness
- r = earth_radius_km + h - delta # radius of lower edge
+ t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(
+ h, rho_s, frequency_MHz,
+ )
+ delta = .0001 + 0.01 * max(h, 0) # layer thickness
+ r = earth_radius_km + h - delta # radius of lower edge
while True:
m = (r + delta) * np.sin(beta) - r
if m >= 0:
- dh = 2 * np.sqrt(2*r*(delta-m)+delta**2-m**2) # horizontal path
+ dh = 2 * np.sqrt(
+ 2 * r * (delta - m) +
+ delta ** 2 - m ** 2,
+ ) # horizontal path
a_acc += dh * gamma
break
- ds = (r+delta)*np.cos(beta)-np.sqrt((r+delta)**2 * np.cos(beta)**2 -
- (2*r*delta + delta**2)) # slope distance
- a_acc += ds*gamma
- alpha = np.arcsin((r+delta)/r * np.sin(beta)) # angle to vertical
+ ds = (r + delta) * np.cos(beta) - np.sqrt((r + delta) **
+ 2 * np.cos(beta) ** 2 - (2 * r * delta + delta ** 2))
+ a_acc += ds * gamma
+ # angle to vertical
+ alpha = np.arcsin((r + delta) / r * np.sin(beta))
h -= delta
r -= delta
- t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz)
+ t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(
+ h, rho_s, frequency_MHz,
+ )
delta = 0.0001 + 0.01 * max(h, 0)
- beta = np.arcsin(n/n_new * np.sin(alpha))
+ beta = np.arcsin(n / n_new * np.sin(alpha))
n = n_new
- t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz)
+ t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(
+ h, rho_s, frequency_MHz,
+ )
delta = .0001 + .01 * max(h, 0)
r = earth_radius_km + h
while True:
- ds = np.sqrt(r**2 * np.cos(beta)**2 + 2*r*delta + delta**2) - r * np.cos(beta)
+ ds = np.sqrt(
+ r ** 2 * np.cos(beta) ** 2 + 2 * r *
+ delta + delta ** 2,
+ ) - r * np.cos(beta)
a_acc += ds * gamma
- alpha = np.arcsin(r/(r+delta) * np.sin(beta))
+ alpha = np.arcsin(r / (r + delta) * np.sin(beta))
h += delta
if h >= 100:
break
r += delta
- t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(h, rho_s,
- frequency_MHz)
- beta = np.arcsin(n/n_new * np.sin(alpha))
+ t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(
+ h, rho_s, frequency_MHz,
+ )
+ beta = np.arcsin(n / n_new * np.sin(alpha))
n = n_new
self.atmospheric_loss.append(a_acc)
self.elevation_has_atmospheric_loss.append(apparent_elevation)
self.freq_has_atmospheric_loss.append(frequency_MHz)
- self.surf_water_dens_has_atmospheric_loss.append(surf_water_vapour_density)
+ self.surf_water_dens_has_atmospheric_loss.append(
+ surf_water_vapour_density,
+ )
return a_acc
@@ -141,7 +243,6 @@ def _get_beam_spreading_att(elevation, altitude, earth_to_space) -> np.array:
----------
elevation (np.array) : free space elevation (degrees)
altitude (float) : altitude of earth station (m)
- sat_params (ParametersFss) : parameters of satellite system
Returns
-------
@@ -151,173 +252,324 @@ def _get_beam_spreading_att(elevation, altitude, earth_to_space) -> np.array:
# calculate scintillation intensity, based on ITU-R P.618-12
altitude_km = altitude / 1000
- numerator = .5411 + .07446 * elevation + altitude_km * (.06272 + .0276 * elevation) \
- + altitude_km ** 2 * .008288
- denominator = (1.728 + .5411 * elevation + .03723 * elevation **2 +
- altitude_km * (.1815 + .06272 * elevation + .0138 * elevation ** 2) +
- (altitude_km ** 2) * (.01727 + .008288 * elevation))**2
+ numerator = .5411 + .07446 * elevation + altitude_km * \
+ (.06272 + .0276 * elevation) + altitude_km ** 2 * .008288
+ denominator = (
+ 1.728 + .5411 * elevation + .03723 * elevation ** 2 + altitude_km * (
+ .1815 + .06272 *
+ elevation + .0138 * elevation ** 2
+ ) + (altitude_km ** 2) * (.01727 + .008288 * elevation)
+ ) ** 2
- attenuation = 10 * np.log10(1 - numerator/denominator)
+ attenuation = 10 * np.log10(1 - numerator / denominator)
if earth_to_space:
attenuation = -attenuation
return attenuation
+ @classmethod
+ def apparent_elevation_angle(cls, elevation_deg: np.array, space_station_alt_m: float) -> np.array:
+ """Calculate apparent elevation angle according to ITU-R P619, Attachment B
+
+ Parameters
+ ----------
+ elevation_deg : np.array
+ free-space elevation angle
+ space_station_alt_m : float
+ space-station altitude
+
+ Returns
+ -------
+ np.array
+ apparent elevation angle
+ """
+ elev_angles_rad = np.deg2rad(elevation_deg)
+ tau_fs1 = 1.728 + 0.5411 * elev_angles_rad + 0.03723 * elev_angles_rad**2
+ tau_fs2 = 0.1815 + 0.06272 * elev_angles_rad + 0.01380 * elev_angles_rad**2
+ tau_fs3 = 0.01727 + 0.008288 * elev_angles_rad
+
+ # change in elevation angle due to refraction
+ tau_fs_deg = 1 / (
+ tau_fs1 + space_station_alt_m * tau_fs2 +
+ space_station_alt_m**2 * tau_fs3
+ )
+ tau_fs = tau_fs_deg / 180. * np.pi
+
+ return np.degrees(elev_angles_rad + tau_fs)
+
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper for the get_loss function that satisfies the ABS class interface
+
+ Calculates P.619 path loss between station_a and station_b stations.
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing station_a
+ station_b : StationManager
+ StationManager container representing station_b
+ params : Parameters
+ Simulation parameters needed for the propagation class.
- def get_loss(self, *args, **kwargs) -> np.array:
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ distance = station_a.get_3d_distance_to(station_b)
+ frequency = frequency * np.ones(distance.shape)
+ indoor_stations = np.tile(
+ station_b.indoor, (station_a.num_stations, 1),
+ )
+
+ # Elevation angles seen from the station on Earth.
+ elevation_angles = {}
+ if station_a.is_space_station:
+ elevation_angles["free_space"] = station_b.get_elevation(station_a)
+ earth_station_antenna_gain = station_b_gains
+ # if (station_b_gains.shape != distance.shape):
+ # raise ValueError(f"Invalid shape for station_b_gains = {station_b_gains.shape}")
+ elevation_angles["apparent"] = self.apparent_elevation_angle(
+ elevation_angles["free_space"],
+ station_a.height,
+ )
+ # Transpose it to fit the expected path loss shape
+ elevation_angles["free_space"] = np.transpose(elevation_angles["free_space"])
+ elevation_angles["apparent"] = np.transpose(elevation_angles["apparent"])
+ elif station_b.is_space_station:
+ elevation_angles["free_space"] = station_a.get_elevation(station_b)
+ earth_station_antenna_gain = station_a_gains
+ elevation_angles["apparent"] = self.apparent_elevation_angle(
+ elevation_angles["free_space"],
+ station_b.height,
+ )
+ else:
+ raise ValueError(
+ "PropagationP619: At least one station must be an space station",
+ )
+
+ # Determine the Earth-space path direction and if the interferer is single or multiple-entry.
+ # The get_loss interface won't tell us who is the interferer, so we need to get it from the parameters.
+ is_intra_imt = True if station_a.is_imt_station(
+ ) and station_b.is_imt_station() else False
+ if is_intra_imt:
+ # Intra-IMT
+ if params.general.imt_link == "UPLINK":
+ is_earth_to_space_link = True
+ else:
+ is_earth_to_space_link = False
+ # Intra-IMT is always multiple-entry interference
+ is_single_entry_interf = False
+ else:
+ # System-IMT
+ imt_station, sys_station = (
+ station_a, station_b,
+ ) if station_a.is_imt_station() else (station_b, station_a)
+ if params.imt.interfered_with:
+ is_earth_to_space_link = True if imt_station.is_space_station else False
+ if sys_station.num_stations > 1:
+ is_single_entry_interf = False
+ else:
+ is_single_entry_interf = True
+ else:
+ is_earth_to_space_link = False if imt_station.is_space_station else True
+ is_single_entry_interf = False
+ earth_station_height = station_b.height
+ mean_clutter_height = self.mean_clutter_height
+ below_rooftop = self.below_rooftop
+ loss = self.get_loss(
+ distance,
+ frequency,
+ indoor_stations,
+ elevation_angles,
+ is_earth_to_space_link,
+ earth_station_antenna_gain,
+ is_single_entry_interf,
+ earth_station_height,
+ mean_clutter_height,
+ below_rooftop,
+ )
+
+ return loss
+
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, dict, bool, np.ndarray, bool, np.ndarray, str, int)
+ def get_loss(
+ self,
+ distance: np.array,
+ frequency: np.array,
+ indoor_stations: np.array,
+ elevation: dict,
+ earth_to_space: bool,
+ earth_station_antenna_gain: np.array,
+ single_entry: bool,
+ earth_station_height: np.array,
+ mean_clutter_height: np.str_,
+ below_rooftop: float
+ ) -> np.array:
"""
Calculates path loss for earth-space link
Parameters
----------
- distance_3D (np.array) : distances between stations [m]
+ distance (np.array) : 3D distances between stations [m]
frequency (np.array) : center frequencies [MHz]
indoor_stations (np.array) : array indicating stations that are indoor
- elevation (dict) : elevation["apparent"](array): apparent elevation angles (degrees)
- elevation["free_space"](array): free-space elevation angles (degrees)
- sat_params (ParametersFss) : parameters of satellite system
- single_entry (bool): True for single-entry interference, False for multiple-entry (default = False)
- number_of_sectors (int): number of sectors in a node (default = 1)
+ elevation (dict) : dict containing free-space elevation angles (degrees), and aparent elevation angles
+ earth_to_space (bool): whether the ray direction is earth-to-space. Affects beam spreadding Attenuation.
+ single_entry (bool): True for single-entry interference, False for
+ multiple-entry (default = False)
+ earth_station_antenna_gain (np.array): Earth station atenna gain array.
Returns
-------
array with path loss values with dimensions of distance_3D
"""
- d = kwargs["distance_3D"]
- f = kwargs["frequency"]
- indoor_stations = kwargs["indoor_stations"]
- elevation = kwargs["elevation"]
- sat_params = kwargs["sat_params"]
- earth_to_space = kwargs["earth_to_space"]
- earth_station_antenna_gain = kwargs["earth_station_antenna_gain"]
- single_entry = kwargs.pop("single_entry", False)
- number_of_sectors = kwargs["number_of_sectors"]
-
- free_space_loss = self.free_space.get_loss(distance_3D=d, frequency=f)
-
- freq_set = np.unique(f)
+ free_space_loss = self.free_space.get_free_space_loss(
+ frequency=frequency, distance=distance,
+ )
+
+ freq_set = np.unique(frequency)
if len(freq_set) > 1:
error_message = "different frequencies not supported in P619"
raise ValueError(error_message)
- atmospheric_gasses_loss = self._get_atmospheric_gasses_loss(frequency_MHz=freq_set,
- apparent_elevation=np.mean(elevation["apparent"]),
- sat_params=sat_params)
- beam_spreading_attenuation = self._get_beam_spreading_att(elevation["free_space"],
- sat_params.imt_altitude,
- earth_to_space)
+ atmospheric_gasses_loss = self._get_atmospheric_gasses_loss(
+ frequency_MHz=freq_set,
+ apparent_elevation=np.mean(elevation["apparent"]),
+ )
+ beam_spreading_attenuation = self._get_beam_spreading_att(
+ elevation["free_space"],
+ self.earth_station_alt_m,
+ earth_to_space,
+ )
diffraction_loss = 0
if single_entry:
- elevation_sectors = np.repeat(elevation["free_space"], number_of_sectors)
- tropo_scintillation_loss = \
- self.scintillation.get_tropospheric_attenuation(elevation = elevation_sectors,
- antenna_gain_dB = earth_station_antenna_gain,
- frequency_MHz = freq_set,
- sat_params = sat_params)
-
- loss = (free_space_loss + self.depolarization_loss +
- atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss)
- loss = np.repeat(loss, number_of_sectors, 1) + tropo_scintillation_loss
+ tropo_scintillation_loss = self.scintillation.get_tropospheric_attenuation(
+ elevation=elevation["free_space"],
+ antenna_gain_dB=earth_station_antenna_gain,
+ frequency_MHz=freq_set,
+ earth_station_alt_m=self.earth_station_alt_m,
+ earth_station_lat_deg=self.earth_station_lat_deg,
+ season=self.season,
+ )
+
+ loss = \
+ free_space_loss + self.depolarization_loss + atmospheric_gasses_loss + beam_spreading_attenuation + \
+ diffraction_loss + tropo_scintillation_loss
else:
- clutter_loss = self.clutter.get_loss(frequency=f, distance=d,
- elevation=elevation["free_space"],
- station_type=StationType.FSS_SS)
- building_loss = self.building_entry.get_loss(f, elevation["apparent"]) * indoor_stations
-
- loss = (free_space_loss + clutter_loss + building_loss + self.polarization_mismatch_loss +
- atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss)
- loss = np.repeat(loss, number_of_sectors, 1)
+ clutter_loss = \
+ self.clutter.get_loss(
+ frequency=frequency,
+ distance=distance,
+ elevation=elevation["free_space"],
+ station_type=StationType.FSS_SS,
+ earth_station_height=earth_station_height,
+ mean_clutter_height=mean_clutter_height,
+ below_rooftop = below_rooftop,
+ )
+ building_loss = self.building_entry.get_loss(
+ frequency, elevation["apparent"],
+ ) * indoor_stations
+
+ loss = (
+ free_space_loss + clutter_loss + building_loss + self.polarization_mismatch_loss +
+ atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss
+ )
return loss
+
if __name__ == '__main__':
- from sharc.parameters.parameters import Parameters
+
+ import matplotlib
+ matplotlib.use('Agg')
+
import matplotlib.pyplot as plt
- import os
- params = Parameters()
+ # params = Parameters()
- propagation_path = os.getcwd()
- sharc_path = os.path.dirname(propagation_path)
- param_file = os.path.join(sharc_path, "parameters", "parameters.ini")
+ # propagation_path = os.getcwd()
+ # sharc_path = os.path.dirname(propagation_path)
+ # param_file = os.path.join(sharc_path, "parameters", "parameters.ini") deprecated
- params.set_file_name(param_file)
- params.read_params()
+ # params.set_file_name(param_file)
+ # params.read_params()
- sat_params = params.fss_ss
+ # sat_params = params.fss_ss
+
+ space_station_alt_m = 20000.0
+ earth_station_alt_m = 1000.0
+ earth_station_lat_deg = -15.7801
+ earth_station_long_diff_deg = 0.0
+ season = "SUMMER"
random_number_gen = np.random.RandomState(101)
- propagation = PropagationP619(random_number_gen)
+ propagation = PropagationP619(
+ random_number_gen=random_number_gen,
+ space_station_alt_m=space_station_alt_m,
+ earth_station_alt_m=earth_station_alt_m,
+ earth_station_lat_deg=earth_station_lat_deg,
+ earth_station_long_diff_deg=earth_station_long_diff_deg,
+ season=season,
+ )
##########################
# Plot atmospheric loss
# compare with benchmark from ITU-R P-619 Fig. 3
- frequency_MHz = 30000.
- sat_params.imt_altitude = 1000
+ frequency_MHz = 2680.
+ earth_station_alt_m = 1000
- apparent_elevation = range(-1, 90, 2)
+ apparent_elevation = np.linspace(0, 90, 91)
- loss_2_5 = np.zeros(len(apparent_elevation))
- loss_12_5 = np.zeros(len(apparent_elevation))
+ loss_false = np.zeros(len(apparent_elevation))
+ loss_true = np.zeros(len(apparent_elevation))
print("Plotting atmospheric loss:")
for index in range(len(apparent_elevation)):
- print("\tApparent Elevation: {} degrees".format(apparent_elevation[index]))
-
- surf_water_vapour_density = 2.5
- loss_2_5[index] = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz,
- apparent_elevation=apparent_elevation[index],
- sat_params=sat_params,
- surf_water_vapour_density=surf_water_vapour_density)
- surf_water_vapour_density = 12.5
- loss_12_5[index] = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz,
- apparent_elevation=apparent_elevation[index],
- sat_params=sat_params,
- surf_water_vapour_density=surf_water_vapour_density)
+ print(
+ "\tApparent Elevation: {} degrees".format(
+ apparent_elevation[index],
+ ),
+ )
+
+ surf_water_vapour_density = 14.121645412892681
+ table = False
+ loss_false[index] = propagation._get_atmospheric_gasses_loss(
+ frequency_MHz=frequency_MHz,
+ apparent_elevation=apparent_elevation[index],
+ surf_water_vapour_density=surf_water_vapour_density,
+ lookupTable=table,
+ )
+ table = True
+ loss_true[index] = propagation._get_atmospheric_gasses_loss(
+ frequency_MHz=frequency_MHz,
+ apparent_elevation=apparent_elevation[index],
+ surf_water_vapour_density=surf_water_vapour_density,
+ lookupTable=table,
+ )
plt.figure()
- plt.semilogy(apparent_elevation, loss_2_5, label='2.5 g/m^3')
- plt.semilogy(apparent_elevation, loss_12_5, label='12.5 g/m^3')
+ plt.semilogy(apparent_elevation, loss_false, label='No Table')
+ plt.semilogy(apparent_elevation, loss_true, label='With Table')
plt.grid(True)
-
-
plt.xlabel("apparent elevation (deg)")
plt.ylabel("Loss (dB)")
plt.title("Atmospheric Gasses Attenuation")
plt.legend()
- altitude_vec = np.arange(0, 6.1, .5) * 1000
- elevation_vec = np.array([0, .5, 1, 2, 3, 5])
- attenuation = np.empty([len(altitude_vec), len(elevation_vec)])
-
- #################################
- # Plot beam spreading attenuation
- # compare with benchmark from ITU-R P-619 Fig. 7
-
- earth_to_space = False
- print("Plotting beam spreading attenuation:")
-
- plt.figure()
- for index in range(len(altitude_vec)):
- attenuation[index, :] = propagation._get_beam_spreading_att(elevation_vec,
- altitude_vec[index],
- earth_to_space)
-
- handles = plt.plot(altitude_vec / 1000, np.abs(attenuation))
- plt.xlabel("altitude (km)")
- plt.ylabel("Attenuation (dB)")
- plt.title("Beam Spreading Attenuation")
-
- for line_handle, elevation in zip(handles, elevation_vec):
- line_handle.set_label("{}deg".format(elevation))
-
- plt.legend(title="Elevation")
-
- plt.grid(True)
-
-
- plt.show()
+ # Save the figures instead of showing them
+ plt.savefig("atmospheric_gasses_attenuation.png")
diff --git a/sharc/propagation/propagation_sat_simple.py b/sharc/propagation/propagation_sat_simple.py
index 30c9535cb..b2ebfd492 100644
--- a/sharc/propagation/propagation_sat_simple.py
+++ b/sharc/propagation/propagation_sat_simple.py
@@ -4,52 +4,143 @@
@author: edgar
"""
+import numpy as np
+from multipledispatch import dispatch
from sharc.propagation.propagation import Propagation
+from sharc.propagation.propagation_p619 import PropagationP619
from sharc.propagation.propagation_free_space import PropagationFreeSpace
from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss
from sharc.support.enumerations import StationType
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
-import numpy as np
class PropagationSatSimple(Propagation):
"""
Implements the simplified satellite propagation model
"""
+ # pylint: disable=function-redefined
+ # pylint: disable=arguments-renamed
- def __init__(self, random_number_gen: np.random.RandomState):
+ def __init__(self, random_number_gen: np.random.RandomState, enable_clutter_loss=True):
super().__init__(random_number_gen)
+ self.enable_clutter_loss = enable_clutter_loss
self.clutter = PropagationClutterLoss(random_number_gen)
self.free_space = PropagationFreeSpace(random_number_gen)
- self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen)
+ self.building_entry = PropagationBuildingEntryLoss(
+ self.random_number_gen,
+ )
self.atmospheric_loss = 0.75
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the PropagationUMi calc_loss method
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing IMT UE station - Station_type.IMT_UE
+ station_b : StationManager
+ StationManager container representing IMT BS stattion
+ params : Parameters
+ Simulation parameters needed for the propagation class - Station_type.IMT_BS
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ distance_3d = station_a.get_3d_distance_to(station_b)
+ frequency = frequency * np.ones(distance_3d.shape)
+ indoor_stations = np.tile(
+ station_b.indoor, (station_a.num_stations, 1),
+ )
+
+ # Elevation angles seen from the station on Earth.
+ elevation_angles = {}
+ if station_a.is_space_station:
+ elevation_angles["free_space"] = station_b.get_elevation(station_a)
+ # if (station_b_gains.shape != distance.shape):
+ # raise ValueError(f"Invalid shape for station_b_gains = {station_b_gains.shape}")
+ elevation_angles["apparent"] = PropagationP619.apparent_elevation_angle(
+ elevation_angles["free_space"],
+ station_a.height,
+ )
+ # Transpose it to fit the expected path loss shape
+ elevation_angles["free_space"] = np.transpose(elevation_angles["free_space"])
+ elevation_angles["apparent"] = np.transpose(elevation_angles["apparent"])
+ elif station_b.is_space_station:
+ elevation_angles["free_space"] = station_a.get_elevation(station_b)
+ elevation_angles["apparent"] = PropagationP619.apparent_elevation_angle(
+ elevation_angles["free_space"],
+ station_b.height,
+ )
+ else:
+ raise ValueError(
+ "PropagationP619: At least one station must be an space station",
+ )
+
+ return self.get_loss(distance_3d, frequency, indoor_stations, elevation_angles)
+
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, dict)
+ def get_loss(
+ self,
+ distance: np.array,
+ frequency: np.array,
+ indoor_stations: np.array,
+ elevation: dict,
+ ) -> np.array:
+ """Calculates the clutter loss.
- def get_loss(self, *args, **kwargs) -> np.array:
- d = kwargs["distance_3D"]
- f = kwargs["frequency"]
- indoor_stations = kwargs["indoor_stations"]
- elevation = kwargs["elevation"]
- number_of_sectors = kwargs.pop("number_of_sectors", 1)
- enable_clutter_loss = kwargs.pop("enable_clutter_loss", True)
+ Parameters
+ ----------
+ distance : np.array
+ Distance between the stations
+ frequency : np.array
+ Array of frequenciews
+ indoor_stations : np.array
+ Bool array indicating if the terrestrial station is indoor or not.
+ elevation : np.array
+ Array with elevation angles w.r.t terrestrial station
- free_space_loss = self.free_space.get_loss(distance_3D = d,
- frequency = f)
+ Returns
+ -------
+ np.array
+ Array of clutter losses with the same shape as distance
+ """
- if enable_clutter_loss:
- clutter_loss = np.maximum(0, self.clutter.get_loss(frequency = f,
- distance = d,
- elevation = elevation["free_space"],
- station_type = StationType.FSS_SS))
+ free_space_loss = self.free_space.get_free_space_loss(
+ distance=distance, frequency=frequency,
+ )
+
+ if self.enable_clutter_loss:
+ clutter_loss = np.maximum(
+ 0, self.clutter.get_loss(
+ frequency=frequency,
+ distance=distance,
+ elevation=elevation["free_space"],
+ station_type=StationType.FSS_SS,
+ ),
+ )
else:
clutter_loss = 0
- building_loss = self.building_entry.get_loss(f, elevation["apparent"]) * indoor_stations
+ building_loss = self.building_entry.get_loss(
+ frequency, elevation["apparent"],
+ ) * indoor_stations
loss = free_space_loss + clutter_loss + building_loss + self.atmospheric_loss
- if number_of_sectors > 1:
- loss = np.repeat(loss, number_of_sectors, 1)
-
return loss
diff --git a/sharc/propagation/propagation_ter_simple.py b/sharc/propagation/propagation_ter_simple.py
index d10de6b1d..5d4321f98 100644
--- a/sharc/propagation/propagation_ter_simple.py
+++ b/sharc/propagation/propagation_ter_simple.py
@@ -4,15 +4,20 @@
@author: edgar
"""
+import numpy as np
+from multipledispatch import dispatch
from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
from sharc.propagation.propagation_free_space import PropagationFreeSpace
from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
from sharc.support.enumerations import StationType
-import numpy as np
class PropagationTerSimple(Propagation):
+ # pylint: disable=function-redefined
+ # pylint: disable=arguments-renamed
"""
Implements the simplified terrestrial propagation model, which is the
basic free space and additional clutter losses
@@ -24,30 +29,92 @@ def __init__(self, random_number_gen: np.random.RandomState):
self.free_space = PropagationFreeSpace(np.random.RandomState(101))
self.building_loss = 20
-
- def get_loss(self, *args, **kwargs) -> np.array:
- if "distance_2D" in kwargs:
- d = kwargs["distance_2D"]
- else:
- d = kwargs["distance_3D"]
-
- f = kwargs["frequency"]
- p = kwargs.pop("loc_percentage", "RANDOM")
- indoor_stations = kwargs["indoor_stations"]
- number_of_sectors = kwargs.pop("number_of_sectors",1)
-
- free_space_loss = self.free_space.get_loss(distance_2D=d,
- frequency=f)
-
- clutter_loss = self.clutter.get_loss(frequency=f,
- distance=d,
- loc_percentage=p,
- station_type=StationType.FSS_ES)
-
- building_loss = self.building_loss*indoor_stations
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the get_loss method to fit the Propagation ABC class interface
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ params : Parameters
+ Simulation parameters needed for the propagation class
+ frequency: float
+ Center frequency
+ station_a : StationManager
+ StationManager container representing the system station
+ station_b : StationManager
+ StationManager container representing the IMT station
+ station_a_gains: np.ndarray defaults to None
+ Not used
+ station_b_gains: np.ndarray defaults to None
+ Not used
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ distance = station_a.get_3d_distance_to(station_b)
+ frequency_array = frequency * np.ones(distance.shape)
+ indoor_stations = np.tile(
+ station_b.indoor, (station_a.num_stations, 1),
+ )
+
+ return self.get_loss(distance, frequency_array, indoor_stations, -1.0)
+
+ # pylint: disable=arguments-differ
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, float)
+ def get_loss(
+ self, distance: np.ndarray, frequency: np.ndarray, indoor_stations: np.ndarray,
+ loc_percentage: float,
+ ) -> np.array:
+ """Calculates loss with a simple terrestrial model:
+ * Building loss is set statically in class construction
+ * Clutter loss is calculated using P.2108 standard
+ * Free-space loss
+
+ Parameters
+ ----------
+ distance : np.ndarray
+ distances array between stations
+ frequency : np.ndarray
+ frequency
+ indoor_stations: np.ndarray
+ array of bool indicating if station n is indoor
+ loc_percentage : str, optional
+ Percentage locations range [0, 1[. If a negative number is given
+ a random percentage is used.
+
+ Returns
+ -------
+ np.array
+ array of losses with distance dimentions
+ """
+ free_space_loss = self.free_space.get_free_space_loss(
+ frequency, distance,
+ )
+ if loc_percentage < 0:
+ loc_percentage = "RANDOM"
+
+ clutter_loss = self.clutter.get_loss(
+ frequency=frequency,
+ distance=distance,
+ loc_percentage=loc_percentage,
+ station_type=StationType.FSS_ES,
+ )
+
+ building_loss = self.building_loss * indoor_stations
loss = free_space_loss + building_loss + clutter_loss
- loss = np.repeat(loss, number_of_sectors, 1)
return loss
@@ -60,25 +127,24 @@ def get_loss(self, *args, **kwargs) -> np.array:
# Print path loss for TerrestrialSimple and Free Space
d = np.linspace(10, 10000, num=10000)
- freq = 27000*np.ones(d.shape)
- indoor_stations = np.zeros(d.shape, dtype = bool)
+ freq = 27000 * np.ones(d.shape)
+ indoor_stations = np.zeros(d.shape, dtype=bool)
loc_percentage = 0.5
free_space = PropagationFreeSpace(np.random.RandomState(101))
ter_simple = PropagationTerSimple(np.random.RandomState(101))
- loss_ter = ter_simple.get_loss(distance_2D = d,
- frequency = freq,
- loc_percentage = loc_percentage,
- indoor_stations = indoor_stations)
+ loss_ter = ter_simple.get_loss(d, freq, indoor_stations, loc_percentage)
- loss_fs = free_space.get_loss(distance_2D = d,
- frequency = freq)
+ loss_fs = free_space.get_free_space_loss(freq, d)
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
- plt.semilogx(np.squeeze(d), np.squeeze(loss_fs), label = "free space")
- plt.semilogx(np.squeeze(d), np.squeeze(loss_ter), label = "free space + clutter loss")
+ plt.semilogx(np.squeeze(d), np.squeeze(loss_fs), label="free space")
+ plt.semilogx(
+ np.squeeze(d), np.squeeze(loss_ter),
+ label="free space + clutter loss",
+ )
plt.title("Free space with additional median clutter loss ($f=27GHz$)")
plt.xlabel("distance [m]")
diff --git a/sharc/propagation/propagation_tvro.py b/sharc/propagation/propagation_tvro.py
index 2be92463e..945740801 100644
--- a/sharc/propagation/propagation_tvro.py
+++ b/sharc/propagation/propagation_tvro.py
@@ -5,12 +5,16 @@
@author: edgar
"""
+import numpy as np
+import matplotlib.pyplot as plt
+
from sharc.support.enumerations import StationType
from sharc.propagation.propagation import Propagation
from sharc.propagation.propagation_free_space import PropagationFreeSpace
+from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
-import numpy as np
-import matplotlib.pyplot as plt
class PropagationTvro(Propagation):
"""
@@ -20,23 +24,101 @@ class PropagationTvro(Propagation):
TODO: calculate the effective environment height for the generic case
"""
- def __init__(self,
- random_number_gen: np.random.RandomState,
- environment : str):
+ def __init__(
+ self,
+ random_number_gen: np.random.RandomState,
+ environment: str,
+ ):
super().__init__(random_number_gen)
if environment.upper() == "URBAN":
- self.d_k = 0.02 #km
+ self.d_k = 0.02 # km
self.shadowing_std = 6
self.h_a = 20
elif environment.upper() == "SUBURBAN":
- self.d_k = 0.025 #km
+ self.d_k = 0.025 # km
self.shadowing_std = 8
self.h_a = 9
self.building_loss = 20
self.free_space_path_loss = PropagationFreeSpace(random_number_gen)
- def get_loss(self, *args, **kwargs) -> np.array:
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the PropagationUMi get_loss method
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing IMT UE station - Station_type.IMT_UE for IMT-IMT links or Sistem
+ station for IMT-System links
+ station_b : StationManager
+ StationManager container representing IMT BS stattion
+ params : Parameters
+ Simulation parameters needed for the propagation class - Station_type.IMT_BS
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ wrap_around_enabled = False
+ if params.imt.topology.type == "MACROCELL":
+ wrap_around_enabled = params.imt.topology.macrocell.wrap_around \
+ and params.imt.topology.macrocell.num_clusters == 1
+ if params.imt.topology.type == "HOTSPOT":
+ wrap_around_enabled = params.imt.topology.hotspot.wrap_around \
+ and params.imt.topology.hotspot.num_clusters == 1
+
+ if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()):
+ distances_2d, distances_3d, _, _ = \
+ station_a.get_dist_angles_wrap_around(station_b)
+ else:
+ distances_2d = station_a.get_distance_to(station_b)
+ distances_3d = station_a.get_3d_distance_to(station_b)
+
+ indoor_stations = np.tile(station_a.indoor, (station_b.num_stations, 1)).transpose()
+
+ # Use the right interface whether the link is IMT-IMT or IMT-System
+ # TODO: Refactor __get_loss and get rid of that if-else.
+ if station_a.is_imt_station() and station_b.is_imt_station():
+ loss = self._get_loss(
+ distance_3D=distances_3d,
+ distance_2D=distances_2d,
+ frequency=frequency * np.ones(distances_2d.shape),
+ bs_height=station_b.height,
+ ue_height=station_a.height,
+ indoor_stations=indoor_stations
+ )
+ else:
+ imt_station, sys_station = (station_a, station_b) \
+ if station_a.is_imt_station() else (station_b, station_a)
+ loss = self._get_loss(
+ distance_3D=distances_3d,
+ distance_2D=distances_2d,
+ frequency=frequency * np.ones(distances_2d.shape),
+ bs_height=station_b.height,
+ imt_sta_type=imt_station.station_type,
+ imt_x=imt_station.x,
+ imt_y=imt_station.y,
+ imt_z=imt_station.height,
+ es_x=sys_station.x,
+ es_y=sys_station.y,
+ es_z=sys_station.height,
+ indoor_stations=indoor_stations
+ )
+
+ return loss
+
+ def _get_loss(self, *args, **kwargs) -> np.array:
"""
Calculates path loss
@@ -55,100 +137,118 @@ def get_loss(self, *args, **kwargs) -> np.array:
frequency = kwargs["frequency"]
# shadowing is enabled by default
shadowing = kwargs.pop("shadowing", True)
- number_of_sectors = kwargs.pop("number_of_sectors",1)
indoor_stations = kwargs["indoor_stations"]
+
if "imt_sta_type" in kwargs.keys():
# calculating path loss for the IMT-system link
height = kwargs["es_z"]
# check if IMT staton is BS or UE
imt_sta_type = kwargs["imt_sta_type"]
if imt_sta_type is StationType.IMT_BS:
- loss = self.get_loss_macrocell(distance_3D,
- frequency,
- height,
- indoor_stations,
- shadowing)
+ loss = self.get_loss_macrocell(
+ distance_3D,
+ frequency,
+ height,
+ indoor_stations,
+ shadowing,
+ )
else:
- loss = self.get_loss_microcell(distance_3D,
- frequency,
- indoor_stations,
- shadowing)
- pass
+ loss = self.get_loss_microcell(
+ distance_3D,
+ frequency,
+ indoor_stations,
+ shadowing,
+ )
else:
# calculating path loss for the IMT-IMT link
height = kwargs["ue_height"]
- loss = self.get_loss_macrocell(distance_3D,
- frequency,
- height,
- indoor_stations,
- shadowing)
- if number_of_sectors > 1:
- loss = np.repeat(loss, number_of_sectors, 1)
-
+ loss = self.get_loss_macrocell(
+ distance_3D,
+ frequency,
+ height,
+ indoor_stations,
+ shadowing,
+ )
+
return loss
- def get_loss_microcell(self,
- distance_3D : np.array,
- frequency : np.array,
- indoor_stations : np.array,
- shadowing) -> np.array:
- pl_los = 102.93 + 20*np.log10(distance_3D/1000)
- pl_nlos = 153.5 + 40*np.log10(distance_3D/1000)
+ def get_loss_microcell(
+ self,
+ distance_3D: np.array,
+ frequency: np.array,
+ indoor_stations: np.array,
+ shadowing,
+ ) -> np.array:
+ pl_los = 102.93 + 20 * np.log10(distance_3D / 1000)
+ pl_nlos = 153.5 + 40 * np.log10(distance_3D / 1000)
pr_los = self.get_los_probability(distance_3D)
- loss = pl_los*pr_los + pl_nlos*(1 - pr_los)
-
+ loss = pl_los * pr_los + pl_nlos * (1 - pr_los)
+
if shadowing:
- shadowing_fading = self.random_number_gen.normal(0,
- 3.89,
- loss.shape)
- loss = loss + shadowing_fading
-
- loss = loss + self.building_loss*indoor_stations
-
- free_space_path_loss = self.free_space_path_loss.get_loss(distance_3D = distance_3D,
- frequency = frequency)
+ shadowing_fading = self.random_number_gen.normal(
+ 0,
+ 3.89,
+ loss.shape,
+ )
+ loss = loss + shadowing_fading
+
+ loss = loss + self.building_loss * indoor_stations
+
+ free_space_path_loss = self.free_space_path_loss.get_free_space_loss(
+ distance=distance_3D,
+ frequency=frequency,
+ )
loss = np.maximum(loss, free_space_path_loss)
- return loss
-
- def get_loss_macrocell(self,
- distance_3D : np.array,
- frequency : np.array,
- height : np.array,
- indoor_stations : np.array,
- shadowing : bool) -> np.array:
-
- free_space_path_loss = self.free_space_path_loss.get_loss(distance_3D = distance_3D,
- frequency = frequency)
+ return loss
- f_fc = .25 + .375*(1 + np.tanh(7.5*(frequency/1000 - .5)))
+ def get_loss_macrocell(
+ self,
+ distance_3D: np.array,
+ frequency: np.array,
+ height: np.array,
+ indoor_stations: np.array,
+ shadowing: bool,
+ ) -> np.array:
+
+ free_space_path_loss = self.free_space_path_loss.get_free_space_loss(
+ distance=distance_3D,
+ frequency=frequency,
+ )
+
+ f_fc = .25 + .375 * (1 + np.tanh(7.5 * (frequency / 1000 - .5)))
clutter_loss = 10.25 * f_fc * np.exp(-self.d_k) * \
- (1 - np.tanh(6*(height/self.h_a - .625))) - .33
+ (1 - np.tanh(6 * (height[:, np.newaxis] / self.h_a - .625))) - .33
loss = free_space_path_loss.copy()
indices = (distance_3D >= 40) & (distance_3D < 10 * self.d_k * 1000)
- loss[indices] = loss[indices] + (distance_3D[indices]/1000 - 0.04)/(10*self.d_k - 0.04) * clutter_loss[indices]
+ loss[indices] = loss[indices] + \
+ (distance_3D[indices] / 1000 - 0.04) / \
+ (10 * self.d_k - 0.04) * clutter_loss[indices]
indices = (distance_3D >= 10 * self.d_k * 1000)
loss[indices] = loss[indices] + clutter_loss[indices]
- loss = loss + self.building_loss*indoor_stations
+ loss = loss + self.building_loss * indoor_stations
if shadowing:
- shadowing_fading = self.random_number_gen.normal(0,
- self.shadowing_std,
- loss.shape)
- loss = loss + shadowing_fading
+ shadowing_fading = self.random_number_gen.normal(
+ 0,
+ self.shadowing_std,
+ loss.shape,
+ )
+ loss = loss + shadowing_fading
loss = np.maximum(loss, free_space_path_loss)
return loss
-
- def get_los_probability(self,
- distance : np.array,
- distance_transition : float = 70) -> np.array:
+ def get_los_probability(
+ self,
+ distance: np.array,
+ distance_transition: float = 70,
+ ) -> np.array:
"""
Returns the line-of-sight (LOS) probability
@@ -161,18 +261,18 @@ def get_los_probability(self,
-------
LOS probability as a numpy array with same length as distance
"""
- p_los = 1/(1 + (1/np.exp(-0.1*(distance - distance_transition))))
+ p_los = 1 / (1 + (1 / np.exp(-0.1 * (distance - distance_transition))))
return p_los
if __name__ == '__main__':
- distance_2D = np.linspace(10, 1000, num=1000)[:,np.newaxis]
- frequency = 3600*np.ones(distance_2D.shape)
- h_bs = 25*np.ones(len(distance_2D[:,0]))
- h_ue = 1.5*np.ones(len(distance_2D[0,:]))
+ distance_2D = np.linspace(10, 1000, num=1000)[:, np.newaxis]
+ frequency = 3600 * np.ones(distance_2D.shape)
+ h_bs = 25 * np.ones(len(distance_2D[:, 0]))
+ h_ue = 1.5 * np.ones(len(distance_2D[0, :]))
h_tvro = 6
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- indoor_stations = np.zeros(distance_3D.shape, dtype = bool)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2)
+ indoor_stations = np.zeros(distance_3D.shape, dtype=bool)
shadowing = False
rand_gen = np.random.RandomState(101)
@@ -180,74 +280,101 @@ def get_los_probability(self,
prop_suburban = PropagationTvro(rand_gen, "SUBURBAN")
prop_free_space = PropagationFreeSpace(rand_gen)
- loss_urban_bs_ue = prop_urban.get_loss(distance_3D = distance_3D,
- frequency = frequency,
- indoor_stations = indoor_stations,
- shadowing = shadowing,
- ue_height = h_ue)
- loss_suburban_bs_ue = prop_suburban.get_loss(distance_3D = distance_3D,
- frequency = frequency,
- indoor_stations = indoor_stations,
- shadowing = shadowing,
- ue_height = h_ue)
-
- loss_urban_bs_tvro = prop_urban.get_loss(distance_3D = distance_3D,
- frequency = frequency,
- indoor_stations = indoor_stations,
- shadowing = shadowing,
- imt_sta_type = StationType.IMT_BS,
- es_z = h_tvro)
- loss_suburban_bs_tvro = prop_suburban.get_loss(distance_3D = distance_3D,
- frequency = frequency,
- indoor_stations = indoor_stations,
- shadowing = shadowing,
- imt_sta_type = StationType.IMT_BS,
- es_z = h_tvro)
-
- loss_ue_tvro = prop_urban.get_loss(distance_3D = distance_3D,
- frequency = frequency,
- indoor_stations = indoor_stations,
- imt_sta_type = StationType.IMT_UE,
- shadowing = shadowing,
- es_z = h_tvro)
-
- loss_fs = prop_free_space.get_loss(distance_3D = distance_3D,
- frequency = frequency)
-
- fig = plt.figure(figsize=(7,5), facecolor='w', edgecolor='k')
+ loss_urban_bs_ue = prop_urban._get_loss(
+ distance_3D=distance_3D,
+ frequency=frequency,
+ indoor_stations=indoor_stations,
+ shadowing=shadowing,
+ ue_height=h_ue,
+ )
+ loss_suburban_bs_ue = prop_suburban._get_loss(
+ distance_3D=distance_3D,
+ frequency=frequency,
+ indoor_stations=indoor_stations,
+ shadowing=shadowing,
+ ue_height=h_ue,
+ )
+
+ loss_urban_bs_tvro = prop_urban._get_loss(
+ distance_3D=distance_3D,
+ frequency=frequency,
+ indoor_stations=indoor_stations,
+ shadowing=shadowing,
+ imt_sta_type=StationType.IMT_BS,
+ es_z=h_tvro,
+ )
+ loss_suburban_bs_tvro = prop_suburban._get_loss(
+ distance_3D=distance_3D,
+ frequency=frequency,
+ indoor_stations=indoor_stations,
+ shadowing=shadowing,
+ imt_sta_type=StationType.IMT_BS,
+ es_z=h_tvro,
+ )
+
+ loss_ue_tvro = prop_urban._get_loss(
+ distance_3D=distance_3D,
+ frequency=frequency,
+ indoor_stations=indoor_stations,
+ imt_sta_type=StationType.IMT_UE,
+ shadowing=shadowing,
+ es_z=h_tvro,
+ )
+
+ loss_fs = prop_free_space.get_free_space_loss(
+ distance=distance_3D,
+ frequency=frequency,
+ )
+
+ fig = plt.figure(figsize=(7, 5), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.semilogx(distance_3D, loss_urban_bs_tvro, "-r", label = "urban, BS-to-TVRO", linewidth = 1)
- ax.semilogx(distance_3D, loss_suburban_bs_tvro, "--r", label = "suburban, BS-to-TVRO", linewidth = 1)
- ax.semilogx(distance_3D, loss_urban_bs_ue, "-b", label = "urban, BS-to-UE", linewidth = 1)
- ax.semilogx(distance_3D, loss_suburban_bs_ue, "--b", label = "suburban, BS-to-UE", linewidth = 1)
- ax.semilogx(distance_3D, loss_ue_tvro, "-.y", label = "UE-to-TVRO", linewidth = 1)
- ax.semilogx(distance_3D, loss_fs, "-g", label = "free space", linewidth = 1.5)
+ ax.semilogx(
+ distance_3D, loss_urban_bs_tvro, "-r",
+ label="urban, BS-to-TVRO", linewidth=1,
+ )
+ ax.semilogx(
+ distance_3D, loss_suburban_bs_tvro, "--r",
+ label="suburban, BS-to-TVRO", linewidth=1,
+ )
+ ax.semilogx(
+ distance_3D, loss_urban_bs_ue, "-b",
+ label="urban, BS-to-UE", linewidth=1,
+ )
+ ax.semilogx(
+ distance_3D, loss_suburban_bs_ue, "--b",
+ label="suburban, BS-to-UE", linewidth=1,
+ )
+ ax.semilogx(
+ distance_3D, loss_ue_tvro, "-.y",
+ label="UE-to-TVRO", linewidth=1,
+ )
+ ax.semilogx(distance_3D, loss_fs, "-g", label="free space", linewidth=1.5)
plt.title("Path loss (no shadowing)")
plt.xlabel("distance [m]")
plt.ylabel("path loss [dB]")
- plt.xlim((distance_3D[0,0], distance_3D[-1,0]))
+ plt.xlim((distance_3D[0, 0], distance_3D[-1, 0]))
plt.ylim((70, 140))
plt.legend(loc="upper left")
plt.tight_layout()
plt.grid()
- #plt.show()
+ # plt.show()
###########################################################################
p_los = prop_urban.get_los_probability(distance_3D)
- fig = plt.figure(figsize=(7,5), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(7, 5), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.semilogy(distance_3D, p_los, "-r", linewidth = 1)
+ ax.semilogy(distance_3D, p_los, "-r", linewidth=1)
plt.title("LOS probability")
plt.xlabel("distance [m]")
plt.ylabel("probability")
- plt.xlim((distance_3D[0,0], 200))
+ plt.xlim((distance_3D[0, 0], 200))
plt.ylim((1e-6, 1))
plt.tight_layout()
plt.grid()
- plt.show()
\ No newline at end of file
+ plt.show()
diff --git a/sharc/propagation/propagation_uma.py b/sharc/propagation/propagation_uma.py
index 97ff361a2..5114c466a 100644
--- a/sharc/propagation/propagation_uma.py
+++ b/sharc/propagation/propagation_uma.py
@@ -5,11 +5,15 @@
@author: edgar
"""
-from sharc.propagation.propagation import Propagation
-
import numpy as np
import matplotlib.pyplot as plt
from cycler import cycler
+from multipledispatch import dispatch
+
+from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
+
class PropagationUMa(Propagation):
"""
@@ -17,8 +21,66 @@ class PropagationUMa(Propagation):
to 3GPP TR 38.900 v14.2.0.
TODO: calculate the effective environment height for the generic case
"""
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the PropagationUMa get_loss method
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing IMT UE station - Station_type.IMT_UE
+ station_b : StationManager
+ StationManager container representing IMT BS stattion
+ params : Parameters
+ Simulation parameters needed for the propagation class - Station_type.IMT_BS
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ wrap_around_enabled = False
+ if params.imt.topology.type == "MACROCELL":
+ wrap_around_enabled = params.imt.topology.macrocell.wrap_around \
+ and params.imt.topology.macrocell.num_clusters == 1
+ if params.imt.topology.type == "HOTSPOT":
+ wrap_around_enabled = params.imt.topology.hotspot.wrap_around \
+ and params.imt.topology.hotspot.num_clusters == 1
+
+ if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()):
+ distances_2d, distances_3d, _, _ = \
+ station_a.get_dist_angles_wrap_around(station_b)
+ else:
+ distances_2d = station_a.get_distance_to(station_b)
+ distances_3d = station_a.get_3d_distance_to(station_b)
+
+ loss = self.get_loss(
+ distances_3d,
+ distances_2d,
+ frequency * np.ones(distances_2d.shape),
+ station_b.height,
+ station_a.height,
+ params.imt.shadowing,
+ )
+
+ # the interface expects station_a.num_stations x station_b.num_stations array
+ return loss
- def get_loss(self, *args, **kwargs) -> np.array:
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool)
+ def get_loss(
+ self, distance_3d: np.array, distance_2d: np.array, frequency: np.array,
+ bs_height: np.array, ue_height: np.array, shadowing: bool,
+ ) -> np.array:
"""
Calculates path loss for LOS and NLOS cases with respective shadowing
(if shadowing is to be added)
@@ -37,44 +99,42 @@ def get_loss(self, *args, **kwargs) -> np.array:
array with path loss values with dimensions of distance_2D
"""
- d_3D = kwargs["distance_3D"]
- d_2D = kwargs["distance_2D"]
- f = kwargs["frequency"]
- h_bs = kwargs["bs_height"]
- h_ue = kwargs["ue_height"]
- h_e = np.ones(d_2D.shape)
- std = kwargs["shadowing"]
-
- if std:
+ h_e = np.ones(distance_2d.shape)
+ if shadowing:
shadowing_los = 4
shadowing_nlos = 6
else:
shadowing_los = 0
shadowing_nlos = 0
- los_probability = self.get_los_probability(d_2D, h_ue)
+ los_probability = self.get_los_probability(distance_2d, ue_height)
los_condition = self.get_los_condition(los_probability)
i_los = np.where(los_condition == True)[:2]
i_nlos = np.where(los_condition == False)[:2]
- loss = np.empty(d_2D.shape)
+ loss = np.empty(distance_2d.shape)
if len(i_los[0]):
- loss_los = self.get_loss_los(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_los)
+ loss_los = self.get_loss_los(
+ distance_2d, distance_3d, frequency, bs_height, ue_height, h_e, shadowing_los,
+ )
loss[i_los] = loss_los[i_los]
if len(i_nlos[0]):
- loss_nlos = self.get_loss_nlos(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_nlos)
+ loss_nlos = self.get_loss_nlos(
+ distance_2d, distance_3d, frequency, bs_height, ue_height, h_e, shadowing_nlos,
+ )
loss[i_nlos] = loss_nlos[i_nlos]
return loss
-
- def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
- frequency: np.array,
- h_bs: np.array, h_ue: np.array, h_e: np.array,
- shadowing_std=4):
+ def get_loss_los(
+ self, distance_2D: np.array, distance_3D: np.array,
+ frequency: np.array,
+ h_bs: np.array, h_ue: np.array, h_e: np.array,
+ shadowing_std=4,
+ ):
"""
Calculates path loss for the LOS (line-of-sight) case.
@@ -88,7 +148,9 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
h_bs : array of base stations antenna heights [m]
h_ue : array of user equipments antenna heights [m]
"""
- breakpoint_distance = self.get_breakpoint_distance(frequency, h_bs, h_ue, h_e)
+ breakpoint_distance = self.get_breakpoint_distance(
+ frequency, h_bs, h_ue, h_e,
+ )
# get index where distance if less than breakpoint
idl = np.where(distance_2D < breakpoint_distance)
@@ -99,25 +161,33 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
loss = np.empty(distance_2D.shape)
if len(idl[0]):
- loss[idl] = 20*np.log10(distance_3D[idl]) + 20*np.log10(frequency[idl]) - 27.55
+ loss[idl] = 20 * np.log10(distance_3D[idl]) + \
+ 20 * np.log10(frequency[idl]) - 27.55
if len(idg[0]):
- fitting_term = -10*np.log10(breakpoint_distance**2 +(h_bs[:,np.newaxis] - h_ue)**2)
- loss[idg] = 40*np.log10(distance_3D[idg]) + 20*np.log10(frequency[idg]) - 27.55 \
+ fitting_term = -10 * \
+ np.log10(
+ breakpoint_distance**2 +
+ (h_bs - h_ue[:, np.newaxis])**2,
+ )
+ loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * np.log10(frequency[idg]) - 27.55 \
+ fitting_term[idg]
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_2D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_2D.shape,
+ )
else:
shadowing = 0
return loss + shadowing
-
- def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array,
- frequency: np.array,
- h_bs: np.array, h_ue: np.array, h_e: np.array,
- shadowing_std=6):
+ def get_loss_nlos(
+ self, distance_2D: np.array, distance_3D: np.array,
+ frequency: np.array,
+ h_bs: np.array, h_ue: np.array, h_e: np.array,
+ shadowing_std=6,
+ ):
"""
Calculates path loss for the NLOS (non line-of-sight) case.
@@ -130,24 +200,26 @@ def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array,
frequency : center frequency [MHz]
h_bs : array of base stations antenna heights [m]
h_ue : array of user equipments antenna heights [m] """
- loss_nlos = -46.46 + 39.08*np.log10(distance_3D) + 20*np.log10(frequency) \
- - 0.6*(h_ue - 1.5)
+ loss_nlos = -46.46 + 39.08 * np.log10(distance_3D) + 20 * np.log10(frequency) \
+ - 0.6 * (h_ue[:, np.newaxis] - 1.5)
idl = np.where(distance_2D < 5000)
if len(idl[0]):
- loss_los = self.get_loss_los(distance_2D, distance_3D,
- frequency, h_bs, h_ue, h_e, 0)
+ loss_los = self.get_loss_los(
+ distance_2D, distance_3D,
+ frequency, h_bs, h_ue, h_e, 0,
+ )
loss_nlos[idl] = np.maximum(loss_los[idl], loss_nlos[idl])
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_3D.shape,
+ )
else:
shadowing = 0
return loss_nlos + shadowing
-
-
def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.array, h_e: np.array) -> float:
"""
Calculates the breakpoint distance for the LOS (line-of-sight) case.
@@ -164,14 +236,14 @@ def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.arr
array of breakpoint distances [m]
"""
# calculate the effective antenna heights
- h_bs_eff = h_bs[:,np.newaxis] - h_e
- h_ue_eff = h_ue - h_e
+ h_bs_eff = h_bs - h_e
+ h_ue_eff = h_ue[:, np.newaxis] - h_e
# calculate the breakpoint distance
- breakpoint_distance = 4*h_bs_eff*h_ue_eff*(frequency*1e6)/(3e8)
+ breakpoint_distance = 4 * h_bs_eff * \
+ h_ue_eff * (frequency * 1e6) / (3e8)
return breakpoint_distance
-
def get_los_condition(self, p_los: np.array) -> np.array:
"""
Evaluates if user equipments are LOS (True) of NLOS (False).
@@ -185,10 +257,11 @@ def get_los_condition(self, p_los: np.array) -> np.array:
An array with True or False if user equipments are in LOS of NLOS
condition, respectively.
"""
- los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los
+ los_condition = self.random_number_gen.random_sample(
+ p_los.shape,
+ ) < p_los
return los_condition
-
def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array:
"""
Returns the line-of-sight (LOS) probability
@@ -208,12 +281,12 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array
idc = np.where(h_ue > 13)[0]
if len(idc):
- c_prime[:,idc] = np.power((h_ue[idc] - 13)/10, 1.5)
+ c_prime[:, idc] = np.power((h_ue[idc] - 13) / 10, 1.5)
p_los = np.ones(distance_2D.shape)
idl = np.where(distance_2D > 18)
- p_los[idl] = (18/distance_2D[idl] + np.exp(-distance_2D[idl]/63)*(1-18/distance_2D[idl])) \
- *(1 + 1.25*c_prime[idl]*np.power(distance_2D[idl]/100, 3)*np.exp(-distance_2D[idl]/150))
+ p_los[idl] = (18 / distance_2D[idl] + np.exp(-distance_2D[idl] / 63) * (1 - 18 / distance_2D[idl])) \
+ * (1 + 1.25 * c_prime[idl] * np.power(distance_2D[idl] / 100, 3) * np.exp(-distance_2D[idl] / 150))
return p_los
@@ -222,9 +295,13 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array
###########################################################################
# Print LOS probability
- distance_2D = np.column_stack((np.linspace(1, 10000, num=10000)[:,np.newaxis],
- np.linspace(1, 10000, num=10000)[:,np.newaxis],
- np.linspace(1, 10000, num=10000)[:,np.newaxis]))
+ distance_2D = np.column_stack((
+ np.linspace(1, 10000, num=10000)[:, np.newaxis],
+ np.linspace(1, 10000, num=10000)[
+ :, np.newaxis,
+ ],
+ np.linspace(1, 10000, num=10000)[:, np.newaxis],
+ ))
h_ue = np.array([1.5, 17, 23])
uma = PropagationUMa(np.random.RandomState(101))
@@ -233,18 +310,18 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array
los_probability = uma.get_los_probability(distance_2D, h_ue)
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y']))
for h in range(len(h_ue)):
name.append("$h_u = {:4.1f}$ $m$".format(h_ue[h]))
- ax.loglog(distance_2D[:,h], los_probability[:,h], label=name[h])
+ ax.loglog(distance_2D[:, h], los_probability[:, h], label=name[h])
plt.title("UMa - LOS probability")
plt.xlabel("distance [m]")
plt.ylabel("probability")
- plt.xlim((0, distance_2D[-1,0]))
+ plt.xlim((0, distance_2D[-1, 0]))
plt.ylim((0, 1.1))
plt.legend(loc="lower left")
plt.tight_layout()
@@ -254,30 +331,39 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array
# Print path loss for UMa-LOS, UMa-NLOS and Free Space
from propagation_free_space import PropagationFreeSpace
shadowing_std = 0
- distance_2D = np.linspace(1, 10000, num=10000)[:,np.newaxis]
- freq = 27000*np.ones(distance_2D.shape)
- h_bs = 25*np.ones(len(distance_2D[:,0]))
- h_ue = 1.5*np.ones(len(distance_2D[0,:]))
- h_e = np.zeros(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
-
- loss_los = uma.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- loss_nlos = uma.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_loss(distance_2D=distance_2D, frequency=freq)
-
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ num_ue = 1
+ num_bs = 1000
+ distance_2D = np.repeat(np.linspace(1, num_bs, num=num_bs)[np.newaxis, :], num_ue, axis=0)
+ freq = 27000 * np.ones(distance_2D.shape)
+ h_bs = 6 * np.ones(num_bs)
+ h_ue = 1.5 * np.ones(num_ue)
+ h_e = np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2)
+
+ loss_los = uma.get_loss_los(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ loss_nlos = uma.get_loss_nlos(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_free_space_loss(
+ distance=distance_2D,
+ frequency=freq,
+ )
+
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y']))
- ax.semilogx(distance_2D, loss_los, label="UMa LOS")
- ax.semilogx(distance_2D, loss_nlos, label="UMa NLOS")
- ax.semilogx(distance_2D, loss_fs, label="free space")
+ ax.semilogx(distance_2D[0, :], loss_los[0, :], label="UMa LOS")
+ ax.semilogx(distance_2D[0, :], loss_nlos[0, :], label="UMa NLOS")
+ ax.semilogx(distance_2D[0, :], loss_fs[0, :], label="free space")
plt.title("UMa - path loss")
plt.xlabel("distance [m]")
plt.ylabel("path loss [dB]")
- plt.xlim((0, distance_2D[-1,0]))
- #plt.ylim((0, 1.1))
+ plt.xlim((0, distance_2D[0, -1]))
+ # plt.ylim((0, 1.1))
plt.legend(loc="upper left")
plt.tight_layout()
plt.grid()
diff --git a/sharc/propagation/propagation_umi.py b/sharc/propagation/propagation_umi.py
index 66073dbee..30253c5fb 100644
--- a/sharc/propagation/propagation_umi.py
+++ b/sharc/propagation/propagation_umi.py
@@ -4,35 +4,104 @@
@author: LeticiaValle_Mac
"""
+import numpy as np
+from multipledispatch import dispatch
from sharc.propagation.propagation import Propagation
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters import Parameters
-import numpy as np
class PropagationUMi(Propagation):
"""
- Implements the Urban Micro path loss model (Street Canyon) with LOS
+ Implements the Urban Micro path loss model (Street Canyon) with LOS
probability according to 3GPP TR 38.900 v14.2.0.
TODO: calculate the effective environment height for the generic case
"""
- def __init__(self,
- random_number_gen: np.random.RandomState,
- los_adjustment_factor: float):
+ def __init__(
+ self,
+ random_number_gen: np.random.RandomState,
+ los_adjustment_factor: float,
+ ):
super().__init__(random_number_gen)
self.los_adjustment_factor = los_adjustment_factor
-
-
- def get_loss(self, *args, **kwargs) -> np.array:
+
+ @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray)
+ def get_loss(
+ self,
+ params: Parameters,
+ frequency: float,
+ station_a: StationManager,
+ station_b: StationManager,
+ station_a_gains=None,
+ station_b_gains=None,
+ ) -> np.array:
+ """Wrapper function for the PropagationUMi get_loss method
+ Calculates the loss between station_a and station_b
+
+ Parameters
+ ----------
+ station_a : StationManager
+ StationManager container representing IMT UE station - Station_type.IMT_UE
+ station_b : StationManager
+ StationManager container representing IMT BS stattion
+ params : Parameters
+ Simulation parameters needed for the propagation class - Station_type.IMT_BS
+
+ Returns
+ -------
+ np.array
+ Return an array station_a.num_stations x station_b.num_stations with the path loss
+ between each station
+ """
+ wrap_around_enabled = False
+ if params.imt.topology.type == "MACROCELL":
+ wrap_around_enabled = params.imt.topology.macrocell.wrap_around \
+ and params.imt.topology.macrocell.num_clusters == 1
+ if params.imt.topology.type == "HOTSPOT":
+ wrap_around_enabled = params.imt.topology.hotspot.wrap_around \
+ and params.imt.topology.hotspot.num_clusters == 1
+
+ if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()):
+ distance_2d, distance_3d, _, _ = \
+ station_a.get_dist_angles_wrap_around(station_b)
+ else:
+ distance_2d = station_a.get_distance_to(station_b)
+ distance_3d = station_a.get_3d_distance_to(station_b)
+
+ loss = self.get_loss(
+ distance_3d,
+ distance_2d,
+ frequency * np.ones(distance_2d.shape),
+ station_b.height,
+ station_a.height,
+ params.imt.shadowing,
+ )
+
+ return loss
+
+ # pylint: disable=function-redefined
+ # pylint: disable=arguments-renamed
+ @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool)
+ def get_loss(
+ self,
+ distance_3D: np.array,
+ distance_2D: np.array,
+ frequency: np.array,
+ bs_height: np.array,
+ ue_height: np.array,
+ shadowing_flag: bool,
+ ) -> np.array:
"""
Calculates path loss for LOS and NLOS cases with respective shadowing
(if shadowing is to be added)
Parameters
----------
- distance_3D (np.array) : 3D distances between stations
- distance_2D (np.array) : 2D distances between stations
- frequency (np.array) : center frequencie [MHz]
+ distance_3D (np.array) : 3D distances between base stations and user equipment
+ distance_2D (np.array) : 2D distances between base stations and user equipment
+ frequency (np.array) : center frequencies [MHz]
bs_height (np.array) : base station antenna heights
ue_height (np.array) : user equipment antenna heights
shadowing (bool) : if shadowing should be added or not
@@ -40,47 +109,51 @@ def get_loss(self, *args, **kwargs) -> np.array:
Returns
-------
array with path loss values with dimensions of distance_2D
-
"""
- d_3D = kwargs["distance_3D"]
- d_2D = kwargs["distance_2D"]
- f = kwargs["frequency"]
- h_bs = kwargs["bs_height"]
- h_ue = kwargs["ue_height"]
- h_e = np.ones(d_2D.shape)
- std = kwargs["shadowing"]
-
- if std:
+ if shadowing_flag:
shadowing_los = 4
shadowing_nlos = 7.82 # option 1 for UMi NLOS
- #shadowing_nlos = 8.2 # option 2 for UMi NLOS
+ # shadowing_nlos = 8.2 # option 2 for UMi NLOS
else:
shadowing_los = 0
shadowing_nlos = 0
- los_probability = self.get_los_probability(d_2D, self.los_adjustment_factor)
+ # effective height
+ h_e = np.ones(distance_2D.shape)
+
+ los_probability = self.get_los_probability(
+ distance_2D, self.los_adjustment_factor,
+ )
los_condition = self.get_los_condition(los_probability)
i_los = np.where(los_condition == True)[:2]
i_nlos = np.where(los_condition == False)[:2]
- loss = np.empty(d_2D.shape)
+ loss = np.empty(distance_2D.shape)
if len(i_los[0]):
- loss_los = self.get_loss_los(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_los)
+ loss_los = self.get_loss_los(
+ distance_2D, distance_3D, frequency, bs_height, ue_height, h_e, shadowing_los,
+ )
loss[i_los] = loss_los[i_los]
if len(i_nlos[0]):
- loss_nlos = self.get_loss_nlos(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_nlos)
+ loss_nlos = self.get_loss_nlos(
+ distance_2D, distance_3D, frequency, bs_height, ue_height, h_e, shadowing_nlos,
+ )
loss[i_nlos] = loss_nlos[i_nlos]
return loss
-
- def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
- frequency: np.array,
- h_bs: np.array, h_ue: np.array, h_e: np.array,
- shadowing_std=4):
+ def get_loss_los(
+ self, distance_2D: np.array,
+ distance_3D: np.array,
+ frequency: np.array,
+ h_bs: np.array,
+ h_ue: np.array,
+ h_e: np.array,
+ shadowing_std=4,
+ ):
"""
Calculates path loss for the LOS (line-of-sight) case.
@@ -94,7 +167,9 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
h_bs : array of base stations antenna heights [m]
h_ue : array of user equipments antenna heights [m]
"""
- breakpoint_distance = self.get_breakpoint_distance(frequency, h_bs, h_ue, h_e)
+ breakpoint_distance = self.get_breakpoint_distance(
+ frequency, h_bs, h_ue, h_e,
+ )
# get index where distance if less than breakpoint
idl = np.where(distance_2D < breakpoint_distance)
@@ -105,25 +180,33 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array,
loss = np.empty(distance_2D.shape)
if len(idl[0]):
- loss[idl] = 21*np.log10(distance_3D[idl]) + 20*np.log10(frequency[idl]) - 27.55
+ loss[idl] = 21 * np.log10(distance_3D[idl]) + \
+ 20 * np.log10(frequency[idl]) - 27.55
if len(idg[0]):
- fitting_term = -9.5*np.log10(breakpoint_distance**2 +(h_bs[:,np.newaxis] - h_ue)**2)
- loss[idg] = 40*np.log10(distance_3D[idg]) + 20*np.log10(frequency[idg]) - 27.55 \
+ fitting_term = -9.5 * \
+ np.log10(
+ breakpoint_distance**2 +
+ (h_bs - h_ue[:, np.newaxis])**2,
+ )
+ loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * np.log10(frequency[idg]) - 27.55 \
+ fitting_term[idg]
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_2D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_2D.shape,
+ )
else:
shadowing = 0
return loss + shadowing
-
- def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array,
- frequency: np.array,
- h_bs: np.array, h_ue: np.array, h_e: np.array,
- shadowing_std=7.82):
+ def get_loss_nlos(
+ self, distance_2D: np.array, distance_3D: np.array,
+ frequency: np.array,
+ h_bs: np.array, h_ue: np.array, h_e: np.array,
+ shadowing_std=7.82,
+ ):
"""
Calculates path loss for the NLOS (non line-of-sight) case.
@@ -138,24 +221,26 @@ def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array,
h_ue : array of user equipments antenna heights [m]
"""
# option 1 for UMi NLOS
- loss_nlos = -37.55 + 35.3*np.log10(distance_3D) + 21.3*np.log10(frequency) \
- - 0.3*(h_ue - 1.5)
+ loss_nlos = -37.55 + 35.3 * np.log10(distance_3D) + 21.3 * np.log10(frequency) \
+ - 0.3 * (h_ue[:, np.newaxis] - 1.5)
- loss_los = self.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, 0)
+ loss_los = self.get_loss_los(
+ distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, 0,
+ )
loss_nlos = np.maximum(loss_los, loss_nlos)
-
+
# option 2 for UMi NLOS
- #loss_nlos = 31.9*np.log10(distance_3D) + 20*np.log10(frequency*1e-3) + 32.4
+ # loss_nlos = 31.9*np.log10(distance_3D) + 20*np.log10(frequency*1e-3) + 32.4
if shadowing_std:
- shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape)
+ shadowing = self.random_number_gen.normal(
+ 0, shadowing_std, distance_3D.shape,
+ )
else:
shadowing = 0
return loss_nlos + shadowing
-
-
def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.array, h_e: np.array) -> float:
"""
Calculates the breakpoint distance for the LOS (line-of-sight) case.
@@ -172,14 +257,14 @@ def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.arr
array of breakpoint distances [m]
"""
# calculate the effective antenna heights
- h_bs_eff = h_bs[:,np.newaxis] - h_e
- h_ue_eff = h_ue - h_e
+ h_bs_eff = h_bs - h_e
+ h_ue_eff = h_ue[:, np.newaxis] - h_e
# calculate the breakpoint distance
- breakpoint_distance = 4*h_bs_eff*h_ue_eff*(frequency*1e6)/(3e8)
+ breakpoint_distance = 4 * h_bs_eff * \
+ h_ue_eff * (frequency * 1e6) / (3e8)
return breakpoint_distance
-
def get_los_condition(self, p_los: np.array) -> np.array:
"""
Evaluates if user equipments are LOS (True) of NLOS (False).
@@ -193,13 +278,16 @@ def get_los_condition(self, p_los: np.array) -> np.array:
An array with True or False if user equipments are in LOS of NLOS
condition, respectively.
"""
- los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los
+ los_condition = self.random_number_gen.random_sample(
+ p_los.shape,
+ ) < p_los
return los_condition
-
- def get_los_probability(self,
- distance_2D: np.array,
- los_adjustment_factor: float) -> np.array:
+ def get_los_probability(
+ self,
+ distance_2D: np.array,
+ los_adjustment_factor: float,
+ ) -> np.array:
"""
Returns the line-of-sight (LOS) probability
@@ -207,7 +295,7 @@ def get_los_probability(self,
----------
distance_2D : Two-dimensional array with 2D distance values from
base station to user terminal [m]
- los_adjustment_factor : adjustment factor to increase/decrease the
+ los_adjustment_factor : adjustment factor to increase/decrease the
LOS probability. Original value is 18 as per 3GPP
Returns
@@ -217,7 +305,10 @@ def get_los_probability(self,
p_los = np.ones(distance_2D.shape)
idl = np.where(distance_2D > los_adjustment_factor)
- p_los[idl] = (los_adjustment_factor/distance_2D[idl] + np.exp(-distance_2D[idl]/36)*(1-los_adjustment_factor/distance_2D[idl]))
+ p_los[idl] = (
+ los_adjustment_factor / distance_2D[idl] +
+ np.exp(-distance_2D[idl] / 36) * (1 - los_adjustment_factor / distance_2D[idl])
+ )
return p_los
@@ -229,31 +320,35 @@ def get_los_probability(self,
###########################################################################
# Print LOS probability
- #h_ue = np.array([1.5, 17, 23])
- distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis]
+ # h_ue = np.array([1.5, 17, 23])
+ distance_2D = np.linspace(1, 1000, num=1000)[:, np.newaxis]
umi = PropagationUMi(np.random.RandomState(101), 18)
los_probability = np.empty(distance_2D.shape)
name = list()
-
+
los_adjustment_factor = 18
- los_probability_18 = umi.get_los_probability(distance_2D, los_adjustment_factor)
+ los_probability_18 = umi.get_los_probability(
+ distance_2D, los_adjustment_factor,
+ )
los_adjustment_factor = 29
- los_probability_29 = umi.get_los_probability(distance_2D, los_adjustment_factor)
+ los_probability_29 = umi.get_los_probability(
+ distance_2D, los_adjustment_factor,
+ )
- fig = plt.figure(figsize=(6,5), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(6, 5), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y']))
- ax.loglog(distance_2D, 100*los_probability_18, label = r"$\alpha = 18$")
- ax.loglog(distance_2D, 100*los_probability_29, label = r"$\alpha = 29$")
+ ax.loglog(distance_2D, 100 * los_probability_18, label=r"$\alpha = 18$")
+ ax.loglog(distance_2D, 100 * los_probability_29, label=r"$\alpha = 29$")
plt.title("UMi - LOS probability")
plt.xlabel("distance between BS and UE [m]")
plt.ylabel("probability [%]")
- ax.legend(loc = "lower left")
- ax.grid(True, which = "both", color="grey", linestyle='-', linewidth=0.2)
+ ax.legend(loc="lower left")
+ ax.grid(True, which="both", color="grey", linestyle='-', linewidth=0.2)
plt.xlim((10, 300))
plt.ylim((10, 102))
plt.tight_layout()
@@ -263,29 +358,38 @@ def get_los_probability(self,
# Print path loss for UMi-LOS, UMi-NLOS and Free Space
from propagation_free_space import PropagationFreeSpace
shadowing_std = 0
- distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis]
- freq = 24350*np.ones(distance_2D.shape)
- h_bs = 6*np.ones(len(distance_2D[:,0]))
- h_ue = 1.5*np.ones(len(distance_2D[0,:]))
+ # 1 ue x 1000 bs
+ num_ue = 1
+ num_bs = 1000
+ distance_2D = np.repeat(np.linspace(1, num_bs, num=num_bs)[np.newaxis, :], num_ue, axis=0)
+ freq = 24350 * np.ones(distance_2D.shape)
+ h_bs = 6 * np.ones(num_bs)
+ h_ue = 1.5 * np.ones(num_ue)
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
-
- loss_los = umi.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- loss_nlos = umi.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std)
- loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_loss(distance_2D=distance_2D, frequency=freq)
-
- fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k')
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2)
+
+ loss_los = umi.get_loss_los(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ loss_nlos = umi.get_loss_nlos(
+ distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std,
+ )
+ loss_fs = PropagationFreeSpace(
+ np.random.RandomState(101,),
+ ).get_free_space_loss(distance=distance_2D, frequency=freq)
+
+ fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k')
ax = fig.gca()
- ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) )
+ ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y']))
- ax.semilogx(distance_2D, loss_los, label="UMi LOS")
- ax.semilogx(distance_2D, loss_nlos, label="UMi NLOS")
- ax.semilogx(distance_2D, loss_fs, label="free space")
+ ax.semilogx(distance_2D[0, :], loss_los[0, :], label="UMi LOS")
+ ax.semilogx(distance_2D[0, :], loss_nlos[0, :], label="UMi NLOS")
+ ax.semilogx(distance_2D[0, :], loss_fs[0, :], label="free space")
plt.title("UMi - path loss")
plt.xlabel("distance [m]")
plt.ylabel("path loss [dB]")
- plt.xlim((0, distance_2D[-1,0]))
+ plt.xlim((0, distance_2D[0, -1]))
plt.ylim((60, 200))
plt.legend(loc="upper left")
plt.tight_layout()
diff --git a/sharc/propagation/scintillation.py b/sharc/propagation/scintillation.py
index 7d5cb4efc..37c2e9a70 100644
--- a/sharc/propagation/scintillation.py
+++ b/sharc/propagation/scintillation.py
@@ -5,11 +5,11 @@
@author: Andre Noll Barreto
"""
-from sharc.propagation.propagation import Propagation
from sharc.propagation.atmosphere import ReferenceAtmosphere
import numpy as np
+
class Scintillation():
"""
Implements the scintillation attenuation according to ITU-R P619
@@ -21,8 +21,7 @@ def __init__(self, random_number_gen: np.random.RandomState):
self.atmosphere = ReferenceAtmosphere()
-
- def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array:
+ def get_tropospheric_attenuation(self, *args, **kwargs) -> np.array:
"""
Calculates tropospheric scintillation based on ITU-R P.619, Appendix D
@@ -36,10 +35,18 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array:
default = "random"
wet_refractivity (float) : wet term of the radio refractivity - optional
if not given, then it is calculated from sat_params
- sat_params (ParametersFss) : satellite channel parameters - optional
- needed only if wet_refractivity is not given
-
+ satellite channel parameters:
+ optional needed only if wet_refractivity is not given
+ space_station_alt_m : float
+ The space statio altitude in meters.
+ Optional needed only if wet_refractivity is not given
+ earth_station_alt_m : float
+ The Earth station altitude in meters.
+ Optional needed only if wet_refractivity is not given
+ earth_station_lat_deg : float
+ The Earth station latitude in degrees.
+ Optional needed only if wet_refractivity is not given
Returns
-------
attenuation (np.array): attenuation (dB) with dimensions equal to "elevation"
@@ -48,44 +55,53 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array:
f_GHz = kwargs["frequency_MHz"] / 1000.
elevation_rad = kwargs["elevation"] / 180. * np.pi
antenna_gain_dB = kwargs["antenna_gain_dB"]
- if not np.isscalar(antenna_gain_dB):
- antenna_gain_dB = antenna_gain_dB.flatten()
time_ratio = kwargs.pop("time_ratio", "random")
wet_refractivity = kwargs.pop("wet_refractivity", False)
if not wet_refractivity:
- sat_params = kwargs["sat_params"]
+ for p in ["earth_station_alt_m", "earth_station_lat_deg", "season"]:
+ if p not in kwargs:
+ raise ValueError(
+ f"Scintillation: parameter {p} is mandatory if wet_refractivity is set.",
+ )
temperature, \
- pressure, \
- water_vapour_density = self.atmosphere.get_reference_atmosphere_p835(sat_params.imt_lat_deg,
- sat_params.imt_altitude,
- sat_params.season)
+ pressure, \
+ water_vapour_density = self.atmosphere.get_reference_atmosphere_p835(
+ kwargs["earth_station_lat_deg"],
+ kwargs["earth_station_lat_deg"],
+ kwargs["season"],
+ )
# calculate saturation water vapour pressure according to ITU-R P.453-12
# water coefficients (ice disregarded)
- coef_a = 6.1121
- coef_b = 18.678
- coef_c = 257.14
- coef_d = 234.5
- EF_water = 1 + 1e-4 * (7.2 + pressure * (.032 + 5.9e-6 * temperature ** 2))
vapour_pressure = water_vapour_density * temperature / 216.7 # eq 10 in P453
- wet_refractivity = (72 * vapour_pressure / temperature
- + 3.75e5 * vapour_pressure / temperature ** 2)
+ wet_refractivity = (
+ 72 * vapour_pressure / temperature +
+ 3.75e5 * vapour_pressure / temperature ** 2
+ )
sigma_ref = 3.6e-3 + 1e-4 * wet_refractivity
h_l = 1000
- path_length = 2 * h_l / (np.sqrt(np.sin(elevation_rad) ** 2 + 2.35e-4) + np.sin(elevation_rad))
- eff_antenna_diameter = .3 * 10 ** (.05 * antenna_gain_dB) / (np.pi * f_GHz)
+ path_length = 2 * h_l / \
+ (np.sqrt(np.sin(elevation_rad) ** 2 + 2.35e-4) + np.sin(elevation_rad))
+ eff_antenna_diameter = .3 * \
+ 10 ** (.05 * antenna_gain_dB) / (np.pi * f_GHz)
x = 1.22 * eff_antenna_diameter ** 2 * (f_GHz / path_length)
- antenna_averaging_factor = np.sqrt(3.86 * (x ** 2 + 1) ** (11 / 12) *
- np.sin(11 / 6 * np.arctan(1 / x)) - 7.08 * x ** 5 / 6)
- scintillation_intensity = (sigma_ref * f_GHz ** (7 / 12) * antenna_averaging_factor
- / np.sin(elevation_rad) ** 1.2)
+ antenna_averaging_factor = np.sqrt(
+ 3.86 * (x ** 2 + 1) ** (11 / 12) *
+ np.sin(11 / 6 * np.arctan(1 / x)) - 7.08 * x ** 5 / 6,
+ )
+ scintillation_intensity = (
+ sigma_ref * f_GHz ** (7 / 12) * antenna_averaging_factor /
+ np.sin(elevation_rad) ** 1.2
+ )
if isinstance(time_ratio, str) and time_ratio.lower() == "random":
- time_ratio = self.random_number_gen.rand(len(elevation_rad))
+ time_ratio = self.random_number_gen.rand(
+ elevation_rad.size,
+ ).reshape(elevation_rad.shape)
# tropospheric scintillation attenuation not exceeded for time_percentage percent time
time_percentage = time_ratio * 100.
@@ -94,31 +110,40 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array:
if np.isscalar(scintillation_intensity):
if not np.isscalar(time_percentage):
num_el = time_percentage.size
- scintillation_intensity = np.ones(num_el) * scintillation_intensity
+ scintillation_intensity = np.ones(
+ num_el,
+ ) * scintillation_intensity
else:
num_el = scintillation_intensity.size
if np.isscalar(time_percentage):
time_percentage = np.ones(num_el) * time_percentage
- attenuation = np.empty(num_el)
+ attenuation = np.empty(elevation_rad.shape)
- a_ste = (2.672 - 1.258 * np.log10(time_percentage) -
- .0835 * np.log10(time_percentage) ** 2 -
- .0597 * np.log10(time_percentage) ** 3)
+ a_ste = (
+ 2.672 - 1.258 * np.log10(time_percentage) -
+ .0835 * np.log10(time_percentage) ** 2 -
+ .0597 * np.log10(time_percentage) ** 3
+ )
- a_stf = (3. - 1.71 * np.log10(100 - time_percentage) +
- .072 * np.log10(100 - time_percentage) ** 2 -
- .061 * np.log10(100 - time_percentage) ** 3)
+ a_stf = (
+ 3. - 1.71 * np.log10(100 - time_percentage) +
+ .072 * np.log10(100 - time_percentage) ** 2 -
+ .061 * np.log10(100 - time_percentage) ** 3
+ )
- gain_indices = np.where(time_percentage <= 50.)[0]
- if gain_indices.size:
- attenuation[gain_indices] = - scintillation_intensity[gain_indices] * a_ste[gain_indices]
+ gain_indices = np.where(time_percentage <= 50.)
+ if gain_indices:
+ attenuation[gain_indices] = - \
+ scintillation_intensity[gain_indices] * a_ste[gain_indices]
fade_indices = np.where(time_percentage > 50.)[0]
if fade_indices.size:
- attenuation[fade_indices] = scintillation_intensity[fade_indices] * a_stf[fade_indices]
+ attenuation[fade_indices] = scintillation_intensity[fade_indices] * \
+ a_stf[fade_indices]
return attenuation
+
if __name__ == '__main__':
from matplotlib import pyplot as plt
@@ -138,21 +163,32 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array:
plt.figure()
for elevation in elevation_vec:
- attenuation = propagation.get_tropospheric_attenuation(elevation=elevation,
- frequency_MHz=frequency_MHz,
- antenna_gain_dB=antenna_gain,
- time_ratio=1 - (percentage_fading_exceeded / 100),
- wet_refractivity=wet_refractivity)
- plt.semilogx(percentage_fading_exceeded, attenuation, label="{} deg".format(elevation))
+ attenuation = propagation.get_tropospheric_attenuation(
+ elevation=elevation,
+ frequency_MHz=frequency_MHz,
+ antenna_gain_dB=antenna_gain,
+ time_ratio=1 -
+ (percentage_fading_exceeded / 100),
+ wet_refractivity=wet_refractivity,
+ )
+ plt.semilogx(
+ percentage_fading_exceeded, attenuation,
+ label="{} deg".format(elevation),
+ )
percentage_gain_exceeded = 10 ** np.arange(-2, 1.1, .1)
for elevation in elevation_vec:
- attenuation = propagation.get_tropospheric_attenuation(elevation=elevation,
- frequency_MHz=frequency_MHz,
- antenna_gain_dB=antenna_gain,
- time_ratio=percentage_gain_exceeded / 100,
- wet_refractivity=wet_refractivity)
- plt.loglog(percentage_gain_exceeded, np.abs(attenuation), ':', label="{} deg".format(elevation))
+ attenuation = propagation.get_tropospheric_attenuation(
+ elevation=elevation,
+ frequency_MHz=frequency_MHz,
+ antenna_gain_dB=antenna_gain,
+ time_ratio=percentage_gain_exceeded / 100,
+ wet_refractivity=wet_refractivity,
+ )
+ plt.loglog(
+ percentage_gain_exceeded, np.abs(attenuation),
+ ':', label="{} deg".format(elevation),
+ )
plt.legend(title='elevation')
plt.grid(True)
diff --git a/sharc/results.py b/sharc/results.py
index 210f99ec5..79d97fec8 100644
--- a/sharc/results.py
+++ b/sharc/results.py
@@ -5,430 +5,296 @@
@author: edgar
"""
-from sharc.plot import Plot
-
-import numpy as np
+import glob
import os
import datetime
-
+import re
+import pathlib
+import pandas as pd
from shutil import copy
+
+class SampleList(list):
+ """
+ This class only exists so that no list property can be confused with a SampleList
+ """
+
+ pass
+
+
class Results(object):
+ """Handle the output of the simulator"""
+
+ # This should always be true for 1st samples flush
+ overwrite_sample_files = True
+
+ def __init__(self):
+ # Transmit power density [dBm/Hz]
+ self.imt_ul_tx_power_density = SampleList()
+ self.imt_ul_tx_power = SampleList()
+ # SINR [dB]
+ self.imt_ul_sinr_ext = SampleList()
+ # SINR [dB]
+ self.imt_ul_sinr = SampleList()
+ # SNR [dB]
+ self.imt_ul_snr = SampleList()
+ self.imt_ul_inr = SampleList()
+ # Throughput [bits/s/Hz]
+ self.imt_ul_tput_ext = SampleList()
+ # Throughput [bits/s/Hz]
+ self.imt_ul_tput = SampleList()
+
+ self.imt_path_loss = SampleList()
+ self.imt_coupling_loss = SampleList()
+ # Antenna gain [dBi]
+ self.imt_bs_antenna_gain = SampleList()
+ # Antenna gain [dBi]
+ self.imt_ue_antenna_gain = SampleList()
+
+ # Antenna gain [dBi]
+ self.system_imt_antenna_gain = SampleList()
+ # Antenna gain [dBi]
+ self.imt_system_antenna_gain = SampleList()
+ # Path Loss [dB]
+ self.imt_system_path_loss = SampleList()
+ # Building entry loss [dB]
+ self.imt_system_build_entry_loss = SampleList()
+ # System diffraction loss [dB]
+ self.imt_system_diffraction_loss = SampleList()
+ # System to IMT coupling loss
+ self.sys_to_imt_coupling_loss = SampleList()
+
+ self.imt_dl_tx_power_density = SampleList()
+ # Transmit power [dBm]
+ self.imt_dl_tx_power = SampleList()
+ # SINR [dB]
+ self.imt_dl_sinr_ext = SampleList()
+ # SINR [dB]
+ self.imt_dl_sinr = SampleList()
+ # SNR [dB]
+ self.imt_dl_snr = SampleList()
+ # I/N [dB]
+ self.imt_dl_inr = SampleList()
+ # Throughput [bits/s/Hz]
+ self.imt_dl_tput_ext = SampleList()
+ # Throughput [bits/s/Hz]
+ self.imt_dl_tput = SampleList()
- def __init__(self, parameters_filename: str, overwrite_output: bool):
- self.imt_ul_tx_power_density = list()
- self.imt_ul_tx_power = list()
- self.imt_ul_sinr_ext = list()
- self.imt_ul_sinr = list()
- self.imt_ul_snr = list()
- self.imt_ul_inr = list()
- self.imt_ul_tput_ext = list()
- self.imt_ul_tput = list()
-
- self.imt_path_loss = list()
- self.imt_coupling_loss = list()
- self.imt_bs_antenna_gain = list()
- self.imt_ue_antenna_gain = list()
-
- self.system_imt_antenna_gain = list()
- self.imt_system_antenna_gain = list()
- self.imt_system_path_loss = list()
- self.imt_system_build_entry_loss = list()
- self.imt_system_diffraction_loss = list()
-
- self.imt_dl_tx_power_density = list()
- self.imt_dl_tx_power = list()
- self.imt_dl_sinr_ext = list()
- self.imt_dl_sinr = list()
- self.imt_dl_snr = list()
- self.imt_dl_inr = list()
- self.imt_dl_tput_ext = list()
- self.imt_dl_tput = list()
-
- self.system_ul_coupling_loss = list()
- self.system_ul_interf_power = list()
-
- self.system_dl_coupling_loss = list()
- self.system_dl_interf_power = list()
-
- self.system_inr = list()
- self.system_pfd = list()
- self.system_rx_interf = list()
+ # PFD
+ self.imt_dl_pfd_external = SampleList()
+ self.imt_dl_pfd_external_aggregated = SampleList()
+
+ self.system_ul_coupling_loss = SampleList()
+ self.system_ul_interf_power = SampleList()
+ # Interference Power [dBm]
+
+ self.system_dl_coupling_loss = SampleList()
+ self.system_dl_interf_power = SampleList()
+ # Interference Power [dBm/MHz]
+ # NOTE: this may not be what you want for a correct
+ # protection criteria analysis since it is
+ # a mean value. If you have both cochannel
+ # and adjacent channel, the adjacent channel interference
+ # will always drag the mean down
+ self.system_dl_interf_power_per_mhz = SampleList()
+ self.system_ul_interf_power_per_mhz = SampleList()
+
+ self.system_inr = SampleList()
+ self.system_pfd = SampleList()
+ self.system_rx_interf = SampleList()
+
+ self.__sharc_dir = pathlib.Path(__file__).parent.resolve()
+
+ def prepare_to_write(
+ self,
+ parameters_filename: str,
+ overwrite_output: bool,
+ output_dir="output",
+ output_dir_prefix="output",
+ ):
+ self.output_dir_parent = output_dir
if not overwrite_output:
today = datetime.date.today()
results_number = 1
- results_dir_head = 'output_' + today.isoformat() + '_' + "{:02n}"
- self.create_dir(results_number,results_dir_head)
+ results_dir_head = (
+ output_dir_prefix + "_" + today.isoformat() + "_" + "{:02n}"
+ )
+ self.create_dir(results_number, results_dir_head)
copy(parameters_filename, self.output_directory)
else:
- self.output_directory = 'output'
+ self.output_directory = self.__sharc_dir / self.output_dir_parent
+ try:
+ os.makedirs(self.output_directory)
+ except FileExistsError:
+ pass
+
+ return self
+
+ def create_dir(self, results_number: int, dir_head: str):
+ """Creates the output directory if it doesn't exist.
+
+ Parameters
+ ----------
+ results_number : int
+ Increment used in directory name
+ dir_head : str
+ Directory name prefix
+
+ Returns
+ -------
+ str
+ output directory name
+ """
+
+ dir_head_complete = (
+ self.__sharc_dir / self.output_dir_parent / dir_head.format(results_number)
+ )
- def create_dir(self,results_number,dir_head):
-
- dir_head_complete = dir_head.format(results_number)
-
try:
os.makedirs(dir_head_complete)
self.output_directory = dir_head_complete
- except FileExistsError as e:
+ except FileExistsError:
self.create_dir(results_number + 1, dir_head)
+ def get_relevant_attributes(self):
+ """
+ Returns the attributes that are used for storing samples
+ """
+ self_dict = self.__dict__
- def generate_plot_list(self, n_bins):
- self.plot_list = list()
- if len(self.system_imt_antenna_gain) > 0:
- values, base = np.histogram(self.system_imt_antenna_gain, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Antenna gain [dBi]"
- y_label = "Probability of antenna gain < $X$"
- title = "[SYS] CDF of system antenna gain towards IMT stations"
- file_name = title
- #x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_system_antenna_gain) > 0:
- values, base = np.histogram(self.imt_system_antenna_gain, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Antenna gain [dBi]"
- y_label = "Probability of antenna gain < $X$"
- title = "[IMT] CDF of IMT station antenna gain towards system"
- file_name = title
- #x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_system_path_loss) > 0:
- values, base = np.histogram(self.imt_system_path_loss, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Path Loss [dB]"
- y_label = "Probability of path loss < $X$"
- title = "[SYS] CDF of IMT to system path loss"
- file_name = title
- #x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_system_build_entry_loss) > 0:
- values, base = np.histogram(self.imt_system_build_entry_loss, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Building entry loss [dB]"
- y_label = "Probability of loss < $X$"
- title = "[SYS] CDF of IMT to system building entry loss"
- file_name = title
- #x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_system_diffraction_loss) > 0:
- values, base = np.histogram(self.imt_system_diffraction_loss, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Building entry loss [dB]"
- y_label = "Probability of loss < $X$"
- title = "[SYS] CDF of IMT to system diffraction loss"
- file_name = title
- #x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_bs_antenna_gain) > 0:
- values, base = np.histogram(self.imt_bs_antenna_gain, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Antenna gain [dBi]"
- y_label = "Probability of antenna gain < $X$"
- title = "[IMT] CDF of BS antenna gain towards the UE"
- file_name = title
- x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ue_antenna_gain) > 0:
- values, base = np.histogram(self.imt_ue_antenna_gain, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Antenna gain [dBi]"
- y_label = "Probability of antenna gain < $X$"
- title = "[IMT] CDF of UE antenna gain towards the BS"
- file_name = title
- x_limits = (0, 25)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ul_tx_power_density) > 0:
- values, base = np.histogram(self.imt_ul_tx_power_density, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Transmit power density [dBm/Hz]"
- y_label = "Probability of transmit power density < $X$"
- title = "[IMT] CDF of UE transmit power density"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_ul_tx_power) > 0:
- values, base = np.histogram(self.imt_ul_tx_power, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Transmit power [dBm]"
- y_label = "Probability of transmit power < $X$"
- title = "[IMT] CDF of UE transmit power"
- file_name = title
- x_limits = (-40, 30)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ul_sinr_ext) > 0:
- values, base = np.histogram(self.imt_ul_sinr_ext, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "SINR [dB]"
- y_label = "Probability of SINR < $X$"
- title = "[IMT] CDF of UL SINR with external interference"
- file_name = title
- x_limits = (-15, 20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ul_sinr) > 0:
- values, base = np.histogram(self.imt_ul_sinr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "SINR [dB]"
- y_label = "Probability of SINR < $X$"
- title = "[IMT] CDF of UL SINR"
- file_name = title
- x_limits = (-15, 20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ul_snr) > 0:
- values, base = np.histogram(self.imt_ul_snr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of UL SNR"
- x_label = "SNR [dB]"
- y_label = "Probability of SNR < $X$"
- file_name = title
- x_limits = (-15, 20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_ul_inr) > 0:
- values, base = np.histogram(self.imt_ul_inr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of UL interference-to-noise ratio"
- x_label = "$I/N$ [dB]"
- y_label = "Probability of $I/N$ < $X$"
- file_name = title
- #x_limits = (-15, 20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_ul_tput_ext) > 0:
- values, base = np.histogram(self.imt_ul_tput_ext, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of UL throughput with external interference"
- x_label = "Throughput [bits/s/Hz]"
- y_label = "Probability of UL throughput < $X$"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_ul_tput) > 0:
- values, base = np.histogram(self.imt_ul_tput, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of UL throughput"
- x_label = "Throughput [bits/s/Hz]"
- y_label = "Probability of UL throughput < $X$"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_path_loss) > 0:
- values, base = np.histogram(self.imt_path_loss, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of path loss"
- x_label = "Path loss [dB]"
- y_label = "Probability of path loss < $X$"
- file_name = title
- x_limits = (40, 150)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_coupling_loss) > 0:
- values, base = np.histogram(self.imt_coupling_loss, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of coupling loss"
- x_label = "Coupling loss [dB]"
- y_label = "Probability of coupling loss < $X$"
- file_name = title
- x_limits = (30, 120)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_dl_tx_power) > 0:
- values, base = np.histogram(self.imt_dl_tx_power, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "Transmit power [dBm]"
- y_label = "Probability of transmit power < $X$"
- title = "[IMT] CDF of DL transmit power"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_dl_sinr_ext) > 0:
- values, base = np.histogram(self.imt_dl_sinr_ext, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "SINR [dB]"
- y_label = "Probability of SINR < $X$"
- title = "[IMT] CDF of DL SINR with external interference"
- file_name = title
- x_limits = (-20, 80)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_dl_sinr) > 0:
- values, base = np.histogram(self.imt_dl_sinr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- x_label = "SINR [dB]"
- y_label = "Probability of SINR < $X$"
- title = "[IMT] CDF of DL SINR"
- file_name = title
- x_limits = (-20, 80)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_dl_snr) > 0:
- values, base = np.histogram(self.imt_dl_snr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of DL SNR"
- x_label = "SNR [dB]"
- y_label = "Probability of SNR < $X$"
- file_name = title
- x_limits = (-20, 80)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.imt_dl_inr) > 0:
- values, base = np.histogram(self.imt_dl_inr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of DL interference-to-noise ratio"
- x_label = "$I/N$ [dB]"
- y_label = "Probability of $I/N$ < $X$"
- file_name = title
- #x_limits = (-15, 20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_dl_tput_ext) > 0:
- values, base = np.histogram(self.imt_dl_tput_ext, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of DL throughput with external interference"
- x_label = "Throughput [bits/s/Hz]"
- y_label = "Probability of throughput < $X$"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.imt_dl_tput) > 0:
- values, base = np.histogram(self.imt_dl_tput, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[IMT] CDF of DL throughput"
- x_label = "Throughput [bits/s/Hz]"
- y_label = "Probability of throughput < $X$"
- file_name = title
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits))
- if len(self.system_inr) > 0:
- values, base = np.histogram(self.system_inr, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[SYS] CDF of system INR"
- x_label = "INR [dB]"
- y_label = "Probability of INR < $X$"
- file_name = title
- x_limits = (-80, 30)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- ###################################################################
- # now we plot INR samples
- x = np.arange(len(self.system_inr))
- y = np.array(self.system_inr)
- title = "[SYS] INR samples"
- x_label = "Number of samples"
- y_label = "INR [dB]"
- file_name = title
- x_limits = (0, 800)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name))
- if len(self.system_pfd) > 0:
- values, base = np.histogram(self.system_pfd, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[SYS] CDF of system PFD"
- x_label = "PFD [dBm/m^2]"
- y_label = "Probability of INR < $X$"
- file_name = title
-# x_limits = (-80, -20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.system_ul_interf_power) > 0:
- values, base = np.histogram(self.system_ul_interf_power, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[SYS] CDF of system interference power from IMT UL"
- x_label = "Interference Power [dBm]"
- y_label = "Probability of Power < $X$"
- file_name = title
- #x_limits = (-80, -20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
- if len(self.system_dl_interf_power) > 0:
- values, base = np.histogram(self.system_dl_interf_power, bins=n_bins)
- cumulative = np.cumsum(values)
- x = base[:-1]
- y = cumulative / cumulative[-1]
- title = "[SYS] CDF of system interference power from IMT DL"
- x_label = "Interference Power [dBm]"
- y_label = "Probability of Power < $X$"
- file_name = title
- #x_limits = (-80, -20)
- y_limits = (0, 1)
- self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits))
+ results_relevant_attr_names = list(
+ filter(lambda x: isinstance(getattr(self, x), SampleList), self_dict)
+ )
+
+ return results_relevant_attr_names
def write_files(self, snapshot_number: int):
- n_bins = 200
- file_extension = ".txt"
- header_text = "Results collected after " + str(snapshot_number) + " snapshots."
- self.generate_plot_list(n_bins)
+ """Writes the sample data to the output file
+
+ Parameters
+ ----------
+ snapshot_number : int
+ Current snapshot number
+ """
+ results_relevant_attr_names = self.get_relevant_attributes()
+ for attr_name in results_relevant_attr_names:
+ file_path = os.path.join(
+ self.output_directory,
+ attr_name + ".csv",
+ )
+ samples = getattr(self, attr_name)
+ if len(samples) == 0:
+ continue
+ df = pd.DataFrame({"samples": samples})
+ if self.overwrite_sample_files:
+ df.to_csv(file_path, mode="w", index=False)
+ else:
+ df.to_csv(file_path, mode="a", index=False, header=False)
+ setattr(self, attr_name, SampleList())
+
+ if self.overwrite_sample_files:
+ self.overwrite_sample_files = False
+
+ @staticmethod
+ def load_many_from_dir(
+ root_dir: str,
+ *,
+ only_latest=True,
+ only_samples: list[str] = None,
+ filter_fn=None
+ ) -> list["Results"]:
+ output_dirs = list(glob.glob(f"{root_dir}/output_*"))
+
+ if len(output_dirs) == 0:
+ print("[WARNING]: Results.load_many_from_dir did not find any results")
+
+ if only_latest:
+ output_dirs = Results.get_most_recent_outputs_for_each_prefix(output_dirs)
+
+ if filter_fn:
+ output_dirs = filter(filter_fn, output_dirs)
+
+ all_res = []
+ for output_dir in output_dirs:
+ res = Results()
+ res.load_from_dir(output_dir, only_samples=only_samples)
+ all_res.append(res)
+
+ return all_res
+
+ def load_from_dir(self, abs_path: str, *, only_samples: list[str] = None) -> "Results":
+ self.output_directory = abs_path
+
+ self_dict = self.__dict__
+ if only_samples is not None:
+ results_relevant_attr_names = only_samples
+ else:
+ results_relevant_attr_names = filter(
+ lambda x: isinstance(getattr(self, x), SampleList), self_dict
+ )
+
+ for attr_name in results_relevant_attr_names:
+ file_path = os.path.join(abs_path, f"{attr_name}.csv")
+ if os.path.exists(file_path):
+ try:
+ # Try reading the .csv file using pandas with different delimiters
+ try:
+ data = pd.read_csv(file_path, delimiter=",")
+ except pd.errors.ParserError:
+ data = pd.read_csv(file_path, delimiter=";")
+
+ # Ensure the data has exactly one column
+ if data.shape[1] != 1:
+ raise Exception(
+ f"The file with samples of {attr_name} should have a single column.",
+ )
+
+ # Remove rows that do not contain valid numeric values
+ data = data.apply(pd.to_numeric, errors="coerce").dropna()
+
+ # Ignore if there is no data
+ if data.empty:
+ continue
+ # Check if there is enough data to load results from.
+
+ setattr(self, attr_name, SampleList(data.to_numpy()[:, 0]))
+
+ except Exception as e:
+ print(e)
+ raise Exception(
+ f"Error processing the sample file ({attr_name}.csv) for {attr_name}: {e}"
+ )
+
+ return self
+
+ @staticmethod
+ def get_most_recent_outputs_for_each_prefix(dirnames: list[str]) -> list[str]:
+ """
+ Input:
+ A list of output directories.
+ Returns:
+ A list containing the most recent output dirname for each output_prefix.
+ Note that if full paths are provided, full paths are returned
+ """
+ res = {}
+
+ for dirname in dirnames:
+ prefix, date, id = Results.get_prefix_date_and_id(dirname)
+ res.setdefault(prefix, {"date": date, "id": id, "dirname": dirname})
+ if date > res[prefix]["date"]:
+ res[prefix]["date"] = date
+ res[prefix]["id"] = id
+ res[prefix]["dirname"] = dirname
+ if date == res[prefix]["date"] and id > res[prefix]["id"]:
+ res[prefix]["id"] = id
+ res[prefix]["dirname"] = dirname
- for plot in self.plot_list:
- np.savetxt(os.path.join(self.output_directory, plot.file_name + file_extension),
- np.transpose([plot.x, plot.y]),
- fmt="%.5f", delimiter="\t", header=header_text)#,
- #newline=os.linesep)
+ return list(map(lambda x: x["dirname"], res.values()))
+ @staticmethod
+ def get_prefix_date_and_id(dirname: str) -> (str, str, str):
+ mtch = re.search("(.*)(20[2-9][0-9]-[0-1][0-9]-[0-3][0-9])_([0-9]{2})", dirname)
+ prefix, date, id = mtch.group(1), mtch.group(2), mtch.group(3)
+ return prefix, date, id
diff --git a/sharc/run_multiple_campaigns.py b/sharc/run_multiple_campaigns.py
new file mode 100644
index 000000000..62a93dd19
--- /dev/null
+++ b/sharc/run_multiple_campaigns.py
@@ -0,0 +1,69 @@
+import subprocess
+import os
+import sys
+import re
+
+
+def run_campaign(campaign_name):
+ # Get the current working directory
+ workfolder = os.path.dirname(os.path.abspath(__file__))
+
+ # Path to the main_cli.py script
+ main_cli_path = os.path.join(workfolder, "main_cli.py")
+
+ # Campaign directory
+ campaign_folder = os.path.join(
+ workfolder, "campaigns", campaign_name, "input",
+ )
+
+ # List of parameter files
+ parameter_files = [
+ os.path.join(campaign_folder, f) for f in os.listdir(
+ campaign_folder,
+ ) if f.endswith('.yaml')
+ ]
+
+ # Run the command for each parameter file
+ for param_file in parameter_files:
+ command = [sys.executable, main_cli_path, "-p", param_file]
+ subprocess.run(command)
+
+
+def run_campaign_re(campaign_name, param_name_regex):
+ """
+ Run a campaign for parameter files matching a given regular expression.
+
+ Args:
+ campaign_name (str): Name of the campaign.
+ param_name_regex (str): Regular expression to filter parameter files.
+ """
+ # Get the current working directory
+ workfolder = os.path.dirname(os.path.abspath(__file__))
+
+ # Path to the main_cli.py script
+ main_cli_path = os.path.join(workfolder, "main_cli.py")
+
+ # Campaign directory
+ campaign_folder = os.path.join(
+ workfolder, "campaigns", campaign_name, "input",
+ )
+
+ # List of parameter files
+ pat = re.compile(param_name_regex)
+ parameter_files = [
+ os.path.join(campaign_folder, f) for f in os.listdir(
+ campaign_folder,
+ ) if pat.match(f)
+ ]
+ if len(parameter_files) == 0:
+ raise ValueError("No parameter files where found with given filter")
+
+ # Run the command for each parameter file
+ for param_file in parameter_files:
+ command = [sys.executable, main_cli_path, "-p", param_file]
+ subprocess.run(command)
+
+
+if __name__ == "__main__":
+ # Example usage
+ run_campaign("imt_hibs_ras_2600_MHz")
diff --git a/sharc/run_multiple_campaigns_mut_thread.py b/sharc/run_multiple_campaigns_mut_thread.py
new file mode 100644
index 000000000..0a76af88f
--- /dev/null
+++ b/sharc/run_multiple_campaigns_mut_thread.py
@@ -0,0 +1,87 @@
+import subprocess
+import os
+import sys
+import re
+from concurrent.futures import ThreadPoolExecutor
+
+
+def run_command(param_file, main_cli_path):
+ command = [sys.executable, main_cli_path, "-p", param_file]
+ subprocess.run(command)
+
+
+def run_campaign(campaign_name):
+ # Path to the working directory
+ workfolder = os.path.dirname(os.path.abspath(__file__))
+ main_cli_path = os.path.join(workfolder, "main_cli.py")
+
+ # Campaign directory
+ campaign_folder = os.path.join(
+ workfolder, "campaigns", campaign_name, "input",
+ )
+
+ # List of parameter files
+ parameter_files = [
+ os.path.join(campaign_folder, f) for f in os.listdir(
+ campaign_folder,
+ ) if f.endswith('.yaml')
+ ]
+
+ if len(parameter_files) == 0:
+ raise ValueError(
+ f"No parameter files were found in {campaign_folder}"
+ )
+
+ # Number of threads (adjust as needed)
+ num_threads = min(len(parameter_files), os.cpu_count())
+
+ # Run the commands in parallel
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
+ executor.map(
+ run_command, parameter_files, [
+ main_cli_path,
+ ] * len(parameter_files),
+ )
+
+
+def run_campaign_re(campaign_name, param_name_regex):
+ """
+ Runs a campaign by executing main_cli.py for each parameter file in the specified campaign's input directory
+ whose filename matches the given regular expression.
+
+ Args:
+ campaign_name (str): The name of the campaign.
+ param_name_regex (str): Regular expression to filter parameter file names.
+ """
+ # Path to the working directory
+ workfolder = os.path.dirname(os.path.abspath(__file__))
+ main_cli_path = os.path.join(workfolder, "main_cli.py")
+
+ # Campaign directory
+ campaign_folder = os.path.join(
+ workfolder, "campaigns", campaign_name, "input",
+ )
+
+ # List of parameter files
+ pat = re.compile(param_name_regex)
+ parameter_files = [
+ os.path.join(campaign_folder, f) for f in os.listdir(
+ campaign_folder,
+ ) if pat.match(f)
+ ]
+
+ # Number of threads (adjust as needed)
+ num_threads = min(len(parameter_files), os.cpu_count())
+
+ # Run the commands in parallel
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
+ executor.map(
+ run_command, parameter_files, [
+ main_cli_path,
+ ] * len(parameter_files),
+ )
+
+
+if __name__ == "__main__":
+ # Example usage
+ run_campaign("imt_hibs_ras_2600_MHz")
diff --git a/sharc/satellite/ngso/__init__.py b/sharc/satellite/ngso/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/sharc/satellite/ngso/constants.py b/sharc/satellite/ngso/constants.py
new file mode 100644
index 000000000..ee798cdf2
--- /dev/null
+++ b/sharc/satellite/ngso/constants.py
@@ -0,0 +1,6 @@
+import numpy as np
+
+# CONSTANTS
+EARTH_RADIUS_KM = 6378.145 # radius of the Earth, in km
+KEPLER_CONST = 398601.8 # Kepler's constant, in km^3/s^2
+EARTH_ROTATION_RATE = 2 * np.pi / (24 * 3600) # earth's average rotation rate, in rad/s
diff --git a/sharc/satellite/ngso/custom_functions.py b/sharc/satellite/ngso/custom_functions.py
new file mode 100644
index 000000000..1e88ef3f4
--- /dev/null
+++ b/sharc/satellite/ngso/custom_functions.py
@@ -0,0 +1,206 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import cartopy.crs as ccrs
+import cartopy.feature as cfeature
+from scipy.special import jv
+
+
+def wrap2pi(angle_rad):
+ """
+ Adjusts an angle in radians to the interval (-ฯ, ฯ).
+ """
+ return (angle_rad + np.pi) % (2 * np.pi) - np.pi
+
+
+def eccentric_anomaly(e, M, terms=40, mod_2pi=True):
+ """
+ Calculate the eccentric anomaly E given the eccentricity e and
+ mean anomaly M using a Bessel series expansion.
+
+ Parameters:
+ ----------
+ e : float
+ Eccentricity of the orbit (0 <= e < 1).
+ M : np.ndarray
+ Mean anomaly in radians; can be any shape.
+ terms : int, optional
+ Number of terms for the Bessel series expansion (default is 40).
+ mod_2pi : bool, optional
+ Whether to return E modulo 2ฯ (default is True).
+
+ Returns:
+ -------
+ np.ndarray
+ Eccentric anomaly E in radians, with the same shape as M.
+ """
+ # Handle edge case where e is near zero, return M directly.
+ if np.isclose(e, 0):
+ return M
+
+ # Prepare for Bessel series expansion
+ n = np.arange(1, terms + 1)[:, None, None] # Shape (terms, 1, 1)
+ M_expanded = M[None, ...] # Add a new dimension to M, for broadcasting
+
+ # Calculate series sum using Bessel functions and sine terms
+ series_sum = np.sum((1 / n) * jv(n, n * e) * np.sin(n * M_expanded), axis=0)
+
+ # Calculate eccentric anomaly
+ E = M + 2 * series_sum
+
+ # Apply modulo operation if specified
+ if mod_2pi:
+ E = np.mod(E, 2 * np.pi)
+
+ return E
+
+
+def keplerian2eci(a, e, delta, Omega, omega, nu):
+ """
+ Calculate the position vector in ECI coordinates for each RAAN and true anomaly.
+
+ Parameters:
+ ----------
+ a : float
+ Semi-major axis in km.
+ e : float
+ Eccentricity (0 <= e < 1).
+ delta : float
+ Orbital inclination in degrees.
+ Omega : np.ndarray
+ Right ascension of the ascending node (RAAN) in degrees. Shape = (N,)
+ omega : float
+ Argument of perigee in degrees.
+ nu : np.ndarray
+ True anomaly in degrees. Shape = (N, length(t))
+
+ Returns:
+ -------
+ r_eci : np.ndarray
+ Position vector in ECI coordinates. Shape = (3, N, length(t))
+ """
+ # Convert angles from degrees to radians
+ delta_rad = np.radians(delta)
+ Omega_rad = np.radians(Omega)
+ omega_rad = np.radians(omega)
+ nu_rad = np.radians(nu)
+
+ # Compute gamma (angle between satellite position and ascending node in the orbital plane)
+ gamma = (nu_rad + omega_rad) % (2 * np.pi)
+
+ # Trigonometric calculations
+ cos_gamma = np.cos(gamma)
+ sin_gamma = np.sin(gamma)
+ cos_raan = np.cos(-Omega_rad)[:, np.newaxis] # Shape (N, 1) for broadcasting
+ sin_raan = np.sin(-Omega_rad)[:, np.newaxis] # Shape (N, 1) for broadcasting
+ cos_incl = np.cos(delta_rad)
+ sin_incl = np.sin(delta_rad)
+
+ # Calculate radius for each true anomaly
+ r = a * (1 - e ** 2) / (1 + e * np.cos(nu_rad))
+
+ # Position in ECI coordinates
+ x = r * (cos_gamma * cos_raan - sin_gamma * sin_raan * cos_incl)
+ y = r * (cos_gamma * sin_raan + sin_gamma * cos_raan * cos_incl)
+ z = r * sin_gamma * sin_incl
+
+ # Stack to form the ECI position vector with shape (3, N, length(t))
+ r_eci = np.array([x, y, z])
+
+ return r_eci
+
+
+def eci2ecef(t, r_eci):
+ """
+ Convert coordinates from Earth-Centered Inertial (ECI) to Earth-Centered Earth-Fixed (ECEF)
+ for each time step, accounting for Earth's rotation.
+
+ Parameters:
+ ----------
+ t : np.ndarray
+ A 1D array of time instants in seconds since the reference epoch. Shape = (T,)
+ r_eci : np.ndarray
+ A 3D array representing the position vectors in ECI coordinates (km). Shape = (3, N, T)
+
+ Returns:
+ -------
+ r_ecef : np.ndarray
+ A 3D array representing the position vectors in ECEF coordinates (km). Shape = (3, N, T)
+ """
+ # Earth's rotation rate in radians per second (WGS-84 value)
+ ฯe = 2 * np.pi / (24 * 3600) # rad/s
+
+ # Calculate the rotation angle ฮธ for each time instant in `t`
+ ฮธ = wrap2pi(ฯe * t) # Shape (T,)
+
+ # Create the rotation matrices for each ฮธ in the array
+ cos_ฮธ = np.cos(ฮธ)
+ sin_ฮธ = np.sin(ฮธ)
+
+ # Rotation matrices for each time step `t`, shape (T, 3, 3)
+ S = np.array([
+ [cos_ฮธ, sin_ฮธ, np.zeros_like(ฮธ)],
+ [-sin_ฮธ, cos_ฮธ, np.zeros_like(ฮธ)],
+ [np.zeros_like(ฮธ), np.zeros_like(ฮธ), np.ones_like(ฮธ)]
+ ]).transpose(2, 0, 1) # Shape (T, 3, 3)
+
+ # Transpose `r_eci` for easier matrix multiplication, shape (T, 3, N)
+ r_eci_t = r_eci.transpose(2, 0, 1)
+
+ # Apply the rotation for each time step using `einsum`
+ r_ecef_t = np.einsum('tij,tjk->tik', S, r_eci_t) # Shape (T, 3, N)
+
+ # Transpose back to shape (3, N, T) for the final result
+ r_ecef = r_ecef_t.transpose(1, 2, 0)
+
+ return r_ecef
+
+
+def plot_ground_tracks(theta_deg, phi_deg, planes=None, satellites=None, title="Satellite Ground Tracks"):
+ """
+ Plots the satellite ground tracks as points based on latitude and longitude data.
+
+ Parameters:
+ theta_deg (ndarray): Array of satellite latitudes in degrees, shape (num_planes * num_satellites_per_plane,
+ num_timesteps).
+ phi_deg (ndarray): Array of satellite longitudes in degrees, shape (num_planes * num_satellites_per_plane,
+ num_timesteps).
+ planes (list, optional): List of plane indices to plot. If None, all planes are plotted.
+ satellites (list, optional): List of satellite indices to plot. If None, all satellites are plotted.
+ title (str): Title for the plot.
+ """
+ # Determine the number of planes and satellites in the data
+ num_planes = len(np.unique(planes)) if planes is not None else theta_deg.shape[0]
+ num_satellites_per_plane = theta_deg.shape[0] // num_planes
+
+ # Set up the plot with Cartopy
+ fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={'projection': ccrs.PlateCarree()})
+ ax.set_global()
+ ax.add_feature(cfeature.COASTLINE)
+ ax.add_feature(cfeature.BORDERS, linestyle=':')
+ ax.gridlines(draw_labels=True)
+
+ # Loop through each plane and satellite, plotting ground tracks as points
+ for plane_idx in range(num_planes):
+ # Skip plane if it's not selected
+ if planes and plane_idx + 1 not in planes:
+ continue
+
+ for sat_idx in range(num_satellites_per_plane):
+ # Calculate satellite index based on plane and satellite number
+ satellite_global_idx = plane_idx * num_satellites_per_plane + sat_idx
+
+ # Skip satellite if it's not selected
+ if satellites and sat_idx + 1 not in satellites:
+ continue
+
+ # Plot the ground track points for the selected satellite
+ ax.scatter(
+ phi_deg[satellite_global_idx, :], theta_deg[satellite_global_idx, :],
+ label=f'Plane {plane_idx + 1}, Satellite {sat_idx + 1}', s=1, # Size of each point
+ transform=ccrs.PlateCarree()
+ )
+
+ # Add legend and title
+ ax.set_title(title)
+ ax.legend(loc='upper right', markerscale=5, fontsize='small')
+ plt.show()
diff --git a/sharc/satellite/ngso/orbit_model.py b/sharc/satellite/ngso/orbit_model.py
new file mode 100644
index 000000000..f8929b313
--- /dev/null
+++ b/sharc/satellite/ngso/orbit_model.py
@@ -0,0 +1,307 @@
+"""Implements a Space Station Orbit model as described in Rec. ITU-R S.1325-3
+"""
+
+import numpy as np
+
+from sharc.satellite.ngso.custom_functions import wrap2pi, eccentric_anomaly, keplerian2eci, eci2ecef, plot_ground_tracks
+from sharc.satellite.utils.sat_utils import ecef2lla
+from sharc.satellite.ngso.constants import EARTH_RADIUS_KM, KEPLER_CONST, EARTH_ROTATION_RATE
+
+
+class OrbitModel():
+ """Orbit Model for satellite positions."""
+
+ def __init__(self,
+ Nsp: int,
+ Np: int,
+ phasing: float,
+ long_asc: float,
+ omega: float,
+ delta: float,
+ hp: float,
+ ha: float,
+ Mo: float):
+ """Instantiates and OrbitModel object from the Orbit parameters as specified in S.1529.
+
+ Parameters
+ ----------
+ Nsp : int
+ number of satellites in the orbital plane (A.4.b.4.b)
+ Np : int
+ number of orbital planes (A.4.b.2)
+ phasing : float
+ satellite phasing between planes, in degrees
+ long_asc : float
+ initial longitude of ascending node of the first plane, in degrees
+ omega : float
+ argument of perigee, in degrees
+ delta : float
+ orbital plane inclination, in degrees
+ perigee_alt_km : float
+ altitude of perigee in km
+ ha : float
+ altitude of apogee in km
+ Mo : float
+ initial mean anomaly for first satellite of first plane, in degrees
+ """
+ self.Nsp = Nsp
+ self.Np = Np
+ self.phasing = phasing
+ self.long_asc = long_asc
+ self.omega = omega
+ self.delta = delta
+ self.perigee_alt_km = hp
+ self.apogee_alt_km = ha
+ self.Mo = Mo
+
+ # Derive other orbit parameters
+ self.semi_major_axis = (hp + ha + 2 * EARTH_RADIUS_KM) / 2 # semi-major axis, in km
+ self.eccentricity = (ha + EARTH_RADIUS_KM - self.semi_major_axis) / self.semi_major_axis # orbital eccentricity (e)
+ self.orbital_period_sec = 2 * np.pi * np.sqrt(self.semi_major_axis ** 3 / KEPLER_CONST) # orbital period, in seconds
+ self.sat_sep_angle_deg = 360 / self.Nsp # satellite separation angle in the plane (degrees)
+ self.orbital_plane_inclination = 360 / self.Np # angle between plane intersections with the equatorial plane (degrees)
+
+ # Initial mean anomalies for all the satellites
+ initial_mean_anomalies_deg = (Mo + np.arange(Nsp) * self.sat_sep_angle_deg + np.arange(self.Np)[:, np.newaxis] *
+ self.phasing) % 360
+ self.initial_mean_anomalies_rad = np.radians(initial_mean_anomalies_deg.flatten())
+
+ # Initial longitudes of ascending node for all the planes
+ inital_raan = (self.long_asc * np.ones(self.Nsp) + np.arange(self.Np)[:, np.newaxis] *
+ self.orbital_plane_inclination) % 360
+ self.inital_raan_rad = np.radians(inital_raan.flatten())
+
+ # Calculated variables
+ self.mean_anomaly = None # computed mean anomalies
+ self.raan_rad = None # computed longitudes of the ascending node
+ self.eccentric_anomaly = None # computed eccentric anomalies
+ self.true_anomaly = None # computed true anomalies
+ self.distance = None # computed distance to Earth's center
+ self.true_anomaly_rel_line_nodes = None # computed true anomaly relative to the line of nodes
+
+ def get_satellite_positions_time_interval(self, initial_time_secs=0, interval_secs=5, n_periods=4) -> dict:
+ """
+ Return the orbit positions vector.
+
+ Parameters
+ ----------
+ initial_time_secs : int, optional
+ initial time instant in seconds, by default 0
+ interval_secs : int, optional
+ time interval between points, by default 5
+ n_periods : int, optional
+ number of orbital peridos, by default 4
+
+ Returns
+ -------
+ dict
+ A dictionary with satellite positions in spherical and ecef coordinates.
+ lat, lon, sx, sy, sz
+ """
+ t = np.arange(initial_time_secs, n_periods * self.orbital_period_sec + interval_secs, interval_secs)
+ return self.__get_satellite_positions(t)
+
+ def get_orbit_positions_time_instant(self, time_instant_secs=0) -> dict:
+ """Returns satellite positions in a determined time instant in seconds"""
+ t = np.array([time_instant_secs])
+ return self.__get_satellite_positions(t)
+
+ def get_orbit_positions_random(self, rng: np.random.RandomState, n_samples=1) -> dict:
+ """Returns satellite positions in a random time instant in seconds.
+ Parameters
+ ----------
+ rng : np.random.RandomState
+ Random number generator for reproducibility
+ n_samples : int
+ Number of random samples to generate, by default 1
+ Returns
+ -------
+ dict
+ A dictionary with satellite positions in spherical and ecef coordinates.
+ lat, lon, sx, sy, sz
+ """
+
+ # return self.__get_satellite_positions(rng.random_sample(1) * 1000 * self.orbital_period_sec)
+ # Mean anomaly (M)
+ self.mean_anomaly = (self.initial_mean_anomalies_rad[:, None] +
+ 2 * np.pi * rng.random_sample(n_samples)) % (2 * np.pi)
+
+ # Eccentric anomaly (E)
+ self.eccentric_anom = eccentric_anomaly(self.eccentricity, self.mean_anomaly)
+
+ # True anomaly (v)
+ self.true_anomaly = 2 * np.arctan(np.sqrt((1 + self.eccentricity) / (1 - self.eccentricity)) *
+ np.tan(self.eccentric_anom / 2))
+
+ self.true_anomaly = np.mod(self.true_anomaly, 2 * np.pi)
+
+ # Distance of the satellite to Earth's center (r)
+ r = self.semi_major_axis * (1 - self.eccentricity ** 2) / (1 + self.eccentricity * np.cos(self.true_anomaly))
+
+ # True anomaly relative to the line of nodes (gamma)
+ self.true_anomaly_rel_line_nodes = wrap2pi(self.true_anomaly + np.radians(self.omega)) # gamma in the interval [-pi, pi]
+
+ # Latitudes of the satellites, in radians (theta)
+ # theta = np.arcsin(np.sin(gamma) * np.sin(np.radians(self.delta)))
+
+ # Longitude variation due to angular displacement, in radians (phiS)
+ # phiS = np.arccos(np.cos(gamma) / np.cos(theta)) * np.sign(gamma)
+
+ # Longitudes of the ascending node (OmegaG)
+ raan_rad = (self.inital_raan_rad[:, None] + 2 * np.pi * rng.random_sample(n_samples)) # shape (Np*Nsp, len(t))
+ raan_rad = wrap2pi(raan_rad)
+
+ # POSITION CALCULATION IN ECEF COORDINATES - ITU-R S.1503
+ r_eci = keplerian2eci(self.semi_major_axis,
+ self.eccentricity,
+ self.delta,
+ np.degrees(self.inital_raan_rad),
+ self.omega,
+ np.degrees(self.true_anomaly))
+
+ r_ecef = eci2ecef(2 * np.pi * rng.random_sample(n_samples) / EARTH_ROTATION_RATE, r_eci)
+ sx, sy, sz = r_ecef[0], r_ecef[1], r_ecef[2]
+ lat = np.degrees(np.arcsin(sz / r))
+ lon = np.degrees(np.arctan2(sy, sx))
+ # (lat, lon, _) = ecef2lla(sx, sy, sz)
+
+ pos_vector = {
+ 'lat': lat,
+ 'lon': lon,
+ 'sx': sx,
+ 'sy': sy,
+ 'sz': sz
+ }
+ return pos_vector
+
+ def __get_satellite_positions(self, t: np.array) -> dict:
+ """Returns the Satellite positins (both lat long and ecef) for a given time vector within the orbit period.
+
+ Parameters
+ ----------
+ t : np.array
+ time instants inside the orbit period in seconds
+
+ Returns
+ -------
+ dict
+ A dictionary with satellite positions in spherical and ecef coordinates.
+ lat, lon, sx, sy, sz
+ """
+ # Mean anomaly (M)
+ self.mean_anomaly = (self.initial_mean_anomalies_rad[:, None] +
+ (2 * np.pi / self.orbital_period_sec) * t) % (2 * np.pi)
+
+ # Eccentric anomaly (E)
+ self.eccentric_anom = eccentric_anomaly(self.eccentricity, self.mean_anomaly)
+
+ # True anomaly (v)
+ self.true_anomaly = 2 * np.arctan(np.sqrt((1 + self.eccentricity) / (1 - self.eccentricity)) *
+ np.tan(self.eccentric_anom / 2))
+ self.true_anomaly = np.mod(self.true_anomaly, 2 * np.pi)
+
+ # Distance of the satellite to Earth's center (r)
+ r = self.semi_major_axis * (1 - self.eccentricity ** 2) / (1 + self.eccentricity * np.cos(self.true_anomaly))
+
+ # True anomaly relative to the line of nodes (gamma)
+ self.true_anomaly_rel_line_nodes = wrap2pi(self.true_anomaly + np.radians(self.omega)) # gamma in the interval [-pi, pi]
+
+ # Latitudes of the satellites, in radians (theta)
+ # theta = np.arcsin(np.sin(gamma) * np.sin(np.radians(self.delta)))
+
+ # Longitude variation due to angular displacement, in radians (phiS)
+ # phiS = np.arccos(np.cos(gamma) / np.cos(theta)) * np.sign(gamma)
+
+ # Longitudes of the ascending node (OmegaG)
+ raan_rad = (self.inital_raan_rad[:, None] + EARTH_ROTATION_RATE * t) # shape (Np*Nsp, len(t))
+ raan_rad = wrap2pi(raan_rad)
+
+ # POSITION CALCULATION IN ECEF COORDINATES - ITU-R S.1503
+ r_eci = keplerian2eci(self.semi_major_axis,
+ self.eccentricity,
+ self.delta,
+ np.degrees(self.inital_raan_rad),
+ self.omega,
+ np.degrees(self.true_anomaly))
+
+ r_ecef = eci2ecef(t, r_eci)
+ sx, sy, sz = r_ecef[0], r_ecef[1], r_ecef[2]
+ lat = np.degrees(np.arcsin(sz / r))
+ lon = np.degrees(np.arctan2(sy, sx))
+ # (lat, lon, _) = ecef2lla(sx, sy, sz)
+
+ pos_vector = {
+ 'lat': lat,
+ 'lon': lon,
+ 'sx': sx,
+ 'sy': sy,
+ 'sz': sz
+ }
+ return pos_vector
+
+
+def main():
+ """Main function to test the OrbitModel class and plot ground tracks.
+
+ This function creates an instance of the OrbitModel class with specified parameters,
+ retrieves satellite positions over a specified time interval, and plots the ground tracks
+ of the satellites using Plotly.
+ """
+ import plotly.graph_objects as go
+
+ orbit_params = {
+ "Nsp": 1,
+ "Np": 28,
+ "phasing": 1.5,
+ "long_asc": 0,
+ "omega": 0,
+ "delta": 53,
+ "hp": 525,
+ "ha": 525,
+ "Mo": 0
+ }
+
+ print("Orbit parameters:")
+ print(orbit_params)
+
+ # Instantiate the OrbitModel
+ orbit_model = OrbitModel(**orbit_params)
+
+ # Get satellite positions over time
+ positions = orbit_model.get_satellite_positions_time_interval(n_periods=1)
+
+ # Extract latitude and longitude
+ latitudes = positions['lat']
+ longitudes = positions['lon']
+
+ # Create a plotly figure
+ fig = go.Figure()
+
+ # Add traces for each satellite
+ for i in range(latitudes.shape[0]):
+ # for i in range(10, 20):
+ fig.add_trace(go.Scattergeo(
+ lon=longitudes[i],
+ lat=latitudes[i],
+ mode='lines',
+ name=f'Satellite {i + 1}'
+ ))
+
+ # Update layout for better visualization
+ fig.update_layout(
+ title="Satellite Ground Tracks",
+ showlegend=False,
+ geo=dict(
+ projection_type="equirectangular",
+ showland=True,
+ landcolor="rgb(243, 243, 243)",
+ countrycolor="rgb(204, 204, 204)"
+ )
+ )
+
+ # Show the plot
+ fig.show()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sharc/satellite/ngso/orbit_predictor_s1503.py b/sharc/satellite/ngso/orbit_predictor_s1503.py
new file mode 100644
index 000000000..c39bc36a1
--- /dev/null
+++ b/sharc/satellite/ngso/orbit_predictor_s1503.py
@@ -0,0 +1,356 @@
+import numpy as np
+import pandas as pd
+from scipy.special import jv
+import matplotlib.pyplot as plt
+import cartopy.crs as ccrs
+import cartopy.feature as cfeature
+import time
+
+# START OF TIMER
+start_time = time.time()
+
+# CONSTANTS
+EARTH_RADIUS_KM = 6378.145 # radius of the Earth, in km
+KEPLER_CONST = 398601.8 # Kepler's constant, in km^3/s^2
+EARTH_ROTATION_RATE = 2 * np.pi / (24 * 3600) # earth's average rotation rate, in rad/s
+
+# INPUTS - NGSO SYSTEM
+
+# Globalstar
+Nsp = 6 # number of satellites in the orbital plane (A.4.b.4.b)
+Np = 8 # number of orbital planes (A.4.b.2)
+phasing = 7.5 # satellite phasing between planes, in degrees
+long_asc = 0 # initial longitude of ascending node of the first plane, in degrees (A.4.b.4.j)
+omega = 0 # argument of perigee, in degrees (A.4.b.4.i)
+delta = 52 # orbital plane inclination, in degrees (A.4.b.4.a)
+hp = 1414 # altitude of perigee in km (A.4.b.4.e)
+ha = 1414 # altitude of apogee in km (A.4.b.4.d)
+Mo = 0 # initial mean anomaly for first satellite of first plane, in degrees
+
+# # Molniya
+# Nsp = 1 # number of satellites in the orbital plane (A.4.b.4.b)
+# Np = 9 # number of orbital planes (A.4.b.2)
+# phasing = 80 # Satellite phasing between planes (degrees)
+# long_asc = 90 # longitude of ascending node in degrees (A.4.b.4.j) or RAAN
+# omega = -90 # argument of perigee in degrees
+# delta = 63.4 # inclination of the orbital plane in degrees
+# hp = 950 # altitude at perigee in km
+# ha = 39520 # altitude at apogee in km
+# Mo = 0 # initial mean anomaly for first satellite of first plane
+
+a = (hp + ha + 2 * EARTH_RADIUS_KM) / 2 # semi-major axis, in km
+e = (ha + EARTH_RADIUS_KM - a) / a # orbital eccentricity (e)
+P = 2 * np.pi * np.sqrt(a ** 3 / KEPLER_CONST) # orbital period, in seconds
+beta = 360 / Nsp # satellite separation angle in the plane (degrees)
+psi = 360 / Np # angle between plane intersections with the equatorial plane (degrees)
+
+# Initial mean anomalies for all the satellites
+M_o = (Mo + np.arange(Nsp) * beta + np.arange(Np)[:, None] * phasing) % 360 # shape (Np, Nsp)
+M0 = np.radians(M_o.flatten()) # shape (Np*Nsp,)
+
+# Initial longitudes of ascending node for all the planes
+Omega_o = (long_asc + np.arange(Nsp) * 0 + np.arange(Np)[:, None] * psi) % 360 # shape (Np, Nsp)
+Omega0 = np.radians(Omega_o.flatten()) # shape (Np*Nsp,)
+
+# INPUTS - EARTH SYSTEM
+
+# station position vector, in km
+lat_es = [0, -10] # latitudes of stations, in degrees
+lon_es = [0, -30] # longitudes of stations, in degrees
+alt_es = [0, 0] # altitudes of stations, in meters
+rx = EARTH_RADIUS_KM + alt_es[0] * 10**-3
+px = rx * np.cos(np.radians(lat_es[0])) * np.cos(np.radians(lon_es[0]))
+py = rx * np.cos(np.radians(lat_es[0])) * np.sin(np.radians(lon_es[0]))
+pz = rx * np.sin(np.radians(lat_es[0]))
+p = np.array([px, py, pz])
+
+# INPUT - TIME OF SIMULATION
+
+interval = 5 # time interval, in seconds
+nP = 4 # number of orbital periods of simulation
+t = np.arange(0, nP * P + interval, interval) # vector of time, in seconds
+
+# CUSTOM FUNCTIONS
+
+
+def wrap2pi(angle_rad):
+ """
+ Adjusts an angle in radians to the interval (-ฯ, ฯ).
+ """
+ return (angle_rad + np.pi) % (2 * np.pi) - np.pi
+
+
+def eccentric_anomaly(e, M, terms=40, mod_2pi=True):
+ """
+ Calculate the eccentric anomaly E given the eccentricity e and
+ mean anomaly M using a Bessel series expansion.
+
+ Parameters:
+ ----------
+ e: float
+ Eccentricity of the orbit (0 <= e < 1).
+ M: np.ndarray
+ Mean anomaly in radians; can be any shape.
+ terms: int, optional
+ Number of terms for the Bessel series expansion (default is 40).
+ mod_2pi: bool, optional
+ Whether to return E modulo 2ฯ (default is True).
+
+ Returns:
+ -------
+ np.ndarray
+ Eccentric anomaly E in radians, with the same shape as M.
+ """
+ # Handle edge case where e is near zero, return M directly.
+ if np.isclose(e, 0):
+ return M
+
+ # Prepare for Bessel series expansion
+ n = np.arange(1, terms + 1)[:, None, None] # Shape (terms, 1, 1)
+ M_expanded = M[None, ...] # Add a new dimension to M, for broadcasting
+
+ # Calculate series sum using Bessel functions and sine terms
+ series_sum = np.sum((1 / n) * jv(n, n * e) * np.sin(n * M_expanded), axis=0)
+
+ # Calculate eccentric anomaly
+ E = M + 2 * series_sum
+
+ # Apply modulo operation if specified
+ if mod_2pi:
+ E = np.mod(E, 2 * np.pi)
+
+ return E
+
+
+def keplerian2eci(a, e, delta, Omega, omega, nu):
+ """
+ Calculate the position vector in ECI coordinates for each RAAN and true anomaly.
+
+ Parameters:
+ ----------
+ a : float
+ Semi-major axis in km.
+ e : float
+ Eccentricity (0 <= e < 1).
+ delta : float
+ Orbital inclination in degrees.
+ Omega : np.ndarray
+ Right ascension of the ascending node (RAAN) in degrees. Shape = (N,)
+ omega : float
+ Argument of perigee in degrees.
+ nu : np.ndarray
+ True anomaly in degrees. Shape = (N, length(t))
+
+ Returns:
+ -------
+ r_eci : np.ndarray
+ Position vector in ECI coordinates. Shape = (3, N, length(t))
+ """
+ # Convert angles from degrees to radians
+ delta_rad = np.radians(delta)
+ Omega_rad = np.radians(Omega)
+ omega_rad = np.radians(omega)
+ nu_rad = np.radians(nu)
+
+ # Compute gamma (angle between satellite position and ascending node in the orbital plane)
+ gamma = (nu_rad + omega_rad) % (2 * np.pi)
+
+ # Trigonometric calculations
+ cos_gamma = np.cos(gamma)
+ sin_gamma = np.sin(gamma)
+ cos_raan = np.cos(-Omega_rad)[:, np.newaxis] # Shape (N, 1) for broadcasting
+ sin_raan = np.sin(-Omega_rad)[:, np.newaxis] # Shape (N, 1) for broadcasting
+ cos_incl = np.cos(delta_rad)
+ sin_incl = np.sin(delta_rad)
+
+ # Calculate radius for each true anomaly
+ r = a * (1 - e ** 2) / (1 + e * np.cos(nu_rad))
+
+ # Position in ECI coordinates
+ x = r * (cos_gamma * cos_raan - sin_gamma * sin_raan * cos_incl)
+ y = r * (cos_gamma * sin_raan + sin_gamma * cos_raan * cos_incl)
+ z = r * sin_gamma * sin_incl
+
+ # Stack to form the ECI position vector with shape (3, N, length(t))
+ r_eci = np.array([x, y, z])
+
+ return r_eci
+
+
+def eci2ecef(t, r_eci):
+ """
+ Convert coordinates from Earth-Centered Inertial (ECI) to Earth-Centered Earth-Fixed (ECEF)
+ for each time step, accounting for Earth's rotation.
+
+ Parameters:
+ ----------
+ t : np.ndarray
+ A 1D array of time instants in seconds since the reference epoch. Shape = (T,)
+ r_eci : np.ndarray
+ A 3D array representing the position vectors in ECI coordinates (km). Shape = (3, N, T)
+
+ Returns:
+ -------
+ r_ecef : np.ndarray
+ A 3D array representing the position vectors in ECEF coordinates (km). Shape = (3, N, T)
+ """
+ # Earth's rotation rate in radians per second (WGS-84 value)
+ ฯe = 2 * np.pi / (24 * 3600) # rad/s
+
+ # Calculate the rotation angle ฮธ for each time instant in `t`
+ ฮธ = wrap2pi(ฯe * t) # Shape (T,)
+
+ # Create the rotation matrices for each ฮธ in the array
+ cos_ฮธ = np.cos(ฮธ)
+ sin_ฮธ = np.sin(ฮธ)
+
+ # Rotation matrices for each time step `t`, shape (T, 3, 3)
+ S = np.array([
+ [cos_ฮธ, sin_ฮธ, np.zeros_like(ฮธ)],
+ [-sin_ฮธ, cos_ฮธ, np.zeros_like(ฮธ)],
+ [np.zeros_like(ฮธ), np.zeros_like(ฮธ), np.ones_like(ฮธ)]
+ ]).transpose(2, 0, 1) # Shape (T, 3, 3)
+
+ # Transpose `r_eci` for easier matrix multiplication, shape (T, 3, N)
+ r_eci_t = r_eci.transpose(2, 0, 1)
+
+ # Apply the rotation for each time step using `einsum`
+ r_ecef_t = np.einsum('tij,tjk->tik', S, r_eci_t) # Shape (T, 3, N)
+
+ # Transpose back to shape (3, N, T) for the final result
+ r_ecef = r_ecef_t.transpose(1, 2, 0)
+
+ return r_ecef
+
+
+def plot_ground_tracks(theta_deg, phi_deg, planes=None, satellites=None, title="Satellite Ground Tracks"):
+ """
+ Plots the satellite ground tracks as points based on latitude and longitude data.
+
+ Parameters:
+ theta_deg (ndarray): Array of satellite latitudes in degrees, shape (num_planes * num_satellites_per_plane,
+ num_timesteps).
+ phi_deg (ndarray): Array of satellite longitudes in degrees, shape (num_planes * num_satellites_per_plane,
+ num_timesteps).
+ planes (list, optional): List of plane indices to plot. If None, all planes are plotted.
+ satellites (list, optional): List of satellite indices to plot. If None, all satellites are plotted.
+ title (str): Title for the plot.
+ """
+ # Determine the number of planes and satellites in the data
+ num_planes = len(np.unique(planes)) if planes is not None else theta_deg.shape[0]
+ num_satellites_per_plane = theta_deg.shape[0] // num_planes
+
+ # Set up the plot with Cartopy
+ fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={'projection': ccrs.PlateCarree()})
+ ax.set_global()
+ ax.add_feature(cfeature.COASTLINE)
+ ax.add_feature(cfeature.BORDERS, linestyle=':')
+ ax.gridlines(draw_labels=True)
+
+ # Loop through each plane and satellite, plotting ground tracks as points
+ for plane_idx in range(num_planes):
+ # Skip plane if it's not selected
+ if planes and plane_idx + 1 not in planes:
+ continue
+
+ for sat_idx in range(num_satellites_per_plane):
+ # Calculate satellite index based on plane and satellite number
+ satellite_global_idx = plane_idx * num_satellites_per_plane + sat_idx
+
+ # Skip satellite if it's not selected
+ if satellites and sat_idx + 1 not in satellites:
+ continue
+
+ # Plot the ground track points for the selected satellite
+ ax.scatter(
+ phi_deg[satellite_global_idx, :], theta_deg[satellite_global_idx, :],
+ label=f'Plane {plane_idx + 1}, Satellite {sat_idx + 1}', s=1, # Size of each point
+ transform=ccrs.PlateCarree()
+ )
+
+ # Add legend and title
+ ax.set_title(title)
+ ax.legend(loc='upper right', markerscale=5, fontsize='small')
+ plt.show()
+
+
+if __name__ == "__main__":
+ # POSITION CALCULATION IN THE ORBITAL PLANE (v and r)
+
+ # Mean anomaly (M)
+ mean_anomaly = (M0[:, None] + (2 * np.pi / P) * t) % (2 * np.pi)
+
+ # Eccentric anomaly (E)
+ eccentric_anom = eccentric_anomaly(e, mean_anomaly)
+
+ # True anomaly (v)
+ v = 2 * np.arctan(np.sqrt((1 + e) / (1 - e)) * np.tan(eccentric_anom / 2))
+ v = np.mod(v, 2 * np.pi)
+
+ # Distance of the satellite to Earth's center (r)
+ r = a * (1 - e ** 2) / (1 + e * np.cos(v))
+
+ # True anomaly relative to the line of nodes (gamma)
+ gamma = wrap2pi(v + np.radians(omega)) # gamma in the interval [-pi, pi]
+
+ # Latitudes of the satellites, in radians (theta)
+ theta = np.arcsin(np.sin(gamma) * np.sin(np.radians(delta)))
+
+ # Longitude variation due to angular displacement, in radians (phiS)
+ phiS = np.arccos(np.cos(gamma) / np.cos(theta)) * np.sign(gamma)
+
+ # Longitudes of the ascending node (OmegaG)
+ OmegaG = (Omega0[:, None] + EARTH_ROTATION_RATE * t) # shape (Np*Nsp, len(t))
+ OmegaG = wrap2pi(OmegaG)
+
+ # POSITION CALCULATION IN ECEF COORDINATES - ITU-R S.1503
+
+ r_eci = keplerian2eci(a, e, delta, np.degrees(Omega0), omega, np.degrees(v))
+ r_ecef = eci2ecef(t, r_eci)
+ sx, sy, sz = r_ecef[0], r_ecef[1], r_ecef[2]
+ lat = np.degrees(np.arcsin(sz / r))
+ lon = np.degrees(np.arctan2(sy, sx))
+
+ # MAP
+ # static map
+ plot_ground_tracks(lat, lon, planes=[1, 2, 3, 4, 5, 6, 7, 8], satellites=[1])
+
+ # DATAFRAME WITH INITIAL DATA
+ data = []
+ for i, time_step in enumerate(t):
+ for plane in range(Np):
+ for satellite in range(Nsp):
+ sat_index = plane * Nsp + satellite # Calculate the index for flattened arrays
+ # Append data for each satellite at each time step
+ data.append([
+ time_step, # Time
+ plane + 1, # Plane number
+ satellite + 1, # Satellite number
+ M_o[plane, satellite], # Mean anomaly (M_o) in degrees
+ Omega_o[plane, satellite], # Longitude of ascending node (Omega_o) in degrees
+ r[sat_index, i], # distance in km
+ lat[sat_index, i], # Latitude in degrees
+ lon[sat_index, i], # Longitude in degrees
+ sx[sat_index, i], # Cartesian x position
+ sy[sat_index, i], # Cartesian y position
+ sz[sat_index, i] # Cartesian z position
+ ])
+
+ df = pd.DataFrame(data, columns=['time',
+ 'plane',
+ 'satellite',
+ 'M_o',
+ 'Omega_o',
+ 'r',
+ 'lat',
+ 'lon',
+ 'sx',
+ 'sy',
+ 'sz'])
+
+ # END OF TIMER
+ end_time = time.time()
+
+ # Calculate elapsed time
+ elapsed_time = end_time - start_time
+ print(f"Execution time: {elapsed_time:.4f} seconds")
diff --git a/sharc/satellite/ngso_parameters.yaml b/sharc/satellite/ngso_parameters.yaml
new file mode 100644
index 000000000..0e0216c5c
--- /dev/null
+++ b/sharc/satellite/ngso_parameters.yaml
@@ -0,0 +1,74 @@
+simulation:
+ seed: 101
+ num_snapshots: 1000
+ simulaton_time_span_sec: 3600
+ time_step_sec: 5
+ min_elev_angle_deg: 30 # minimum elevaation angle between earth-station and space-station for communication
+ simulation_time_start_sec: 0
+ # Optional parameters. Deal with them later
+ # simulation_time_end_sec: 3600
+ # simulation_time_step_sec: 5
+ # precession:
+ # traffic_model: full-buffer
+ # perturbations:
+constellations:
+ - name: Acme-Star-1
+ space_station:
+ antenna_pattern: Taylor1.4
+ max_transmit_power_dB: 46.0
+ max_transmit_gain_dBi: 30.0
+ max_receive_gain_dBi: 30.0
+ max_num_of_beams: 19
+ cell_radius_m: 19000
+ orbit_shells:
+ - n_planes: 20
+ inclination_deg: 54.5
+ perigee_alt_km: 525.0
+ apogee_alt_km: 525.0
+ sats_per_plane: 32
+ long_asc_deg: 18.0
+ phasing_deg: 3.9
+ - n_planes: 12
+ inclination_deg: 26.0
+ perigee_alt_km: 580.0
+ apogee_alt_km: 580.0
+ sats_per_plane: 20
+ long_asc_deg: 30.0
+ phasing_deg: 2.0
+ - n_planes: 26
+ inclination_deg: 97.77
+ perigee_alt_km: 595
+ apogee_alt_km: 595.0
+ sats_per_plane: 30
+ long_asc_deg: 14.0
+ phasing_deg: 7.8
+ - name: Acme-Star-2
+ space_station:
+ antenna_pattern: Taylor1.4
+ max_transmit_power_dB: 46.0
+ max_transmit_gain_dBi: 30.0
+ max_receive_gain_dBi: 30.0
+ max_num_of_beams: 19
+ cell_radius_m: 19000
+ orbit_shells:
+ - n_planes: 20
+ inclination_deg: 54.5
+ perigee_alt_km: 525.0
+ apogee_alt_km: 525.0
+ sats_per_plane: 32
+ long_asc_deg: 18.0
+ phasing_deg: 3.9
+ - n_planes: 12
+ inclination_deg: 26.0
+ perigee_alt_km: 580.0
+ apogee_alt_km: 580.0
+ sats_per_plane: 20
+ long_asc_deg: 30.0
+ phasing_deg: 2.0
+ - n_planes: 26
+ inclination_deg: 97.77
+ perigee_alt_km: 595
+ apogee_alt_km: 595.0
+ sats_per_plane: 30
+ long_asc_deg: 14.0
+ phasing_deg: 7.8
diff --git a/sharc/satellite/scripts/plot_footprints.py b/sharc/satellite/scripts/plot_footprints.py
new file mode 100644
index 000000000..5617d7987
--- /dev/null
+++ b/sharc/satellite/scripts/plot_footprints.py
@@ -0,0 +1,481 @@
+# Generates a 3D plot of the Earth with the satellites positions
+# https://geopandas.org/en/stable/docs/user_guide/io.html
+import os
+import geopandas as gpd
+import numpy as np
+import plotly.graph_objects as go
+
+from sharc.station_factory import StationFactory
+from sharc.parameters.parameters_mss_d2d import ParametersOrbit, ParametersMssD2d
+from sharc.support.sharc_geom import GeometryConverter
+from sharc.satellite.utils.sat_utils import ecef2lla
+from sharc.station_manager import StationManager
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+
+
+geoconv = GeometryConverter()
+
+sys_lat = -14.5
+sys_long = -45
+sys_alt = 1200
+
+geoconv.set_reference(
+ sys_lat, sys_long, sys_alt
+)
+
+
+def plot_back(fig):
+ """back half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ # 50 latitude points from -90 to 90 degrees.
+ lat_vals = np.linspace(-90, 90, 50)
+ # 50 longitude points for the front half.
+ lon_vals = np.linspace(0, 180, 50)
+ # lon and lat will be 2D arrays.
+ lon, lat = np.meshgrid(lon_vals, lat_vals)
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(
+ lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=x / 1e3, y=y / 1e3, z=z / 1e3,
+ # Uniform color scale for a solid color.
+ colorscale=[[0, clor], [1, clor]],
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1),
+ colorbar=dict(
+ tickmode='array',
+ tickvals=[0],
+ ticktext=[""],
+ title=""
+ ),
+ )
+
+
+def plot_front(fig):
+ """front half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ # 50 latitude points from -90 to 90 degrees.
+ lat_vals = np.linspace(-90, 90, 50)
+ # 50 longitude points for the front half.
+ lon_vals = np.linspace(-180, 0, 50)
+ # lon and lat will be 2D arrays.
+ lon, lat = np.meshgrid(lon_vals, lat_vals)
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(
+ lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=x / 1e3, y=y / 1e3, z=z / 1e3,
+ # Uniform color scale for a solid color.
+ colorscale=[[0, clor], [1, clor]],
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1),
+ colorbar=dict(
+ tickmode='array',
+ tickvals=[0],
+ ticktext=[""],
+ title=""
+ ),
+ )
+
+
+def plot_polygon(poly, div=1, alt=0):
+ xy_coords = poly.exterior.coords.xy
+ lon = np.array(xy_coords[0])
+ lat = np.array(xy_coords[1])
+
+ # lon = lon * np.pi / 180
+ # lat = lat * np.pi / 180
+
+ # R = EARTH_RADIUS_KM
+ x, y, z = geoconv.convert_lla_to_transformed_cartesian(lat, lon, alt)
+
+ return x / div, y / div, z / div
+
+
+def plot_mult_polygon(mult_poly, div=1e3):
+ if mult_poly.geom_type == 'Polygon':
+ return [plot_polygon(mult_poly, div=div, alt=1000)]
+ elif mult_poly.geom_type == 'MultiPolygon':
+ return [plot_polygon(poly, div=div, alt=1000) for poly in mult_poly.geoms]
+
+
+def plot_globe_with_borders():
+ # Read the shapefile. Creates a DataFrame object
+ countries_borders_shp_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "../../data/countries/ne_110m_admin_0_countries.shp")
+ gdf = gpd.read_file(countries_borders_shp_file)
+ fig = go.Figure()
+ fig.update_layout(
+ scene=dict(
+ aspectmode="data",
+ xaxis=dict(showbackground=False),
+ yaxis=dict(showbackground=False),
+ zaxis=dict(showbackground=False)
+ ),
+ margin=dict(l=0, r=0, b=0, t=0)
+ )
+ # return fig
+ plot_front(fig)
+ plot_back(fig)
+ x_all, y_all, z_all = [], [], []
+
+ for i in gdf.index:
+ # print(gdf.loc[i].NAME) # Call a specific attribute
+
+ polys = gdf.loc[i].geometry # Polygons or MultiPolygons
+
+ if polys.geom_type == 'Polygon':
+ x, y, z = plot_polygon(polys)
+ x_all.extend(x / 1e3)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y / 1e3)
+ y_all.extend([None])
+ z_all.extend(z / 1e3)
+ z_all.extend([None])
+
+ elif polys.geom_type == 'MultiPolygon':
+
+ for poly in polys.geoms:
+ x, y, z = plot_polygon(poly)
+ x_all.extend(x / 1e3)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y / 1e3)
+ y_all.extend([None])
+ z_all.extend(z / 1e3)
+ z_all.extend([None])
+
+ fig.add_trace(go.Scatter3d(x=x_all, y=y_all, z=z_all, mode='lines',
+ line=dict(color='rgb(0, 0, 0)'), showlegend=False))
+
+ return fig
+
+
+if __name__ == "__main__":
+ colors = ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#7f0000']
+ # steps from previous section
+ # e.g. [3, 3, 14] means that 1st clr will be for -3 dB, 2nd for -6dB and 3rd for -20dB
+ # from normalized gains
+ step = [3, 17, 20, 20] # dB
+ # choose a seed to use for getting the satellites
+ # if seed is different, different random numbers are taken
+ SEED = 31
+ # choose if colors appear continuously or in discrete steps
+ DISCRETIZE = True
+ # choose if a surface point received gain should be all sat gains summed
+ # or if only max gain will be considered
+ SUM_GAINS = False
+
+ # Define the orbit parameters for two satellite constellations
+ orbit_1 = ParametersOrbit(
+ n_planes=28, sats_per_plane=120, phasing_deg=1.5, long_asc_deg=0,
+ inclination_deg=53.0, perigee_alt_km=525.0, apogee_alt_km=525.0
+ )
+
+ # Antenna parameters
+ g_max = 34.1 # dBi
+ l_r = l_t = 1.6 # meters
+ slr = 20 # dB
+ n_side_lobes = 2 # number of side lobes
+ freq = 2e3 # MHz
+
+ antenna_params = ParametersAntennaS1528(
+ antenna_gain=g_max,
+ frequency=freq, # in MHz
+ bandwidth=5, # in MHz
+ slr=slr,
+ n_side_lobes=n_side_lobes,
+ l_r=l_r,
+ l_t=l_t
+ )
+
+ spotbeam_radius = 39475 # meters
+
+ # Configure the MSS D2D system parameters
+ params = ParametersMssD2d(
+ name="Example-MSS-D2D",
+ antenna_pattern="ITU-R-S.1528-Taylor",
+ num_sectors=19,
+ antenna_s1528=antenna_params,
+ intersite_distance=np.sqrt(3) * spotbeam_radius,
+ orbits=[orbit_1]
+ )
+ params.sat_is_active_if.conditions = [
+ # "MINIMUM_ELEVATION_FROM_ES",
+ "LAT_LONG_INSIDE_COUNTRY",
+ ]
+ params.sat_is_active_if.minimum_elevation_from_es = 5.0
+ params.sat_is_active_if.lat_long_inside_country.country_names = ["Brazil"]
+ # params.beams_load_factor = 0.1
+ # params.center_beam_positioning.type = "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE"
+ # params.center_beam_positioning.angle_from_subsatellite_phi.type = "~U(MIN,MAX)"
+ # params.center_beam_positioning.angle_from_subsatellite_phi.distribution.min = -60.0
+ # params.center_beam_positioning.angle_from_subsatellite_phi.distribution.max = 60.0
+ # params.center_beam_positioning.distance_from_subsatellite.type = "~SQRT(U(0,1))*MAX"
+ # params.center_beam_positioning.distance_from_subsatellite.distribution.max = 1111000.0
+
+ print("instantiating stations")
+ # Create a topology with a single base station
+ from sharc.topology.topology_single_base_station import TopologySingleBaseStation
+ imt_topology = TopologySingleBaseStation(
+ cell_radius=500, num_clusters=1
+ )
+
+ # Create a random number generator
+ rng = np.random.RandomState(seed=SEED)
+
+ # Lists to store satellite positions (all and visible)
+ # Plot the ground station (blue marker)
+ # ground_sta_pos = lla2ecef(sys_lat, sys_long, sys_alt)
+ ground_sta_pos = geoconv.convert_lla_to_transformed_cartesian(
+ sys_lat, sys_long, sys_alt)
+
+ center_of_earth = StationManager(1)
+ # rotated and then translated center of earth
+ center_of_earth.x = np.array([0.0])
+ center_of_earth.y = np.array([0.0])
+ center_of_earth.z = np.array([-geoconv.get_translation()])
+
+ mss_d2d_manager = StationFactory.generate_mss_d2d(params, rng, geoconv)
+
+ # Extract satellite positions
+ x_vec = mss_d2d_manager.x / 1e3 # (Km)
+ y_vec = mss_d2d_manager.y / 1e3 # (Km)
+ z_vec = mss_d2d_manager.z / 1e3 # (Km)
+ # Store all positions
+
+ # Plot the globe with satellite positions
+ fig = plot_globe_with_borders()
+
+ # Set the camera position in Plotly
+ show_range = 1e4
+ fig.update_layout(
+ scene=dict(
+ zaxis=dict(
+ range=(-show_range / 2, show_range / 2)
+ ),
+ yaxis=dict(
+ range=(-show_range / 2, show_range / 2)
+ ),
+ xaxis=dict(
+ range=(-show_range / 2, show_range / 2)
+ ),
+ camera=dict(
+ center=dict(x=0, y=0, z=center_of_earth.z[0] / show_range / 1e3),
+ )
+ )
+ )
+
+ # # Satellite as fp center
+ # center_fp_at_sat = 0
+ # # get original sat xyz
+ # orx, ory, orz = geoconv.revert_transformed_cartesian_to_cartesian(
+ # station_1.x[center_fp_at_sat],
+ # station_1.y[center_fp_at_sat],
+ # station_1.z[center_fp_at_sat],
+ # )
+ # sat_lat, sat_long, sat_alt = ecef2lla(orx, ory, orz)
+
+ # lat_vals = np.linspace(sat_lat - 10.0, sat_lat + 10.0, 200)
+ # lon_vals = np.linspace(sat_long - 10.0, sat_long + 10.0, 200)
+
+ # # Ground station as fp center
+ # lat_vals = np.linspace(geoconv.ref_lat - 10.0, geoconv.ref_lat + 10.0, 50)
+ # lon_vals = np.linspace(geoconv.ref_long - 10.0, geoconv.ref_long + 10.0, 50)
+
+ # Arbitrary range for fp calulation
+ lat_vals = np.linspace(-33.69111, 2.81972, 200)
+ lon_vals = np.linspace(-72.89583, -34.80861, 200)
+
+ # lon and lat will be 2D arrays.
+ lon, lat = np.meshgrid(lon_vals, lat_vals)
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(lat_flat, lon_flat, 0)
+
+ # creates a StationManager to calculate the gains on
+ surf_manager = StationManager(len(x_flat))
+ surf_manager.x = x_flat
+ surf_manager.y = y_flat
+ surf_manager.z = z_flat
+ surf_manager.height = z_flat
+
+ station_1 = mss_d2d_manager
+ mss_active = np.where(station_1.active)[0]
+ station_2 = surf_manager
+ station_2_active = np.where(station_2.active)[0]
+
+ print("Calculating gains (memory intensive)")
+ # Calculate vector and apointment off_axis
+ gains = np.zeros((len(mss_active), len(station_2_active)))
+ phi, theta = station_1.get_pointing_vector_to(station_2)
+ off_axis_angle = station_1.get_off_axis_angle(station_2)
+ phi, theta = station_1.get_pointing_vector_to(station_2)
+ for k in mss_active:
+ gains[k, station_2_active] = \
+ station_1.antenna[k].calculate_gain(
+ off_axis_angle_vec=off_axis_angle[k, station_2_active],
+ theta_vec=theta[k, station_2_active],
+ phi_vec=phi[k, station_2_active],
+ )
+
+ if SUM_GAINS:
+ gains = 10 ** (gains / 10)
+ gains = 10 * np.log10(np.sum(gains, axis=0))
+ else:
+ gains = np.max(gains, axis=0)
+
+ world_surf_x = x_flat.reshape(lat.shape)
+ world_surf_y = y_flat.reshape(lat.shape)
+ world_surf_z = z_flat.reshape(lat.shape)
+ reshaped_gain = gains.reshape(lat.shape)
+ clor = 'rgb(220, 220, 220)'
+
+ mx_gain = np.max(reshaped_gain)
+ mn_gain = np.min(reshaped_gain)
+ rnge = mx_gain - mn_gain
+ n_steps = len(step) - 1
+ colorscale = []
+ bins = []
+ at = 0
+ offset = len(colors) - len(step)
+
+ for i in range(0, n_steps + 1):
+ ci = offset + i
+
+ bins.append(mx_gain - at * rnge)
+ at += step[i] / rnge
+
+ if DISCRETIZE and i / n_steps - 1 / n_steps / 2 > 0:
+ colorscale.append([i / n_steps - 1 / n_steps / 2, "rgb(100,100,100)"])
+ colorscale.append([i / n_steps - 1 / n_steps / 2 + 0.001, colors[ci]])
+ colorscale.append([i / n_steps, colors[ci]])
+ if DISCRETIZE and i / n_steps + 1 / n_steps / 2 < 1.0:
+ colorscale.append([i / n_steps + 1 / n_steps / 2 - 0.001, colors[ci]])
+ # bins.append(mn_gain)
+ bins.reverse()
+
+ if DISCRETIZE:
+ # cumul_steps = np.cumsum(steps)
+ surfacecolor = np.digitize(reshaped_gain, bins, right=True)
+ colorbar = dict(
+ tickmode='array',
+ tickvals=np.arange(0, len(bins)),
+ ticktext=[f"< {bins[0]:.2f} dB"] + [f"{bins[i]:.2f} to {bins[i + 1]:.2f} dB" for i in range(len(bins) - 1)],
+ title="Gain (dB)"
+ )
+ else:
+ surfacecolor = reshaped_gain
+ colorbar = None
+
+ fig.add_surface(
+ x=world_surf_x / 1e3, y=world_surf_y / 1e3, z=world_surf_z / 1e3,
+ surfacecolor=surfacecolor,
+ # Uniform color scale for a solid color.
+ colorscale=colorscale,
+ opacity=1.0,
+ showlegend=False,
+ # lighting=dict(diffuse=0)
+ colorbar=colorbar,
+ )
+
+ # Plot all satellites (red markers)
+ print("adding sats")
+ fig.add_trace(go.Scatter3d(
+ x=mss_d2d_manager.x / 1e3,
+ y=mss_d2d_manager.y / 1e3,
+ z=mss_d2d_manager.z / 1e3,
+ mode='markers',
+ marker=dict(size=2, color='red', opacity=0.5),
+ showlegend=False
+ ))
+
+ # Plot visible satellites (green markers)
+ # print(visible_positions['x'][visible_positions['x'] > 0])
+ fig.add_trace(go.Scatter3d(
+ x=mss_d2d_manager.x[mss_d2d_manager.active] / 1e3,
+ y=mss_d2d_manager.y[mss_d2d_manager.active] / 1e3,
+ z=mss_d2d_manager.z[mss_d2d_manager.active] / 1e3,
+ mode='markers',
+ marker=dict(size=3, color='green', opacity=0.8),
+ showlegend=False
+ ))
+
+ fig.add_trace(go.Scatter3d(
+ x=[ground_sta_pos[0] / 1e3],
+ y=[ground_sta_pos[1] / 1e3],
+ z=[ground_sta_pos[2] / 1e3],
+ mode='markers',
+ marker=dict(size=5, color='blue', opacity=1.0),
+ showlegend=False
+ ))
+
+ polygons_lim = plot_mult_polygon(
+ params.sat_is_active_if.lat_long_inside_country.filter_polygon
+ )
+ from functools import reduce
+
+ lim_x, lim_y, lim_z = reduce(
+ lambda acc, it: (list(it[0]) + [None] + acc[0], list(it[1]) + [None] + acc[1], list(it[2]) + [None] + acc[2]),
+ polygons_lim,
+ ([], [], [])
+ )
+
+ fig.add_trace(go.Scatter3d(
+ x=lim_x,
+ y=lim_y,
+ z=lim_z,
+ mode='lines',
+ line=dict(color='rgb(0, 0, 255)'),
+ showlegend=False
+ ))
+
+ # fig.add_trace(go.Scatter3d(
+ # x=center_of_earth.x / 1e3,
+ # y=center_of_earth.y / 1e3,
+ # z=center_of_earth.z / 1e3,
+ # mode='markers',
+ # marker=dict(size=5, color='black', opacity=1.0),
+ # showlegend=False
+ # ))
+
+ # Display the plot
+ print(len(fig.data))
+ fig.show()
diff --git a/sharc/satellite/scripts/plot_orbits_single_color_3d.py b/sharc/satellite/scripts/plot_orbits_single_color_3d.py
new file mode 100644
index 000000000..0fcaa780f
--- /dev/null
+++ b/sharc/satellite/scripts/plot_orbits_single_color_3d.py
@@ -0,0 +1,302 @@
+# Generates a 3D plot of the Earth with the satellites positions
+# https://geopandas.org/en/stable/docs/user_guide/io.html
+import os
+import geopandas as gpd
+import numpy as np
+import plotly.graph_objects as go
+
+from sharc.support.sharc_geom import GeometryConverter
+from sharc.station_manager import StationManager
+geoconv = GeometryConverter()
+
+sys_lat = -14.5
+sys_long = -45
+sys_alt = 1200
+
+geoconv.set_reference(
+ sys_lat, sys_long, sys_alt
+)
+
+from sharc.satellite.ngso.orbit_model import OrbitModel
+from sharc.satellite.utils.sat_utils import calc_elevation, lla2ecef
+from sharc.satellite.ngso.constants import EARTH_RADIUS_KM
+from sharc.parameters.parameters_mss_d2d import ParametersOrbit, ParametersMssD2d
+from sharc.station_factory import StationFactory
+
+
+def plot_back(fig):
+ """back half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ lat_vals = np.linspace(-90, 90, 50) # 50 latitude points from -90 to 90 degrees.
+ lon_vals = np.linspace(0, 180, 50) # 50 longitude points for the front half.
+ lon, lat = np.meshgrid(lon_vals, lat_vals) # lon and lat will be 2D arrays.
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=(x / 1e3), y=(y / 1e3), z=(z / 1e3),
+ colorscale=[[0, clor], [1, clor]], # Uniform color scale for a solid color.
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1)
+ )
+
+
+def plot_front(fig):
+ """front half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+
+ # Create a mesh grid for latitude and longitude.
+ # For the "front" half, we can use longitudes from -180 to 0 degrees.
+ lat_vals = np.linspace(-90, 90, 50) # 50 latitude points from -90 to 90 degrees.
+ lon_vals = np.linspace(-180, 0, 50) # 50 longitude points for the front half.
+ lon, lat = np.meshgrid(lon_vals, lat_vals) # lon and lat will be 2D arrays.
+
+ # Flatten the mesh to pass to the converter function.
+ lat_flat = lat.flatten()
+ lon_flat = lon.flatten()
+
+ # Convert the lat/lon grid to transformed Cartesian coordinates.
+ # Ensure your converter function can handle vectorized (numpy array) inputs.
+ x_flat, y_flat, z_flat = geoconv.convert_lla_to_transformed_cartesian(lat_flat, lon_flat, 0)
+
+ # Reshape the converted coordinates back to the 2D grid shape.
+ x = x_flat.reshape(lat.shape)
+ y = y_flat.reshape(lat.shape)
+ z = z_flat.reshape(lat.shape)
+
+ # Add the surface to the Plotly figure.
+ fig.add_surface(
+ x=(x / 1e3), y=(y / 1e3), z=(z / 1e3),
+ colorscale=[[0, clor], [1, clor]], # Uniform color scale for a solid color.
+ opacity=1.0,
+ showlegend=False,
+ lighting=dict(diffuse=0.1)
+ )
+
+
+def plot_polygon(poly):
+
+ xy_coords = poly.exterior.coords.xy
+ lon = np.array(xy_coords[0])
+ lat = np.array(xy_coords[1])
+
+ # lon = lon * np.pi / 180
+ # lat = lat * np.pi / 180
+
+ # R = EARTH_RADIUS_KM
+ x, y, z = geoconv.convert_lla_to_transformed_cartesian(lat, lon, 0)
+
+ return x, y, z
+
+
+def plot_globe_with_borders():
+ # Read the shapefile. Creates a DataFrame object
+ countries_borders_shp_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "../../data/countries/ne_110m_admin_0_countries.shp")
+ gdf = gpd.read_file(countries_borders_shp_file)
+ fig = go.Figure()
+ fig.update_layout(
+ scene=dict(
+ aspectmode="data",
+ xaxis=dict(showbackground=False),
+ yaxis=dict(showbackground=False),
+ zaxis=dict(showbackground=False)
+ ),
+ margin=dict(l=0, r=0, b=0, t=0)
+ )
+ # return fig
+ plot_front(fig)
+ plot_back(fig)
+ x_all, y_all, z_all = [], [], []
+
+ for i in gdf.index:
+ # print(gdf.loc[i].NAME) # Call a specific attribute
+
+ polys = gdf.loc[i].geometry # Polygons or MultiPolygons
+
+ if polys.geom_type == 'Polygon':
+ x, y, z = plot_polygon(polys)
+ x_all.extend(x / 1e3)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y / 1e3)
+ y_all.extend([None])
+ z_all.extend(z / 1e3)
+ z_all.extend([None])
+
+ elif polys.geom_type == 'MultiPolygon':
+
+ for poly in polys.geoms:
+ x, y, z = plot_polygon(poly)
+ x_all.extend(x / 1e3)
+ x_all.extend([None]) # None separates different polygons
+ y_all.extend(y / 1e3)
+ y_all.extend([None])
+ z_all.extend(z / 1e3)
+ z_all.extend([None])
+
+ fig.add_trace(go.Scatter3d(x=x_all, y=y_all, z=z_all, mode='lines',
+ line=dict(color='rgb(0, 0, 0)'), showlegend=False))
+
+ return fig
+
+
+if __name__ == "__main__":
+ # Define the orbit parameters for two satellite constellations
+ orbit_1 = ParametersOrbit(
+ n_planes=6, sats_per_plane=8, phasing_deg=7.5, long_asc_deg=0,
+ inclination_deg=52, perigee_alt_km=1414, apogee_alt_km=1414
+ )
+
+ orbit_2 = ParametersOrbit(
+ n_planes=4, sats_per_plane=10, phasing_deg=5.0, long_asc_deg=90,
+ inclination_deg=60, perigee_alt_km=1200, apogee_alt_km=1200
+ )
+
+ # Configure the MSS D2D system parameters
+ params = ParametersMssD2d(
+ name="Example-MSS-D2D",
+ antenna_pattern="ITU-R-S.1528-Taylor",
+ orbits=[orbit_1, orbit_2],
+ frequency=2100,
+ bandwidth=5
+ )
+
+ params.antenna_s1528.antenna_gain = 30.0
+
+ params.sat_is_active_if.conditions = [
+ "LAT_LONG_INSIDE_COUNTRY",
+ "MINIMUM_ELEVATION_FROM_ES",
+ ]
+ params.sat_is_active_if.minimum_elevation_from_es = 5
+ # params.sat_is_active_if.lat_long_inside_country.country_name = "Colombia"
+ params.sat_is_active_if.lat_long_inside_country.margin_from_border = 0
+ # params.sat_is_active_if.lat_long_inside_country.margin_from_border = 0
+ params.sat_is_active_if.lat_long_inside_country.country_names = ["Brazil"]
+
+ params.propagate_parameters()
+
+ # Create a topology with a single base station
+ from sharc.topology.topology_single_base_station import TopologySingleBaseStation
+ imt_topology = TopologySingleBaseStation(
+ cell_radius=500, num_clusters=1
+ )
+
+ # Create a random number generator
+ rng = np.random.RandomState(seed=42)
+
+ # Number of iterations (drops)
+ NUM_DROPS = 100
+
+ # Lists to store satellite positions (all and visible)
+ all_positions = {'x': [], 'y': [], 'z': []}
+ visible_positions = {'x': [], 'y': [], 'z': []}
+
+ # Plot the ground station (blue marker)
+ # ground_sta_pos = lla2ecef(sys_lat, sys_long, sys_alt)
+ ground_sta_pos = geoconv.convert_lla_to_transformed_cartesian(sys_lat, sys_long, 1200.0)
+
+ center_of_earth = StationManager(1)
+ # rotated and then translated center of earth
+ center_of_earth.x = np.array([0.0])
+ center_of_earth.y = np.array([0.0])
+ center_of_earth.z = np.array([-geoconv.get_translation()])
+
+ vis_elevation = []
+ for _ in range(NUM_DROPS):
+ # Generate satellite positions using the StationFactory
+ mss_d2d_manager = StationFactory.generate_mss_d2d(params, rng, geoconv)
+
+ # Extract satellite positions
+ x_vec = mss_d2d_manager.x / 1e3 # (Km)
+ y_vec = mss_d2d_manager.y / 1e3 # (Km)
+ z_vec = mss_d2d_manager.z / 1e3 # (Km)
+ # Store all positions
+ all_positions['x'].extend(x_vec)
+ all_positions['y'].extend(y_vec)
+ all_positions['z'].extend(z_vec)
+
+ # Identify visible satellites
+ vis_sat_idxs = np.where(mss_d2d_manager.active)[0]
+
+ # should be pointing at nadir
+ off_axis = mss_d2d_manager.get_off_axis_angle(center_of_earth)
+ if len(np.where(off_axis > 0.01)[0]):
+ print("AOPA, off axis parece estar errado")
+ print("onde?: ", np.where(off_axis > 0.01))
+
+ visible_positions['x'].extend(x_vec[vis_sat_idxs])
+ visible_positions['y'].extend(y_vec[vis_sat_idxs])
+ visible_positions['z'].extend(z_vec[vis_sat_idxs])
+ vis_elevation.extend(mss_d2d_manager.elevation[vis_sat_idxs])
+
+ # Flatten arrays
+ all_positions['x'] = np.concatenate([all_positions['x']])
+ all_positions['y'] = np.concatenate([all_positions['y']])
+ all_positions['z'] = np.concatenate([all_positions['z']])
+
+ visible_positions['x'] = np.concatenate([visible_positions['x']])
+ visible_positions['y'] = np.concatenate([visible_positions['y']])
+ visible_positions['z'] = np.concatenate([visible_positions['z']])
+
+ # Plot the globe with satellite positions
+ fig = plot_globe_with_borders()
+
+ # Plot all satellites (red markers)
+ print("adding sats")
+ fig.add_trace(go.Scatter3d(
+ x=all_positions['x'],
+ y=all_positions['y'],
+ z=all_positions['z'],
+ mode='markers',
+ marker=dict(size=2, color='red', opacity=0.5),
+ showlegend=False
+ ))
+
+ # Plot visible satellites (green markers)
+ # print(visible_positions['x'][visible_positions['x'] > 0])
+ # print("vis_elevation", vis_elevation)
+ fig.add_trace(go.Scatter3d(
+ x=visible_positions['x'],
+ y=visible_positions['y'],
+ z=visible_positions['z'],
+ mode='markers',
+ marker=dict(size=3, color='green', opacity=0.8),
+ showlegend=False
+ ))
+
+ fig.add_trace(go.Scatter3d(
+ x=[ground_sta_pos[0] / 1e3],
+ y=[ground_sta_pos[1] / 1e3],
+ z=[ground_sta_pos[2] / 1e3],
+ mode='markers',
+ marker=dict(size=5, color='blue', opacity=1.0),
+ showlegend=False
+ ))
+
+ # fig.add_trace(go.Scatter3d(
+ # x=center_of_earth.x / 1e3,
+ # y=center_of_earth.y / 1e3,
+ # z=center_of_earth.z / 1e3,
+ # mode='markers',
+ # marker=dict(size=5, color='black', opacity=1.0),
+ # showlegend=False
+ # ))
+
+ # Display the plot
+ print(len(fig.data))
+ fig.show()
diff --git a/sharc/satellite/scripts/plot_satellites_3d.py b/sharc/satellite/scripts/plot_satellites_3d.py
new file mode 100644
index 000000000..9399b9f2b
--- /dev/null
+++ b/sharc/satellite/scripts/plot_satellites_3d.py
@@ -0,0 +1,187 @@
+# Generates a 3D plot of the Earth with the satellites positions
+# https://geopandas.org/en/stable/docs/user_guide/io.html
+import os
+import geopandas as gpd
+import numpy as np
+import plotly.graph_objects as go
+
+from sharc.satellite.ngso.orbit_model import OrbitModel
+from sharc.satellite.utils.sat_utils import calc_elevation, lla2ecef
+from sharc.satellite.ngso.constants import EARTH_RADIUS_KM
+
+
+def plot_back(fig):
+ """back half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+ R = np.sqrt(EARTH_RADIUS_KM)
+ u_angle = np.linspace(0, np.pi, 25)
+ v_angle = np.linspace(0, np.pi, 25)
+ x_dir = np.outer(R * np.cos(u_angle), R * np.sin(v_angle))
+ y_dir = np.outer(R * np.sin(u_angle), R * np.sin(v_angle))
+ z_dir = np.outer(R * np.ones(u_angle.shape[0]), R * np.cos(v_angle))
+ fig.add_surface(z=z_dir, x=x_dir, y=y_dir, colorscale=[[0, clor], [1, clor]],
+ opacity=1.0, showlegend=False, lighting=dict(
+ # opacity=fig.sphere_alpha, colorscale=[[0, fig.sphere_color], [1, fig.sphere_color]])
+ diffuse=0.1))
+
+
+def plot_front(fig):
+ """front half of sphere"""
+ clor = 'rgb(220, 220, 220)'
+ R = np.sqrt(EARTH_RADIUS_KM)
+ u_angle = np.linspace(-np.pi, 0, 25)
+ v_angle = np.linspace(0, np.pi, 25)
+ x_dir = np.outer(R * np.cos(u_angle), R * np.sin(v_angle))
+ y_dir = np.outer(R * np.sin(u_angle), R * np.sin(v_angle))
+ z_dir = np.outer(R * np.ones(u_angle.shape[0]), R * np.cos(v_angle))
+ fig.add_surface(z=z_dir, x=x_dir, y=y_dir, colorscale=[[0, clor], [1, clor]], opacity=1.0, showlegend=False,
+ lighting=dict(
+ # opacity=fig.sphere_alpha, colorscale=[[0, fig.sphere_color], [1, fig.sphere_color]])
+ diffuse=0.1))
+
+
+def plot_polygon(poly):
+
+ xy_coords = poly.exterior.coords.xy
+ lon = np.array(xy_coords[0])
+ lat = np.array(xy_coords[1])
+
+ lon = lon * np.pi / 180
+ lat = lat * np.pi / 180
+
+ R = EARTH_RADIUS_KM
+ x = R * np.cos(lat) * np.cos(lon)
+ y = R * np.cos(lat) * np.sin(lon)
+ z = R * np.sin(lat)
+
+ return x, y, z
+
+
+def plot_globe_with_borders():
+ # Read the shapefile. Creates a DataFrame object
+ countries_borders_shp_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "../../data/countries/ne_110m_admin_0_countries.shp")
+ gdf = gpd.read_file(countries_borders_shp_file)
+ fig = go.Figure()
+ plot_front(fig)
+ plot_back(fig)
+
+ for i in gdf.index:
+ # print(gdf.loc[i].NAME) # Call a specific attribute
+
+ polys = gdf.loc[i].geometry # Polygons or MultiPolygons
+
+ if polys.geom_type == 'Polygon':
+ x, y, z = plot_polygon(polys)
+ fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode='lines',
+ line=dict(color='rgb(0, 0,0)'), showlegend=False))
+
+ elif polys.geom_type == 'MultiPolygon':
+
+ for poly in polys.geoms:
+ x, y, z = plot_polygon(poly)
+ fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode='lines',
+ line=dict(color='rgb(0, 0,0)'), showlegend=False))
+ return fig
+
+
+if __name__ == "__main__":
+
+ # Plot Global Star orbit using OrbitModel object
+ orbit = OrbitModel(
+ Nsp=6, # number of sats per plane
+ Np=8, # number of planes
+ phasing=7.5, # phasing in degrees
+ long_asc=0, # longitude of the ascending node in degrees
+ omega=0, # argument of perigee in degrees
+ delta=52, # inclination in degrees
+ hp=1414, # perigee altitude in km
+ ha=1414, # apogee altitude in km
+ Mo=0 # mean anomaly in degrees
+ )
+
+ # Set parameters for ground station
+ GROUND_STA_LAT = -15.7801
+ GROUND_STA_LON = -42.9292
+ MIN_ELEV_ANGLE_DEG = 5.0
+
+ # Plot satellite traces from a time interval
+ fig = plot_globe_with_borders()
+ pos_vec = orbit.get_satellite_positions_time_interval(initial_time_secs=0, interval_secs=5, n_periods=1)
+ fig.add_trace(go.Scatter3d(x=pos_vec['sx'].flatten(),
+ y=pos_vec['sy'].flatten(),
+ z=pos_vec['sz'].flatten(),
+ mode='lines',
+ showlegend=False))
+
+ fig.update_layout(
+ title_text='Satellite Traces',
+ scene=dict(
+ xaxis_title='X [km]',
+ yaxis_title='Y [km]',
+ zaxis_title='Z [km]',
+ aspectmode='data'
+ ),
+ margin=dict(l=0, r=0, b=0, t=0)
+ )
+
+ fig.show()
+
+ # Plot satellites positions taken randomly
+ fig = plot_globe_with_borders()
+ NUM_DROPS = 100
+ rng = np.random.RandomState(seed=6)
+ pos_vec = orbit.get_orbit_positions_random(rng=rng, n_samples=NUM_DROPS)
+ fig.add_trace(go.Scatter3d(x=pos_vec['sx'].flatten(),
+ y=pos_vec['sy'].flatten(),
+ z=pos_vec['sz'].flatten(),
+ mode='markers',
+ marker=dict(size=2,
+ color='red',
+ opacity=0.8),
+ showlegend=False))
+ fig.show()
+
+ # Show visible satellites from ground-station
+ fig = plot_globe_with_borders()
+ NUM_DROPS = 1000
+ rng = np.random.RandomState(seed=6)
+ pos_vec = orbit.get_orbit_positions_random(rng=rng, n_samples=NUM_DROPS)
+ look_angles = calc_elevation(GROUND_STA_LAT, pos_vec['lat'], GROUND_STA_LON, pos_vec['lon'], orbit.apogee_alt_km)
+ elevation_angles_per_drop = look_angles[np.where(np.array(look_angles) > 0)]
+ num_of_visible_sats_per_drop = np.sum(look_angles > MIN_ELEV_ANGLE_DEG, axis=0)
+
+ # plot all satellites in drops
+ fig.add_trace(go.Scatter3d(x=pos_vec['sx'].flatten(),
+ y=pos_vec['sy'].flatten(),
+ z=pos_vec['sz'].flatten(),
+ mode='markers',
+ marker=dict(size=2,
+ color='red',
+ opacity=0.8),
+ showlegend=False))
+
+ # plot visible satellites
+ fig.add_trace(go.Scatter3d(
+ x=pos_vec['sx'][np.where(look_angles > MIN_ELEV_ANGLE_DEG)].flatten(),
+ y=pos_vec['sy'][np.where(look_angles > MIN_ELEV_ANGLE_DEG)].flatten(),
+ z=pos_vec['sz'][np.where(look_angles > MIN_ELEV_ANGLE_DEG)].flatten(),
+ mode='markers',
+ marker=dict(size=2,
+ color='green',
+ opacity=0.8),
+ showlegend=False))
+
+ # plot ground station
+ groud_sta_pos = lla2ecef(GROUND_STA_LAT, GROUND_STA_LON, 0.0)
+ fig.add_trace(go.Scatter3d(
+ x=np.array(groud_sta_pos[0] / 1e3),
+ y=np.array(groud_sta_pos[1] / 1e3),
+ z=np.array(groud_sta_pos[2] / 1e3),
+ mode='markers',
+ marker=dict(size=4,
+ color='blue',
+ opacity=1.0),
+ showlegend=False))
+
+ fig.show()
diff --git a/sharc/satellite/utils/sat_utils.py b/sharc/satellite/utils/sat_utils.py
new file mode 100644
index 000000000..6229d45b9
--- /dev/null
+++ b/sharc/satellite/utils/sat_utils.py
@@ -0,0 +1,123 @@
+import numpy as np
+from sharc.parameters.constants import EARTH_RADIUS
+
+EARTH_RADIUS_KM = EARTH_RADIUS / 1000
+
+
+class WGS84Defs:
+ """Constants for the WGS84 ellipsoid model."""
+ SEMI_MAJOR_AXIS = 6378137.0 # Semi-major axis (in meters)
+ SEMI_MINOR_AXIS = 6356752.3 # Semi-major axis (in meters)
+ ECCENTRICITY = 8.1819190842622e-2 # WGS84 ellipsoid eccentricity
+ FLATTENING = 0.0033528106647474805
+ FIRST_ECCENTRICITY_SQRD = 6.69437999014e-3
+
+
+def ecef2lla(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple:
+ """Coverts ECEF cartesian coordinates to lat long in WSG84 CRS.
+
+ Parameters
+ ----------
+ x : np.ndarray
+ x coordintate in meters
+ y : np.ndarray
+ y coordintate in meters
+ z : np.ndarray
+ x coordintate in meters
+
+ Returns
+ -------
+ tuple (lat, long, alt)
+ lat long and altitude in WSG84 format
+ """
+ # Longitude calculation
+ lon = np.arctan2(y, x)
+
+ # Iteratively solve for latitude and altitude
+ p = np.sqrt(np.power(x, 2) + np.power(y, 2))
+ lat = np.arctan2(z, p * (1 - WGS84Defs.ECCENTRICITY**2)) # Initial estimate for latitude
+ for _ in range(5): # Iteratively improve the estimate
+ N = WGS84Defs.SEMI_MAJOR_AXIS / np.sqrt(1 - WGS84Defs.ECCENTRICITY**2 * np.sin(lat)**2)
+ alt = p / np.cos(lat) - N
+ lat = np.arctan2(z, p * (1 - WGS84Defs.ECCENTRICITY**2 * (N / (N + alt))))
+
+ # Convert latitude and longitude from radians to degrees
+ lat = np.degrees(lat)
+ lon = np.degrees(lon)
+ return lat, lon, alt
+
+
+def lla2ecef(lat: np.ndarray, lng: np.ndarray, alt: np.ndarray) -> tuple:
+ """Converts from geodetic WSG84 to ECEF coordinates
+
+ Parameters
+ ----------
+ lat : np.ndarray
+ latitude in degrees
+ lng : np.ndarray
+ longitute in degrees
+ alt : np.ndarray
+ altitude in meters
+
+ Returns
+ -------
+ tuple
+ x, y and z coordinates
+ """
+ lat = np.deg2rad(lat)
+ lng = np.deg2rad(lng)
+ n_phi = WGS84Defs.SEMI_MAJOR_AXIS / np.sqrt(1 - WGS84Defs.FIRST_ECCENTRICITY_SQRD * np.sin(lat)**2)
+ x = (n_phi + alt) * np.cos(lat) * np.cos(lng)
+ y = (n_phi + alt) * np.cos(lat) * np.sin(lng)
+ z = ((1 - WGS84Defs.FLATTENING)**2 * n_phi + alt) * np.sin(lat)
+
+ return x, y, z
+
+
+def calc_elevation(Le: np.ndarray,
+ Ls: np.ndarray,
+ le: np.ndarray,
+ ls: np.ndarray,
+ sat_height: np.ndarray) -> np.ndarray:
+ """Calculates the elevation angle from the earth station
+ to space station, given earth and space station coordinates.
+ Negative elevation angles means the space stations is not visible from Earth station.
+
+ Parameters
+ ----------
+ Le : (ndarray)
+ latitudes of the earth station
+ Ls : (ndarray)
+ latitudes of the space station
+ le : (ndarray)
+ longitudes of the earth station
+ ls : (ndarray)
+ latitudes of the space station
+ sat_height : (ndarray)
+ space station altitudes
+
+ Returns
+ -------
+ (ndarray)
+ array of elevation angles from the earth station in degrees.
+ """
+ Le = np.radians(Le)
+ Ls = np.radians(Ls)
+ le = np.radians(le)
+ ls = np.radians(ls)
+ gamma = np.arccos(
+ np.cos(Le) * np.cos(Ls) * np.cos(ls - le) + np.sin(Le) * np.sin(Ls)
+ )
+ rs = EARTH_RADIUS_KM + sat_height
+ slant = np.sqrt(rs**2 + EARTH_RADIUS_KM**2 - 2 * rs * EARTH_RADIUS_KM * np.cos(gamma))
+ elev_angle = np.arccos((slant**2 + EARTH_RADIUS_KM**2 - rs**2) / \
+ (2 * slant * EARTH_RADIUS_KM)) - np.pi / 2
+
+ return np.degrees(elev_angle)
+
+
+if __name__ == "__main__":
+ r1 = ecef2lla(7792.1450, 0, 0)
+ print(r1)
+ r2 = lla2ecef(r1[0], r1[1], r1[2])
+ print(r2)
diff --git a/sharc/sharc_definitions.py b/sharc/sharc_definitions.py
new file mode 100644
index 000000000..abad74984
--- /dev/null
+++ b/sharc/sharc_definitions.py
@@ -0,0 +1,8 @@
+"""Commom SHARC definitions"""
+SHARC_IMPLEMENTED_SYSTEMS = [
+ "HAPS",
+ "MSS_SS",
+ "SINGLE_EARTH_STATION",
+ "MSS_D2D",
+ "SINGLE_SPACE_STATION",
+]
diff --git a/sharc/simulation.py b/sharc/simulation.py
index 69ec221e3..e06c35fd3 100644
--- a/sharc/simulation.py
+++ b/sharc/simulation.py
@@ -12,13 +12,12 @@
import math
import sys
import matplotlib.pyplot as plt
-from matplotlib.patches import Wedge
from sharc.support.enumerations import StationType
from sharc.topology.topology_factory import TopologyFactory
+from sharc.support.sharc_geom import GeometryConverter
from sharc.parameters.parameters import Parameters
-from sharc.propagation.propagation import Propagation
-from sharc.station_manager import StationManager
+from sharc.station_manager import StationManager, copy_active_stations
from sharc.results import Results
from sharc.propagation.propagation_factory import PropagationFactory
@@ -32,8 +31,14 @@ def __init__(self, parameters: Parameters, parameter_file: str):
self.parameters = parameters
self.parameters_filename = parameter_file
- if self.parameters.general.system == "EESS_PASSIVE":
- self.param_system = self.parameters.eess_passive
+ if self.parameters.general.system == "METSAT_SS":
+ self.param_system = self.parameters.metsat_ss
+ elif self.parameters.general.system == "EESS_SS":
+ self.param_system = self.parameters.eess_ss
+ elif self.parameters.general.system == "SINGLE_EARTH_STATION":
+ self.param_system = self.parameters.single_earth_station
+ elif self.parameters.general.system == "SINGLE_SPACE_STATION":
+ self.param_system = self.parameters.single_space_station
elif self.parameters.general.system == "FSS_SS":
self.param_system = self.parameters.fss_ss
elif self.parameters.general.system == "FSS_ES":
@@ -46,19 +51,43 @@ def __init__(self, parameters: Parameters, parameter_file: str):
self.param_system = self.parameters.rns
elif self.parameters.general.system == "RAS":
self.param_system = self.parameters.ras
+ elif self.parameters.general.system == "MSS_SS":
+ self.param_system = self.parameters.mss_ss
+ elif self.parameters.general.system == "MSS_D2D":
+ self.param_system = self.parameters.mss_d2d
else:
- sys.stderr.write("ERROR\nInvalid system: " + self.parameters.general.system)
+ sys.stderr.write(
+ "ERROR\nInvalid system: " +
+ self.parameters.general.system,
+ )
sys.exit(1)
-
- self.wrap_around_enabled = self.parameters.imt.wrap_around and \
- (self.parameters.imt.topology == 'MACROCELL' \
- or self.parameters.imt.topology == 'HOTSPOT') and \
- self.parameters.imt.num_clusters == 1
+
+ self.wrap_around_enabled = False
+ if self.parameters.imt.topology.type == "MACROCELL":
+ self.wrap_around_enabled = self.parameters.imt.topology.macrocell.wrap_around \
+ and self.parameters.imt.topology.macrocell.num_clusters == 1
+ if self.parameters.imt.topology.type == "HOTSPOT":
+ self.wrap_around_enabled = self.parameters.imt.topology.hotspot.wrap_around \
+ and self.parameters.imt.topology.hotspot.num_clusters == 1
self.co_channel = self.parameters.general.enable_cochannel
self.adjacent_channel = self.parameters.general.enable_adjacent_channel
- self.topology = TopologyFactory.createTopology(self.parameters)
+ geometry_converter = GeometryConverter()
+
+ if self.parameters.imt.topology.central_latitude is not None:
+ geometry_converter.set_reference(
+ self.parameters.imt.topology.central_latitude,
+ self.parameters.imt.topology.central_longitude,
+ self.parameters.imt.topology.central_altitude,
+ )
+
+ self.geometry_converter = geometry_converter
+
+ self.topology = TopologyFactory.createTopology(
+ self.parameters,
+ geometry_converter
+ )
self.bs_power_gain = 0
self.ue_power_gain = 0
@@ -105,65 +134,103 @@ def __init__(self, parameters: Parameters, parameter_file: str):
if self.overlapping_bandwidth < 0:
self.overlapping_bandwidth = 0
- if (self.overlapping_bandwidth == self.param_system.bandwidth and
- not self.parameters.imt.interfered_with) or \
- (self.overlapping_bandwidth == self.parameters.imt.bandwidth and
- self.parameters.imt.interfered_with):
+ if (self.overlapping_bandwidth == self.param_system.bandwidth and not self.parameters.imt.interfered_with) or \
+ (self.overlapping_bandwidth == self.parameters.imt.bandwidth and self.parameters.imt.interfered_with):
self.adjacent_channel = False
+ if not self.co_channel and not self.adjacent_channel:
+ raise ValueError("Both co_channel and adjacent_channel can't be false")
+
random_number_gen = np.random.RandomState(self.parameters.general.seed)
- self.propagation_imt = PropagationFactory.create_propagation(self.parameters.imt.channel_model, self.parameters,
- random_number_gen)
- self.propagation_system = PropagationFactory.create_propagation(self.param_system.channel_model, self.parameters,
- random_number_gen)
+ self.propagation_imt = PropagationFactory.create_propagation(
+ self.parameters.imt.channel_model,
+ self.parameters,
+ self.parameters.imt,
+ random_number_gen,
+ )
+ self.propagation_system = PropagationFactory.create_propagation(
+ self.param_system.channel_model,
+ self.parameters,
+ self.param_system,
+ random_number_gen,
+ )
def add_observer_list(self, observers: list):
for o in observers:
self.add_observer(o)
- def initialize(self, *args, **kwargs):
+ def initialize_topology_dependant_variables(self):
"""
- This method is executed only once to initialize the simulation variables.
+ This method 'could' be called on every snapshot to re-initialize variables.
+ However, it'll probably be used to re-initialize variables only on demand,
+ when needed.
"""
-
- self.topology.calculate_coordinates()
num_bs = self.topology.num_base_stations
- num_ue = num_bs*self.parameters.imt.ue_k*self.parameters.imt.ue_k_m
+ num_ue = num_bs * self.parameters.imt.ue.k * self.parameters.imt.ue.k_m
+
+ # TODO: remove this from here and put it inside the parameter eval
+ # or antenna itself
+ if self.parameters.imt.bs.antenna.pattern == "ARRAY":
+ self.bs_power_gain = 10 * math.log10(
+ self.parameters.imt.bs.antenna.array.n_rows *
+ self.parameters.imt.bs.antenna.array.n_columns,
+ )
+ self.ue_power_gain = 10 * math.log10(
+ self.parameters.imt.ue.antenna.array.n_rows *
+ self.parameters.imt.ue.antenna.array.n_columns,
+ )
+ else:
+ self.bs_power_gain = 0
+ self.ue_power_gain = 0
- self.bs_power_gain = 10*math.log10(self.parameters.antenna_imt.bs_n_rows*
- self.parameters.antenna_imt.bs_n_columns)
- self.ue_power_gain = 10*math.log10(self.parameters.antenna_imt.ue_n_rows*
- self.parameters.antenna_imt.ue_n_columns)
self.imt_bs_antenna_gain = list()
self.imt_ue_antenna_gain = list()
self.path_loss_imt = np.empty([num_bs, num_ue])
self.coupling_loss_imt = np.empty([num_bs, num_ue])
self.coupling_loss_imt_system = np.empty(num_ue)
-
self.bs_to_ue_phi = np.empty([num_bs, num_ue])
self.bs_to_ue_theta = np.empty([num_bs, num_ue])
- self.bs_to_ue_beam_rbs = -1.0*np.ones(num_ue, dtype=int)
+ self.bs_to_ue_beam_rbs = -1.0 * np.ones(num_ue, dtype=int)
self.ue = np.empty(num_ue)
self.bs = np.empty(num_bs)
- self.system = np.empty(1)
# this attribute indicates the list of UE's that are connected to each
# base station. The position the the list indicates the resource block
# group that is allocated to the given UE
- self.link = dict([(bs,list()) for bs in range(num_bs)])
+ self.link = dict([(bs, list()) for bs in range(num_bs)])
+
+ def initialize(self, *args, **kwargs):
+ """
+ This method is executed only once to initialize the simulation variables.
+ """
+
+ self.topology.calculate_coordinates()
+
+ self.initialize_topology_dependant_variables()
+
+ self.system = np.empty(1)
# calculates the number of RB per BS
- self.num_rb_per_bs = math.trunc((1-self.parameters.imt.guard_band_ratio)* \
- self.parameters.imt.bandwidth /self.parameters.imt.rb_bandwidth)
+ self.num_rb_per_bs = math.trunc(
+ (1 - self.parameters.imt.guard_band_ratio) *
+ self.parameters.imt.bandwidth / self.parameters.imt.rb_bandwidth,
+ )
# calculates the number of RB per UE on a given BS
- self.num_rb_per_ue = math.trunc(self.num_rb_per_bs/self.parameters.imt.ue_k)
-
- self.results = Results(self.parameters_filename, self.parameters.general.overwrite_output)
-
- if self.parameters.general.system == 'RAS':
- self.polarization_loss = 0.0
+ self.num_rb_per_ue = math.trunc(
+ self.num_rb_per_bs / self.parameters.imt.ue.k,
+ )
+
+ self.results = Results().prepare_to_write(
+ self.parameters_filename,
+ self.parameters.general.overwrite_output,
+ self.parameters.general.output_dir,
+ self.parameters.general.output_dir_prefix,
+ )
+
+ if hasattr(self.param_system, "polarization_loss"):
+ self.polarization_loss = self.param_system.polarization_loss
else:
self.polarization_loss = 3.0
@@ -174,130 +241,176 @@ def finalize(self, *args, **kwargs):
snapshot_number = kwargs["snapshot_number"]
self.results.write_files(snapshot_number)
- def calculate_coupling_loss(self,
- station_a: StationManager,
- station_b: StationManager,
- propagation: Propagation,
- c_channel = True) -> np.array:
+ def calculate_coupling_loss_system_imt(
+ self,
+ system_station: StationManager,
+ imt_station: StationManager,
+ is_co_channel=True,
+ ) -> np.array:
"""
- Calculates the path coupling loss from each station_a to all station_b.
- Result is returned as a numpy array with dimensions num_a x num_b
- TODO: calculate coupling loss between activa stations only
+ Calculates the coupling loss (path loss + antenna gains + other losses) between
+ a system station and an IMT station.
+
+ Returns an numpy array with system_station.size X imt_station.size with coupling loss
+ values.
+
+ Parameters
+ ----------
+ system_station : StationManager
+ A StationManager object with system stations
+ imt_station : StationManager
+ A StationManager object with IMT stations
+ is_co_channel : bool, optional
+ Whether the interference analysis is co-channel or not, by default True
+
+ Returns
+ -------
+ np.array
+ Returns an numpy array with system_station.size X imt_station.size with coupling loss
+ values.
"""
- if station_a.station_type is StationType.EESS_PASSIVE or \
- station_a.station_type is StationType.FSS_SS or \
- station_a.station_type is StationType.HAPS or \
- station_a.station_type is StationType.RNS:
- elevation_angles = station_b.get_elevation_angle(station_a, self.param_system)
- elif station_a.station_type is StationType.IMT_BS and \
- station_b.station_type is StationType.IMT_UE and \
- self.parameters.imt.topology == "INDOOR":
- elevation_angles = np.transpose(station_b.get_elevation(station_a))
- elif station_a.station_type is StationType.FSS_ES or \
- station_a.station_type is StationType.RAS:
- elevation_angles = station_b.get_elevation(station_a)
- else:
- elevation_angles = None
-
- if station_a.station_type is StationType.EESS_PASSIVE or \
- station_a.station_type is StationType.FSS_SS or \
- station_a.station_type is StationType.FSS_ES or \
- station_a.station_type is StationType.HAPS or \
- station_a.station_type is StationType.FS or \
- station_a.station_type is StationType.RNS or \
- station_a.station_type is StationType.RAS:
- # Calculate distance from transmitters to receivers. The result is a
- # num_station_a x num_station_b
- d_2D = station_a.get_distance_to(station_b)
- d_3D = station_a.get_3d_distance_to(station_b)
-
- if self.parameters.imt.interfered_with:
- freq = self.param_system.frequency
- else:
- freq = self.parameters.imt.frequency
-
- if station_b.station_type is StationType.IMT_UE:
- # define antenna gains
- gain_a = self.calculate_gains(station_a, station_b)
- gain_b = np.transpose(self.calculate_gains(station_b, station_a, c_channel))
- sectors_in_node = 1
- additional_loss = self.parameters.imt.ue_ohmic_loss \
- + self.parameters.imt.ue_body_loss \
- + self.polarization_loss
- else:
- # define antenna gains
- gain_a = np.repeat(self.calculate_gains(station_a, station_b), self.parameters.imt.ue_k, 1)
- gain_b = np.transpose(self.calculate_gains(station_b, station_a, c_channel))
- sectors_in_node = self.parameters.imt.ue_k
- additional_loss = self.parameters.imt.bs_ohmic_loss \
- + self.polarization_loss
-
- if self.parameters.imt.interfered_with:
- earth_to_space = False
- single_entry = True
- else:
- earth_to_space = True
- single_entry = False
-
- if station_a.station_type is StationType.EESS_PASSIVE or \
- station_a.station_type is StationType.FSS_SS or \
- station_a.station_type is StationType.HAPS or \
- station_a.station_type is StationType.RNS:
- path_loss = propagation.get_loss(distance_3D=d_3D,
- frequency=freq*np.ones(d_3D.shape),
- indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)),
- elevation=elevation_angles, sat_params = self.param_system,
- earth_to_space = earth_to_space, earth_station_antenna_gain=gain_b,
- single_entry=single_entry, number_of_sectors=sectors_in_node)
- else:
- path_loss = propagation.get_loss(distance_3D=d_3D,
- frequency=freq*np.ones(d_3D.shape),
- indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)),
- elevation=elevation_angles, es_params=self.param_system,
- tx_gain = gain_a, rx_gain = gain_b, number_of_sectors=sectors_in_node,
- imt_sta_type=station_b.station_type,
- imt_x=station_b.x,
- imt_y=station_b.y,
- imt_z=station_b.height,
- es_x=station_a.x,
- es_y=station_a.y,
- es_z=station_a.height)
- if self.param_system.channel_model == "HDFSS":
- self.imt_system_build_entry_loss = path_loss[1]
- self.imt_system_diffraction_loss = path_loss[2]
- path_loss = path_loss[0]
-
- self.system_imt_antenna_gain = gain_a
- self.imt_system_antenna_gain = gain_b
- self.imt_system_path_loss = path_loss
- # IMT <-> IMT
+ # Set the frequency and other parameters for the propagation model
+ if self.parameters.imt.interfered_with:
+ freq = self.param_system.frequency
else:
- d_2D = self.bs_to_ue_d_2D
- d_3D = self.bs_to_ue_d_3D
freq = self.parameters.imt.frequency
-
- path_loss = propagation.get_loss(distance_3D=d_3D,
- distance_2D=d_2D,
- frequency=self.parameters.imt.frequency*np.ones(d_2D.shape),
- indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)),
- bs_height=station_a.height,
- ue_height=station_b.height,
- elevation=elevation_angles,
- shadowing=self.parameters.imt.shadowing)
+
+ # Calculate the antenna gains of the IMT station with respect to the system's station
+ if imt_station.station_type is StationType.IMT_UE:
+ # define antenna gains
+ gain_sys_to_imt = self.calculate_gains(system_station, imt_station)
+ gain_imt_to_sys = np.transpose(
+ self.calculate_gains(imt_station, system_station, is_co_channel))
+ additional_loss = self.parameters.imt.ue.ohmic_loss \
+ + self.parameters.imt.ue.body_loss \
+ + self.polarization_loss
+ elif imt_station.station_type is StationType.IMT_BS:
# define antenna gains
- gain_a = self.calculate_gains(station_a, station_b)
- gain_b = np.transpose(self.calculate_gains(station_b, station_a))
+ # repeat for each BS beam
+ gain_sys_to_imt = np.repeat(
+ self.calculate_gains(system_station, imt_station),
+ self.parameters.imt.ue.k, 1,
+ )
+ gain_imt_to_sys = np.transpose(
+ self.calculate_gains(
+ imt_station, system_station, is_co_channel,
+ ),
+ )
+ additional_loss = self.parameters.imt.bs.ohmic_loss \
+ + self.polarization_loss
+ else:
+ # should never reach this line
+ return ValueError(f"Invalid IMT StationType! {imt_station.station_type}")
+
+ # TODO: this performance betterment doesn't work when one of the stations is IMT_BS
+ # so do something that works for it
+ # # Calculate the path loss based on the propagation model only for active stations
+ # actv_sys = copy_active_stations(system_station)
+ # actv_imt = copy_active_stations(imt_station)
+ # path_loss = np.zeros((system_station.num_stations, imt_station.num_stations))
+ # actv_path_loss = self.propagation_system.get_loss(
+ # self.parameters,
+ # freq,
+ # actv_sys,
+ # actv_imt,
+ # gain_sys_to_imt,
+ # gain_imt_to_sys,
+ # )
+ # path_loss[np.ix_(system_station.active, imt_station.active)] = actv_path_loss
+
+ path_loss = self.propagation_system.get_loss(
+ self.parameters,
+ freq,
+ system_station,
+ imt_station,
+ gain_sys_to_imt,
+ gain_imt_to_sys,
+ )
+
+ # Store antenna gains and path loss samples
+ if self.param_system.channel_model == "HDFSS":
+ self.imt_system_build_entry_loss = path_loss[1]
+ self.imt_system_diffraction_loss = path_loss[2]
+ path_loss = path_loss[0]
+
+ if imt_station.station_type is StationType.IMT_UE:
+ self.imt_system_path_loss = path_loss
+ else:
+ # Repeat for each BS beam
+ self.imt_system_path_loss = np.repeat(
+ path_loss, self.parameters.imt.ue.k, 1,
+ )
+
+ self.system_imt_antenna_gain = gain_sys_to_imt
+ self.imt_system_antenna_gain = gain_imt_to_sys
+
+ # calculate coupling loss
+ coupling_loss = \
+ self.imt_system_path_loss - self.system_imt_antenna_gain - self.imt_system_antenna_gain + additional_loss
+
+ # Simulator expects imt_stations x system_stations shape
+ return np.transpose(coupling_loss)
+
+ def calculate_intra_imt_coupling_loss(
+ self,
+ imt_ue_station: StationManager,
+ imt_bs_station: StationManager,
+ ) -> np.array:
+ """
+ Calculates the coupling loss (path loss + antenna gains + other losses) between
+ a IMT stations (UE and BS).
+
+ Returns an numpy array with imt_bs_station.size X imt_ue_station.size with coupling loss
+ values.
+
+ Parameters
+ ----------
+ system_station : StationManager
+ A StationManager object representins IMT_UE stations
+ imt_station : StationManager
+ A StationManager object representins IMT_BS stations
+ is_co_channel : bool, optional
+ Whether the interference analysis is co-channel or not, by default True.
+ This parameter is ignored. It's keeped to maintein method interface.
- # collect IMT BS and UE antenna gain samples
- self.path_loss_imt = path_loss
- self.imt_bs_antenna_gain = gain_a
- self.imt_ue_antenna_gain = gain_b
- additional_loss = self.parameters.imt.bs_ohmic_loss \
- + self.parameters.imt.ue_ohmic_loss \
- + self.parameters.imt.ue_body_loss
+ Returns
+ -------
+ np.array
+ Returns an numpy array with imt_bs_station.size X imt_ue_station.size with coupling loss
+ values.
+ """
+ # Calculate the antenna gains
+
+ ant_gain_bs_to_ue = self.calculate_gains(
+ imt_bs_station, imt_ue_station,
+ )
+ ant_gain_ue_to_bs = self.calculate_gains(
+ imt_ue_station, imt_bs_station,
+ )
+
+ # Calculate the path loss between IMT stations. Primarly used for UL power control.
+
+ # Note on the array dimentions for coupling loss calculations:
+ # The function get_loss returns an array station_a x station_b
+ path_loss = self.propagation_imt.get_loss(
+ self.parameters,
+ self.parameters.imt.frequency,
+ imt_ue_station,
+ imt_bs_station,
+ ant_gain_ue_to_bs,
+ ant_gain_bs_to_ue,
+ )
+
+ # Collect IMT BS and UE antenna gain samples
+ self.path_loss_imt = np.transpose(path_loss)
+ self.imt_bs_antenna_gain = ant_gain_bs_to_ue
+ self.imt_ue_antenna_gain = np.transpose(ant_gain_ue_to_bs)
+ additional_loss = self.parameters.imt.bs.ohmic_loss \
+ + self.parameters.imt.ue.ohmic_loss \
+ + self.parameters.imt.ue.body_loss
# calculate coupling loss
- coupling_loss = np.squeeze(path_loss - gain_a - gain_b) + additional_loss
+ coupling_loss = self.path_loss_imt - self.imt_bs_antenna_gain - self.imt_ue_antenna_gain + additional_loss
return coupling_loss
@@ -307,10 +420,14 @@ def connect_ue_to_bs(self):
user equipments are distributed and pointed to a certain base station
according to the decisions taken at TG 5/1 meeting
"""
- num_ue_per_bs = self.parameters.imt.ue_k*self.parameters.imt.ue_k_m
+ num_ue_per_bs = self.parameters.imt.ue.k * self.parameters.imt.ue.k_m
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
- ue_list = [i for i in range(bs*num_ue_per_bs, bs*num_ue_per_bs + num_ue_per_bs)]
+ ue_list = [
+ i for i in range(
+ bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs,
+ )
+ ]
self.link[bs] = ue_list
def select_ue(self, random_number_gen: np.random.RandomState):
@@ -324,27 +441,50 @@ def select_ue(self, random_number_gen: np.random.RandomState):
else:
self.bs_to_ue_d_2D = self.bs.get_distance_to(self.ue)
self.bs_to_ue_d_3D = self.bs.get_3d_distance_to(self.ue)
- self.bs_to_ue_phi, self.bs_to_ue_theta = self.bs.get_pointing_vector_to(self.ue)
+ self.bs_to_ue_phi, self.bs_to_ue_theta = self.bs.get_pointing_vector_to(
+ self.ue,
+ )
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
# select K UE's among the ones that are connected to BS
random_number_gen.shuffle(self.link[bs])
- K = self.parameters.imt.ue_k
+ K = self.parameters.imt.ue.k
del self.link[bs][K:]
# Activate the selected UE's and create beams
if self.bs.active[bs]:
self.ue.active[self.link[bs]] = np.ones(K, dtype=bool)
for ue in self.link[bs]:
# add beam to BS antennas
- self.bs.antenna[bs].add_beam(self.bs_to_ue_phi[bs,ue],
- self.bs_to_ue_theta[bs,ue])
+
+ # limit beamforming angle
+ bs_beam_phi = np.clip(
+ self.bs_to_ue_phi[bs, ue],
+ *(self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range + self.bs.azimuth[bs])
+ )
+
+ bs_beam_theta = np.clip(
+ self.bs_to_ue_theta[bs, ue],
+ *self.parameters.imt.bs.antenna.array.vertical_beamsteering_range
+ )
+
+ self.bs.antenna[bs].add_beam(
+ bs_beam_phi,
+ bs_beam_theta,
+ )
+
+ # TODO?: limit beamforming on UE as well
+ # would make sense, but we don't have any parameters explicitly setting it
+
# add beam to UE antennas
- self.ue.antenna[ue].add_beam(self.bs_to_ue_phi[bs,ue] - 180,
- 180 - self.bs_to_ue_theta[bs,ue])
+ self.ue.antenna[ue].add_beam(
+ self.bs_to_ue_phi[bs, ue] - 180,
+ 180 - self.bs_to_ue_theta[bs, ue],
+ )
# set beam resource block group
- self.bs_to_ue_beam_rbs[ue] = len(self.bs.antenna[bs].beams_list) - 1
-
+ self.bs_to_ue_beam_rbs[ue] = len(
+ self.bs.antenna[bs].beams_list,
+ ) - 1
def scheduler(self):
"""
@@ -354,13 +494,21 @@ def scheduler(self):
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
ue = self.link[bs]
- self.bs.bandwidth[bs] = self.num_rb_per_ue*self.parameters.imt.rb_bandwidth
- self.ue.bandwidth[ue] = self.num_rb_per_ue*self.parameters.imt.rb_bandwidth
-
- def calculate_gains(self,
- station_1: StationManager,
- station_2: StationManager,
- c_channel = True) -> np.array:
+ self.bs.bandwidth[bs] = self.num_rb_per_ue * \
+ self.parameters.imt.rb_bandwidth
+ self.ue.bandwidth[ue] = self.num_rb_per_ue * \
+ self.parameters.imt.rb_bandwidth
+ self.ue.center_freq[ue] = np.array([
+ self.parameters.imt.frequency +
+ self.num_rb_per_ue * self.parameters.imt.rb_bandwidth * (i - (len(ue) - 1) / 2) for i in range(len(ue))
+ ])
+
+ def calculate_gains(
+ self,
+ station_1: StationManager,
+ station_2: StationManager,
+ c_channel=True,
+ ) -> np.array:
"""
Calculates the gains of antennas in station_1 in the direction of
station_2
@@ -369,99 +517,103 @@ def calculate_gains(self,
station_2_active = np.where(station_2.active)[0]
# Initialize variables (phi, theta, beams_idx)
- if(station_1.station_type is StationType.IMT_BS):
- if(station_2.station_type is StationType.IMT_UE):
+ if (station_1.station_type is StationType.IMT_BS):
+ if (station_2.station_type is StationType.IMT_UE):
phi = self.bs_to_ue_phi
theta = self.bs_to_ue_theta
beams_idx = self.bs_to_ue_beam_rbs[station_2_active]
- elif(station_2.station_type is StationType.EESS_PASSIVE or \
- station_2.station_type is StationType.FSS_SS or \
- station_2.station_type is StationType.FSS_ES or \
- station_2.station_type is StationType.HAPS or \
- station_2.station_type is StationType.FS or \
- station_2.station_type is StationType.RNS or \
- station_2.station_type is StationType.RAS):
+ elif not station_2.is_imt_station():
phi, theta = station_1.get_pointing_vector_to(station_2)
- phi = np.repeat(phi,self.parameters.imt.ue_k,0)
- theta = np.repeat(theta,self.parameters.imt.ue_k,0)
- beams_idx = np.tile(np.arange(self.parameters.imt.ue_k),self.bs.num_stations)
+ phi = np.repeat(phi, self.parameters.imt.ue.k, 0)
+ theta = np.repeat(theta, self.parameters.imt.ue.k, 0)
+ beams_idx = np.tile(
+ np.arange(self.parameters.imt.ue.k), self.bs.num_stations,
+ )
- elif(station_1.station_type is StationType.IMT_UE):
+ elif (station_1.station_type is StationType.IMT_UE):
phi, theta = station_1.get_pointing_vector_to(station_2)
- beams_idx = np.zeros(len(station_2_active),dtype=int)
-
- elif(station_1.station_type is StationType.EESS_PASSIVE or \
- station_1.station_type is StationType.FSS_SS or \
- station_1.station_type is StationType.FSS_ES or \
- station_1.station_type is StationType.HAPS or \
- station_1.station_type is StationType.FS or \
- station_1.station_type is StationType.RNS or \
- station_1.station_type is StationType.RAS):
+ beams_idx = np.zeros(len(station_2_active), dtype=int)
+
+ elif not station_1.is_imt_station():
phi, theta = station_1.get_pointing_vector_to(station_2)
- beams_idx = np.zeros(len(station_2_active),dtype=int)
+ beams_idx = np.zeros(len(station_2_active), dtype=int)
# Calculate gains
gains = np.zeros(phi.shape)
- if (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.EESS_PASSIVE) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FSS_SS) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FSS_ES) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.HAPS) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FS) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.RNS) or \
- (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.RAS):
+ if station_1.station_type is StationType.IMT_BS and not station_2.is_imt_station():
+ off_axis_angle = station_1.get_off_axis_angle(station_2)
for k in station_1_active:
- for b in range(k*self.parameters.imt.ue_k,(k+1)*self.parameters.imt.ue_k):
- gains[b,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[b,station_2_active],
- theta_vec=theta[b,station_2_active],
- beams_l=np.array([beams_idx[b]]),
- co_channel=c_channel)
-
- elif (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.EESS_PASSIVE) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FSS_SS) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FSS_ES) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.HAPS) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FS) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.RNS) or \
- (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.RAS):
- for k in station_1_active:
- gains[k,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[k,station_2_active],
- theta_vec=theta[k,station_2_active],
- beams_l=beams_idx,
- co_channel=c_channel)
+ for b in range(k * self.parameters.imt.ue.k, (k + 1) * self.parameters.imt.ue.k):
+ gains[b, station_2_active] = station_1.antenna[k].calculate_gain(
+ phi_vec=phi[b, station_2_active],
+ theta_vec=theta[
+ b,
+ station_2_active,
+ ],
+ beams_l=np.repeat(
+ beams_idx[b], len(station_2_active)
+ ),
+ co_channel=c_channel,
+ off_axis_angle_vec=off_axis_angle[k, station_2_active]
+ )
+
+ elif station_1.station_type is StationType.IMT_UE and not station_2.is_imt_station():
+ off_axis_angle = station_1.get_off_axis_angle(station_2)
+ for k in station_1_active:
+ gains[k, station_2_active] = station_1.antenna[k].calculate_gain(
+ off_axis_angle_vec=off_axis_angle[k, station_2_active],
+ phi_vec=phi[k, station_2_active],
+ theta_vec=theta[
+ k,
+ station_2_active,
+ ],
+ beams_l=beams_idx,
+ co_channel=c_channel,
+ )
elif station_1.station_type is StationType.RNS:
- gains[0,station_2_active] = station_1.antenna[0].calculate_gain(phi_vec = phi[0,station_2_active],
- theta_vec = theta[0,station_2_active])
+ gains[0, station_2_active] = station_1.antenna[0].calculate_gain(
+ phi_vec=phi[0, station_2_active],
+ theta_vec=theta[0, station_2_active],
+ )
- elif station_1.station_type is StationType.EESS_PASSIVE or \
- station_1.station_type is StationType.FSS_SS or \
- station_1.station_type is StationType.FSS_ES or \
- station_1.station_type is StationType.HAPS or \
- station_1.station_type is StationType.FS or \
- station_1.station_type is StationType.RAS:
+ elif not station_1.is_imt_station():
off_axis_angle = station_1.get_off_axis_angle(station_2)
- distance = station_1.get_distance_to(station_2)
- theta = np.degrees(np.arctan((station_1.height - station_2.height)/distance)) + station_1.elevation
- gains[0,station_2_active] = station_1.antenna[0].calculate_gain(off_axis_angle_vec=off_axis_angle[0,station_2_active],
- theta_vec=theta[0,station_2_active])
- else: # for IMT <-> IMT
+ phi, theta = station_1.get_pointing_vector_to(station_2)
for k in station_1_active:
- gains[k,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[k,station_2_active],
- theta_vec=theta[k,station_2_active],
- beams_l=beams_idx)
-
+ gains[k, station_2_active] = \
+ station_1.antenna[k].calculate_gain(
+ off_axis_angle_vec=off_axis_angle[k, station_2_active],
+ theta_vec=theta[k, station_2_active],
+ phi_vec=phi[k, station_2_active],
+ )
+ else: # for IMT <-> IMT
+ off_axis_angle = station_1.get_off_axis_angle(station_2)
+ for k in station_1_active:
+ gains[k, station_2_active] = station_1.antenna[k].calculate_gain(
+ off_axis_angle_vec=off_axis_angle[k, station_2_active],
+ phi_vec=phi[k, station_2_active],
+ theta_vec=theta[
+ k,
+ station_2_active,
+ ],
+ beams_l=beams_idx,
+ )
return gains
- def calculate_imt_tput(self,
- sinr: np.array,
- sinr_min: float,
- sinr_max: float,
- attenuation_factor: float) -> np.array:
+ def calculate_imt_tput(
+ self,
+ sinr: np.array,
+ sinr_min: float,
+ sinr_max: float,
+ attenuation_factor: float,
+ ) -> np.array:
tput_min = 0
- tput_max = attenuation_factor*math.log2(1+math.pow(10, 0.1*sinr_max))
+ tput_max = attenuation_factor * \
+ math.log2(1 + math.pow(10, 0.1 * sinr_max))
- tput = attenuation_factor*np.log2(1+np.power(10, 0.1*sinr))
+ tput = attenuation_factor * np.log2(1 + np.power(10, 0.1 * sinr))
id_min = np.where(sinr < sinr_min)[0]
id_max = np.where(sinr > sinr_max)[0]
@@ -473,7 +625,7 @@ def calculate_imt_tput(self,
return tput
- def calculate_bw_weights(self, bw_imt: float, bw_sys: float, ue_k: int) -> np.array:
+ def calculate_bw_weights(self, bw_ue: np.array, fc_ue: np.array, bw_sys: float, fc_sys: float) -> np.array:
"""
Calculates the weight that each resource block group of IMT base stations
will have when estimating the interference to other systems based on
@@ -490,46 +642,47 @@ def calculate_bw_weights(self, bw_imt: float, bw_sys: float, ue_k: int) -> np.ar
-------
K-dimentional array of weights
"""
+ ue_min_f = fc_ue - bw_ue / 2
+ ue_max_f = fc_ue + bw_ue / 2
- if bw_imt <= bw_sys:
- weights = np.ones(ue_k)
-
- elif bw_imt > bw_sys:
- weights = np.zeros(ue_k)
-
- bw_per_rbg = bw_imt / ue_k
+ sys_min_f = fc_sys - bw_sys / 2
+ sys_max_f = fc_sys + bw_sys / 2
- # number of resource block groups that will have weight equal to 1
- rb_ones = math.floor( bw_sys / bw_per_rbg )
+ overlap = np.maximum(
+ 0,
+ np.minimum(ue_max_f, sys_max_f) - np.maximum(ue_min_f, sys_min_f)
+ ) / bw_ue
- # weight of the rbg that will generate partial interference
- rb_partial = np.mod( bw_sys, bw_per_rbg ) / bw_per_rbg
-
- # assign value to weight array
- weights[:rb_ones] = 1
- weights[rb_ones] = rb_partial
-
- return weights
+ return overlap
def plot_scenario(self):
- fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k')
ax = fig.gca()
# Plot network topology
self.topology.plot(ax)
# Plot user equipments
- ax.scatter(self.ue.x, self.ue.y, color='r', edgecolor="w", linewidth=0.5, label="UE")
+ ax.scatter(
+ self.ue.x, self.ue.y, color='r',
+ edgecolor="w", linewidth=0.5, label="UE",
+ )
# wedge = Wedge((0, 0), 300, 0, 360, 290, color='b', alpha=0.2, fill=True)
# ax.add_artist(wedge)
-
+
# Plot UE's azimuth
d = 0.1 * self.topology.cell_radius
for i in range(len(self.ue.x)):
- plt.plot([self.ue.x[i], self.ue.x[i] + d*math.cos(math.radians(self.ue.azimuth[i]))],
- [self.ue.y[i], self.ue.y[i] + d*math.sin(math.radians(self.ue.azimuth[i]))],
- 'r-')
+ plt.plot(
+ [self.ue.x[i], self.ue.x[i] + d *
+ math.cos(math.radians(self.ue.azimuth[i]))],
+ [
+ self.ue.y[i], self.ue.y[i] + d *
+ math.sin(math.radians(self.ue.azimuth[i])),
+ ],
+ 'r-',
+ )
plt.axis('image')
plt.title("Simulation scenario")
@@ -540,14 +693,17 @@ def plot_scenario(self):
plt.show()
if self.parameters.imt.topology == "INDOOR":
- fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k')
+ fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k')
ax = fig.gca()
# Plot network topology
- self.topology.plot(ax,top_view=False)
+ self.topology.plot(ax, top_view=False)
# Plot user equipments
- ax.scatter(self.ue.x, self.ue.height, color='r', edgecolor="w", linewidth=0.5, label="UE")
+ ax.scatter(
+ self.ue.x, self.ue.height, color='r',
+ edgecolor="w", linewidth=0.5, label="UE",
+ )
plt.title("Simulation scenario: side view")
plt.xlabel("x-coordinate [m]")
@@ -555,7 +711,7 @@ def plot_scenario(self):
plt.legend(loc="upper left", scatterpoints=1)
plt.tight_layout()
plt.show()
-
+
# sys.exit(0)
@abstractmethod
@@ -563,7 +719,6 @@ def snapshot(self, *args, **kwargs):
"""
Performs a single snapshot.
"""
- pass
@abstractmethod
def power_control(self):
@@ -576,4 +731,3 @@ def collect_results(self, *args, **kwargs):
"""
Collects results.
"""
- pass
diff --git a/sharc/simulation_downlink.py b/sharc/simulation_downlink.py
index 30094e369..266a96840 100644
--- a/sharc/simulation_downlink.py
+++ b/sharc/simulation_downlink.py
@@ -11,9 +11,7 @@
from sharc.simulation import Simulation
from sharc.parameters.parameters import Parameters
from sharc.station_factory import StationFactory
-from sharc.support.enumerations import StationType
-
-from sharc.propagation.propagation_factory import PropagationFactory
+from sharc.parameters.constants import BOLTZMANN_CONSTANT
class SimulationDownlink(Simulation):
@@ -30,35 +28,49 @@ def snapshot(self, *args, **kwargs):
seed = kwargs["seed"]
random_number_gen = np.random.RandomState(seed)
-
+
# In case of hotspots, base stations coordinates have to be calculated
# on every snapshot. Anyway, let topology decide whether to calculate
# or not
+ num_stations_before = self.topology.num_base_stations
+
self.topology.calculate_coordinates(random_number_gen)
+ if num_stations_before != self.topology.num_base_stations:
+ self.initialize_topology_dependant_variables()
+
# Create the base stations (remember that it takes into account the
# network load factor)
- self.bs = StationFactory.generate_imt_base_stations(self.parameters.imt,
- self.parameters.antenna_imt,
- self.topology, random_number_gen)
+ self.bs = StationFactory.generate_imt_base_stations(
+ self.parameters.imt,
+ # TODO: remove this:
+ self.parameters.imt.bs.antenna.array,
+ self.topology, random_number_gen,
+ )
# Create the other system (FSS, HAPS, etc...)
- self.system = StationFactory.generate_system(self.parameters, self.topology, random_number_gen)
+ self.system = StationFactory.generate_system(
+ self.parameters, self.topology, random_number_gen,
+ geometry_converter=self.geometry_converter
+ )
# Create IMT user equipments
- self.ue = StationFactory.generate_imt_ue(self.parameters.imt,
- self.parameters.antenna_imt,
- self.topology, random_number_gen)
+ self.ue = StationFactory.generate_imt_ue(
+ self.parameters.imt,
+ # TODO: remove this:
+ self.parameters.imt.ue.antenna.array,
+ self.topology, random_number_gen,
+ )
+
+ # self.plot_scenario()
- #self.plot_scenario()
-
self.connect_ue_to_bs()
self.select_ue(random_number_gen)
# Calculate coupling loss after beams are created
- self.coupling_loss_imt = self.calculate_coupling_loss(self.bs,
- self.ue,
- self.propagation_imt)
+ self.coupling_loss_imt = self.calculate_intra_imt_coupling_loss(
+ self.ue, self.bs,
+ )
self.scheduler()
self.power_control()
@@ -67,13 +79,11 @@ def snapshot(self, *args, **kwargs):
# interference into IMT
self.calculate_sinr()
self.calculate_sinr_ext()
- pass
else:
# Execute this piece of code if IMT generates interference into
# the other system
self.calculate_sinr()
self.calculate_external_interference()
- pass
self.collect_results(write_to_file, snapshot_number)
@@ -86,19 +96,20 @@ def power_control(self):
"""
# Currently, the maximum transmit power of the base station is equaly
# divided among the selected UEs
- total_power = self.parameters.imt.bs_conducted_power \
- + self.bs_power_gain
- tx_power = total_power - 10 * math.log10(self.parameters.imt.ue_k)
+ total_power = self.parameters.imt.bs.conducted_power \
+ + self.bs_power_gain
+ tx_power = total_power - 10 * math.log10(self.parameters.imt.ue.k)
# calculate transmit powers to have a structure such as
# {bs_1: [pwr_1, pwr_2,...], ...}, where bs_1 is the base station id,
# pwr_1 is the transmit power from bs_1 to ue_1, pwr_2 is the transmit
# power from bs_1 to ue_2, etc
bs_active = np.where(self.bs.active)[0]
- self.bs.tx_power = dict([(bs, tx_power*np.ones(self.parameters.imt.ue_k)) for bs in bs_active])
+ self.bs.tx_power = dict(
+ [(bs, tx_power * np.ones(self.parameters.imt.ue.k)) for bs in bs_active])
# Update the spectral mask
if self.adjacent_channel:
- self.bs.spectral_mask.set_mask(power = total_power)
+ self.bs.spectral_mask.set_mask(p_tx=total_power)
def calculate_sinr(self):
"""
@@ -107,26 +118,33 @@ def calculate_sinr(self):
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
ue = self.link[bs]
- self.ue.rx_power[ue] = self.bs.tx_power[bs] - self.coupling_loss_imt[bs,ue]
+ self.ue.rx_power[ue] = self.bs.tx_power[bs] - \
+ self.coupling_loss_imt[bs, ue]
# create a list with base stations that generate interference in ue_list
bs_interf = [b for b in bs_active if b not in [bs]]
# calculate intra system interference
for bi in bs_interf:
- interference = self.bs.tx_power[bi] - self.coupling_loss_imt[bi,ue]
+ interference = self.bs.tx_power[bi] - \
+ self.coupling_loss_imt[bi, ue]
- self.ue.rx_interference[ue] = 10*np.log10( \
- np.power(10, 0.1*self.ue.rx_interference[ue]) + np.power(10, 0.1*interference))
+ self.ue.rx_interference[ue] = 10 * np.log10(
+ np.power(
+ 10, 0.1 * self.ue.rx_interference[ue]) + np.power(10, 0.1 * interference),
+ )
+ # Thermal noise in dBm
self.ue.thermal_noise = \
- 10*math.log10(self.parameters.imt.BOLTZMANN_CONSTANT*self.parameters.imt.noise_temperature*1e3) + \
- 10*np.log10(self.ue.bandwidth * 1e6) + \
+ 10 * math.log10(BOLTZMANN_CONSTANT * self.parameters.imt.noise_temperature * 1e3) + \
+ 10 * np.log10(self.ue.bandwidth * 1e6) + \
self.ue.noise_figure
self.ue.total_interference = \
- 10*np.log10(np.power(10, 0.1*self.ue.rx_interference) + \
- np.power(10, 0.1*self.ue.thermal_noise))
+ 10 * np.log10(
+ np.power(10, 0.1 * self.ue.rx_interference) +
+ np.power(10, 0.1 * self.ue.thermal_noise),
+ )
self.ue.sinr = self.ue.rx_power - self.ue.total_interference
self.ue.snr = self.ue.rx_power - self.ue.thermal_noise
@@ -136,38 +154,187 @@ def calculate_sinr_ext(self):
Calculates the downlink SINR and INR for each UE taking into account the
interference that is generated by the other system into IMT system.
"""
- self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system,
- self.ue,
- self.propagation_system,
- c_channel = self.co_channel)
+ self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt(self.system,
+ self.ue,
+ self.co_channel)
# applying a bandwidth scaling factor since UE transmits on a portion
# of the satellite's bandwidth
# calculate interference only to active UE's
ue = np.where(self.ue.active)[0]
+ active_sys = np.where(self.system.active)[0]
- tx_power_sys = self.param_system.tx_power_density + 10*np.log10(self.ue.bandwidth[ue]*1e6) + 30
- self.ue.ext_interference[ue] = tx_power_sys - self.coupling_loss_imt_system[ue]
+ # All UEs are active on an active BS
+ bs_active = np.where(self.bs.active)[0]
+ for bs in bs_active:
+ ue = self.link[bs]
+
+ # Get the weight factor for the system overlaping bandwidth in each UE band.
+ weights = self.calculate_bw_weights(
+ self.ue.bandwidth[ue],
+ self.ue.center_freq[ue],
+ float(self.param_system.bandwidth),
+ float(self.param_system.frequency),
+ )
- self.ue.sinr_ext[ue] = self.ue.rx_power[ue] \
- - (10*np.log10(np.power(10, 0.1*self.ue.total_interference[ue]) + np.power(10, 0.1*self.ue.ext_interference[ue])))
- self.ue.inr[ue] = self.ue.ext_interference[ue] - self.ue.thermal_noise[ue]
+ in_band_interf_power = -500.
+ if self.co_channel:
+ # Inteferer transmit power in dBm over the overlapping band (MHz) with UEs.
+ if self.overlapping_bandwidth > 0:
+ # in_band_interf_power = self.param_system.tx_power_density + \
+ # 10 * np.log10(self.overlapping_bandwidth * 1e6) + 30
+ in_band_interf_power = \
+ self.param_system.tx_power_density + 10 * np.log10(
+ self.ue.bandwidth[ue, np.newaxis] * 1e6
+ ) + 10 * np.log10(weights)[:, np.newaxis] - self.coupling_loss_imt_system[ue, :][:, active_sys]
+
+ oob_power = -500.
+ if self.adjacent_channel:
+ # emissions outside of tx bandwidth and inside of rx bw
+ # due to oob emissions on tx side
+ tx_oob = np.resize(-500., len(ue))
+
+ # emissions outside of rx bw and inside of tx bw
+ # due to non ideal filtering on rx side
+ # will be the same for all UE's, only considering
+ rx_oob = np.resize(-500., len(ue))
+
+ # TODO: M.2101 states that:
+ # "The ACIR value should be calculated based on per UE allocated number of resource blocks"
+
+ # should we actually implement that for ACS since the receiving filter is fixed?
+
+ # or maybe ignore ACS altogether (ACS = inf)? If we consider only allocated RB, it makes
+ # no sense to use ACS.
+ # At the same time, ignoring ACS doesn't seem correct since the interference
+ # could DECREASE when it would make sense for it to increase.
+ # e.g. adjacent systems -> slightly co-channel with ACS = inf
+ # should interfer ^ less than this ^
+
+ # Unless we never use ACS..?
+ if self.parameters.imt.adjacent_ch_reception == "ACS":
+ if self.overlapping_bandwidth:
+ if not hasattr(self, "ALREADY_WARNED_ABOUT_ACS_WHEN_OVERLAPPING_BAND"):
+ print(
+ "[WARNING]: You're trying to use ACS on a partially overlapping band"
+ "with UEs. Verify the code implements the behavior you expect"
+ )
+ self.ALREADY_WARNED_ABOUT_ACS_WHEN_OVERLAPPING_BAND = True
+ # only apply ACS over non overlapping bw
+ p_tx = self.param_system.tx_power_density \
+ + 10 * np.log10(
+ (self.param_system.bandwidth - self.overlapping_bandwidth) * 1e6
+ )
+
+ rx_oob[::] = p_tx - self.parameters.imt.ue.adjacent_ch_selectivity
+ elif self.parameters.imt.adjacent_ch_reception == "OFF":
+ pass
+ else:
+ raise ValueError(
+ f"No implementation for parameters.imt.adjacent_ch_reception == {self.parameters.imt.adjacent_ch_reception}"
+ )
+
+ # for tx oob we accept ACLR and spectral mask
+ if self.param_system.adjacent_ch_emissions == "SPECTRAL_MASK":
+ ue_bws = self.ue.bandwidth[ue]
+ center_freqs = self.ue.center_freq[ue]
+
+ for i, center_freq, bw in zip(range(len(center_freqs)), center_freqs, ue_bws):
+ # calculate tx emissions in UE in use bandwidth only
+ tx_oob[i] = self.system.spectral_mask.power_calc(
+ center_freq,
+ bw
+ ) - 30
+ elif self.param_system.adjacent_ch_emissions == "ACLR":
+ # consider ACLR only over non co-channel RBs
+ # This should diminish some of the ACLR interference
+ # in a way that make sense
+ tx_oob[::] = self.param_system.tx_power_density + \
+ 10 * np.log10(self.param_system.bandwidth * 1e6) - \
+ self.param_system.adjacent_ch_leak_ratio + \
+ 10 * np.log10(1. - weights)
+ elif self.param_system.adjacent_ch_emissions == "OFF":
+ pass
+ else:
+ raise ValueError(
+ f"No implementation for param_system.adjacent_ch_emissions == {self.param_system.adjacent_ch_emissions}"
+ )
+
+ # Out of band power
+ # sum linearly power leaked into band and power received in the adjacent band
+
+ oob_power = 10 * np.log10(
+ 10 ** (0.1 * tx_oob) + 10 ** (0.1 * rx_oob)
+ )
+ # repeat ue received power for each coupling loss
+ oob_power = np.tile(
+ np.reshape(
+ oob_power,
+ (-1, 1)
+ ),
+ (1, len(active_sys))
+ )
+ # could use different coupling loss if
+ # different antenna pattern is to be considered on adj channel
+ oob_power -= self.coupling_loss_imt_system[ue, :][:, active_sys]
+
+ # Total external interference into the UE in dBm
+ ue_ext_int = 10 * np.log10(np.power(10, 0.1 * in_band_interf_power) + np.power(10, 0.1 * oob_power))
+ # ue_ext_int = ext_interference - self.coupling_loss_imt_system[ue, :][:, active_sys]
+
+ # Sum all the interferers for each UE
+ self.ue.ext_interference[ue] = 10 * np.log10(np.sum(np.power(10, 0.1 * ue_ext_int), axis=1)) + 30
+
+ self.ue.sinr_ext[ue] = \
+ self.ue.rx_power[ue] - (10 * np.log10(np.power(10, 0.1 * self.ue.total_interference[ue]) +
+ np.power(10, 0.1 * (self.ue.ext_interference[ue]))))
+
+ # Calculate INR in dB
+ self.ue.thermal_noise[ue] = \
+ 10 * np.log10(BOLTZMANN_CONSTANT * self.parameters.imt.noise_temperature * 1e3) + \
+ 10 * np.log10(self.ue.bandwidth[ue] * 1e6) + self.parameters.imt.ue.noise_figure
+
+ self.ue.inr[ue] = self.ue.ext_interference[ue] - \
+ self.ue.thermal_noise[ue]
+
+ # Calculate PFD at the UE
+
+ # Distance from each system transmitter to each UE receiver (in meters)
+ dist_sys_to_imt = self.system.get_3d_distance_to(self.ue) # shape: [n_tx, n_ue]
+
+ # EIRP in dBW/MHz per transmitter
+ eirp_dBW_MHz = self.param_system.tx_power_density + 60 + self.system_imt_antenna_gain
+
+ # PFD formula (dBW/mยฒ/MHz)
+ # PFD = EIRP - 10log10(4ฯ) - 20log10(distance)
+ # Store the PFD for each transmitter and each UE
+ self.ue.pfd_external = eirp_dBW_MHz - 10.992098640220963 - 20 * np.log10(dist_sys_to_imt)
+
+ # Total PFD per UE (sum of PFDs from each transmitter)
+ # Convert PFD from dB to linear scale (W/mยฒ/MHz)
+ pfd_linear = 10 ** (self.ue.pfd_external / 10)
+ # Sum PFDs from all transmitters for each UE (axis=0 assumes shape [n_tx, n_ue])
+ pfd_agg_linear = np.sum(pfd_linear[active_sys][:, ue], axis=0)
+ # Convert back to dBW
+ self.ue.pfd_external_aggregated = 10 * np.log10(pfd_agg_linear)
def calculate_external_interference(self):
"""
Calculates interference that IMT system generates on other system
"""
-
if self.co_channel:
- self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system,
- self.bs,
- self.propagation_system)
-
+ self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt(
+ self.system,
+ self.bs,
+ is_co_channel=True,
+ )
if self.adjacent_channel:
- self.coupling_loss_imt_system_adjacent = self.calculate_coupling_loss(self.system,
- self.bs,
- self.propagation_system,
- c_channel=False)
+ self.coupling_loss_imt_system_adjacent = \
+ self.calculate_coupling_loss_system_imt(
+ self.system,
+ self.bs,
+ is_co_channel=False,
+ )
# applying a bandwidth scaling factor since UE transmits on a portion
# of the interfered systems bandwidth
@@ -175,98 +342,168 @@ def calculate_external_interference(self):
rx_interference = 0
bs_active = np.where(self.bs.active)[0]
+ sys_active = np.where(self.system.active)[0]
for bs in bs_active:
-
- active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)]
+ active_beams = [
+ i for i in range(
+ bs *
+ self.parameters.imt.ue.k, (bs + 1) *
+ self.parameters.imt.ue.k,
+ )
+ ]
if self.co_channel:
if self.overlapping_bandwidth:
acs = 0
- weights = self.calculate_bw_weights(self.parameters.imt.bandwidth,
- self.param_system.bandwidth,
- self.parameters.imt.ue_k)
+ ue = self.link[bs]
+ weights = self.calculate_bw_weights(
+ self.ue.bandwidth[ue],
+ self.ue.center_freq[ue],
+ self.param_system.bandwidth,
+ self.param_system.frequency,
+ )
else:
acs = self.param_system.adjacent_ch_selectivity
- weights = np.ones(self.parameters.imt.ue_k)
+ weights = np.ones(self.parameters.imt.ue.k)
- interference = self.bs.tx_power[bs] - self.coupling_loss_imt_system[active_beams]
- rx_interference += np.sum(weights*np.power(10, 0.1*interference)) / 10**(acs/10.)
+ interference = self.bs.tx_power[bs] - \
+ self.coupling_loss_imt_system[active_beams, sys_active]
+ rx_interference += np.sum(
+ weights * np.power(
+ 10,
+ 0.1 * interference,
+ ),
+ ) / 10**(acs / 10.)
if self.adjacent_channel:
- # The unwanted emission is calculated in terms of TRP (after
- # antenna). In SHARC implementation, ohmic losses are already
- # included in coupling loss. Then, care has to be taken;
+ # The unwanted emission is calculated in terms of TRP (after
+ # antenna). In SHARC implementation, ohmic losses are already
+ # included in coupling loss. Then, care has to be taken;
# otherwise ohmic loss will be included twice.
oob_power = self.bs.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth) \
- + self.parameters.imt.bs_ohmic_loss
+ + self.parameters.imt.bs.ohmic_loss
oob_interference = oob_power \
- - self.coupling_loss_imt_system_adjacent[active_beams[0]] \
- + 10*np.log10((self.param_system.bandwidth - self.overlapping_bandwidth)/
- self.param_system.bandwidth)
-
- rx_interference += math.pow(10, 0.1*oob_interference)
+ - self.coupling_loss_imt_system_adjacent[active_beams[0]] \
+ + 10 * np.log10(
+ (self.param_system.bandwidth - self.overlapping_bandwidth) /
+ self.param_system.bandwidth,
+ )
+
+ rx_interference += math.pow(10, 0.1 * oob_interference)
- self.system.rx_interference = 10*np.log10(rx_interference)
+ # Total received interference - dBW
+ self.system.rx_interference = 10 * np.log10(rx_interference)
# calculate N
self.system.thermal_noise = \
- 10*math.log10(self.param_system.BOLTZMANN_CONSTANT* \
- self.system.noise_temperature*1e3) + \
- 10*math.log10(self.param_system.bandwidth * 1e6)
+ 10 * math.log10(BOLTZMANN_CONSTANT * self.system.noise_temperature * 1e3) + \
+ 10 * math.log10(self.param_system.bandwidth * 1e6)
- # calculate INR at the system
- self.system.inr = np.array([self.system.rx_interference - self.system.thermal_noise])
+ # Calculate INR at the system - dBm/MHz
+ self.system.inr = np.array(
+ [self.system.rx_interference - self.system.thermal_noise],
+ )
# Calculate PFD at the system
- if self.system.station_type is StationType.RAS:
- self.system.pfd = 10*np.log10(10**(self.system.rx_interference/10)/self.system.antenna[0].effective_area)
+ # TODO: generalize this a bit more if needed
+ if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1:
+ self.system.pfd = 10 * \
+ np.log10(
+ 10**(self.system.rx_interference / 10) /
+ self.system.antenna[0].effective_area,
+ )
def collect_results(self, write_to_file: bool, snapshot_number: int):
if not self.parameters.imt.interfered_with and np.any(self.bs.active):
self.results.system_inr.extend(self.system.inr.tolist())
- self.results.system_dl_interf_power.extend([self.system.rx_interference])
- if self.system.station_type is StationType.RAS:
+ self.results.system_dl_interf_power.extend(
+ [self.system.rx_interference],
+ )
+ self.results.system_dl_interf_power_per_mhz.extend(
+ [self.system.rx_interference - 10 * math.log10(self.system.bandwidth)],
+ )
+ # TODO: generalize this a bit more if needed (same conditional as above)
+ if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1:
self.results.system_pfd.extend([self.system.pfd])
bs_active = np.where(self.bs.active)[0]
+ sys_active = np.where(self.system.active)[0]
for bs in bs_active:
ue = self.link[bs]
- self.results.imt_path_loss.extend(self.path_loss_imt[bs,ue])
- self.results.imt_coupling_loss.extend(self.coupling_loss_imt[bs,ue])
-
- self.results.imt_bs_antenna_gain.extend(self.imt_bs_antenna_gain[bs,ue])
- self.results.imt_ue_antenna_gain.extend(self.imt_ue_antenna_gain[bs,ue])
-
- tput = self.calculate_imt_tput(self.ue.sinr[ue],
- self.parameters.imt.dl_sinr_min,
- self.parameters.imt.dl_sinr_max,
- self.parameters.imt.dl_attenuation_factor)
+ self.results.imt_path_loss.extend(self.path_loss_imt[bs, ue])
+ self.results.imt_coupling_loss.extend(
+ self.coupling_loss_imt[bs, ue],
+ )
+
+ self.results.imt_bs_antenna_gain.extend(
+ self.imt_bs_antenna_gain[bs, ue],
+ )
+ self.results.imt_ue_antenna_gain.extend(
+ self.imt_ue_antenna_gain[bs, ue],
+ )
+
+ tput = self.calculate_imt_tput(
+ self.ue.sinr[ue],
+ self.parameters.imt.downlink.sinr_min,
+ self.parameters.imt.downlink.sinr_max,
+ self.parameters.imt.downlink.attenuation_factor,
+ )
self.results.imt_dl_tput.extend(tput.tolist())
- if self.parameters.imt.interfered_with:
- tput_ext = self.calculate_imt_tput(self.ue.sinr_ext[ue],
- self.parameters.imt.dl_sinr_min,
- self.parameters.imt.dl_sinr_max,
- self.parameters.imt.dl_attenuation_factor)
+ # Results for IMT-SYSTEM
+ if self.parameters.imt.interfered_with: # IMT suffers interference
+ tput_ext = self.calculate_imt_tput(
+ self.ue.sinr_ext[ue],
+ self.parameters.imt.downlink.sinr_min,
+ self.parameters.imt.downlink.sinr_max,
+ self.parameters.imt.downlink.attenuation_factor,
+ )
self.results.imt_dl_tput_ext.extend(tput_ext.tolist())
- self.results.imt_dl_sinr_ext.extend(self.ue.sinr_ext[ue].tolist())
+ self.results.imt_dl_sinr_ext.extend(
+ self.ue.sinr_ext[ue].tolist(),
+ )
self.results.imt_dl_inr.extend(self.ue.inr[ue].tolist())
- self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,ue])
- self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,ue])
- self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,ue])
+ self.results.imt_dl_pfd_external.extend(self.ue.pfd_external[sys_active[:, np.newaxis], ue].flatten())
+
+ self.results.imt_dl_pfd_external_aggregated.extend(self.ue.pfd_external_aggregated[ue].tolist())
+
+ self.results.system_imt_antenna_gain.extend(
+ self.system_imt_antenna_gain[sys_active[:, np.newaxis], ue].flatten(),
+ )
+ self.results.imt_system_antenna_gain.extend(
+ self.imt_system_antenna_gain[sys_active[:, np.newaxis], ue].flatten(),
+ )
+ self.results.imt_system_path_loss.extend(
+ self.imt_system_path_loss[sys_active[:, np.newaxis], ue].flatten(),
+ )
if self.param_system.channel_model == "HDFSS":
- self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[0,ue])
- self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[0,ue])
- else:
- active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)]
- self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,active_beams])
- self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,active_beams])
- self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,active_beams])
+ self.results.imt_system_build_entry_loss.extend(
+ self.imt_system_build_entry_loss[sys_active[:, np.newaxis], ue].flatten(),
+ )
+ self.results.imt_system_diffraction_loss.extend(
+ self.imt_system_diffraction_loss[sys_active[:, np.newaxis], ue].flatten(),
+ )
+ self.results.sys_to_imt_coupling_loss.extend(
+ self.coupling_loss_imt_system[np.array(ue)[:, np.newaxis], sys_active].flatten())
+ else: # IMT is the interferer
+ self.results.system_imt_antenna_gain.extend(
+ self.system_imt_antenna_gain[sys_active[:, np.newaxis], bs].flatten(),
+ )
+ self.results.imt_system_antenna_gain.extend(
+ self.imt_system_antenna_gain[sys_active[:, np.newaxis], bs].flatten(),
+ )
+ self.results.imt_system_path_loss.extend(
+ self.imt_system_path_loss[sys_active[:, np.newaxis], bs].flatten(),
+ )
if self.param_system.channel_model == "HDFSS":
- self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,bs])
- self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,bs])
+ self.results.imt_system_build_entry_loss.extend(
+ self.imt_system_build_entry_loss[:, bs],
+ )
+ self.results.imt_system_diffraction_loss.extend(
+ self.imt_system_diffraction_loss[:, bs],
+ )
self.results.imt_dl_tx_power.extend(self.bs.tx_power[bs].tolist())
@@ -276,4 +513,3 @@ def collect_results(self, write_to_file: bool, snapshot_number: int):
if write_to_file:
self.results.write_files(snapshot_number)
self.notify_observers(source=__name__, results=self.results)
-
diff --git a/sharc/simulation_uplink.py b/sharc/simulation_uplink.py
index 723b63d04..af2ddbd94 100644
--- a/sharc/simulation_uplink.py
+++ b/sharc/simulation_uplink.py
@@ -11,9 +11,8 @@
from sharc.simulation import Simulation
from sharc.parameters.parameters import Parameters
from sharc.station_factory import StationFactory
-from sharc.support.enumerations import StationType
+from sharc.parameters.constants import BOLTZMANN_CONSTANT
-from sharc.propagation.propagation_factory import PropagationFactory
class SimulationUplink(Simulation):
"""
@@ -33,30 +32,45 @@ def snapshot(self, *args, **kwargs):
# In case of hotspots, base stations coordinates have to be calculated
# on every snapshot. Anyway, let topology decide whether to calculate
# or not
+ num_stations_before = self.topology.num_base_stations
+
self.topology.calculate_coordinates(random_number_gen)
+ if num_stations_before != self.topology.num_base_stations:
+ self.initialize_topology_dependant_variables()
+
# Create the base stations (remember that it takes into account the
# network load factor)
- self.bs = StationFactory.generate_imt_base_stations(self.parameters.imt,
- self.parameters.antenna_imt,
- self.topology, random_number_gen)
+ self.bs = StationFactory.generate_imt_base_stations(
+ self.parameters.imt,
+ # TODO: remove this:
+ self.parameters.imt.bs.antenna.array,
+ self.topology, random_number_gen,
+ )
# Create the other system (FSS, HAPS, etc...)
- self.system = StationFactory.generate_system(self.parameters, self.topology, random_number_gen)
+ self.system = StationFactory.generate_system(
+ self.parameters, self.topology, random_number_gen,
+ geometry_converter=self.geometry_converter
+ )
# Create IMT user equipments
- self.ue = StationFactory.generate_imt_ue(self.parameters.imt,
- self.parameters.antenna_imt,
- self.topology, random_number_gen)
- #self.plot_scenario()
+ self.ue = StationFactory.generate_imt_ue(
+ self.parameters.imt,
+ # TODO: remove this:
+ self.parameters.imt.ue.antenna.array,
+ self.topology, random_number_gen,
+ )
+ # self.plot_scenario()
self.connect_ue_to_bs()
self.select_ue(random_number_gen)
# Calculate coupling loss after beams are created
- self.coupling_loss_imt = self.calculate_coupling_loss(self.bs,
- self.ue,
- self.propagation_imt)
+ self.coupling_loss_imt = self.calculate_intra_imt_coupling_loss(
+ self.ue,
+ self.bs,
+ )
self.scheduler()
self.power_control()
@@ -65,44 +79,41 @@ def snapshot(self, *args, **kwargs):
# interference into IMT
self.calculate_sinr()
self.calculate_sinr_ext()
- #self.add_external_interference()
- #self.recalculate_sinr()
- #self.calculate_imt_degradation()
- pass
else:
# Execute this piece of code if IMT generates interference into
# the other system
self.calculate_sinr()
self.calculate_external_interference()
- #self.calculate_external_degradation()
- pass
self.collect_results(write_to_file, snapshot_number)
-
def power_control(self):
"""
Apply uplink power control algorithm
"""
- if self.parameters.imt.ue_tx_power_control == "OFF":
+ if self.parameters.imt.ue.tx_power_control == "OFF":
ue_active = np.where(self.ue.active)[0]
- self.ue.tx_power[ue_active] = self.parameters.imt.ue_p_cmax * np.ones(len(ue_active))
+ self.ue.tx_power[ue_active] = self.parameters.imt.ue.p_cmax * \
+ np.ones(len(ue_active))
else:
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
ue = self.link[bs]
- p_cmax = self.parameters.imt.ue_p_cmax
+ p_cmax = self.parameters.imt.ue.p_cmax
m_pusch = self.num_rb_per_ue
- p_o_pusch = self.parameters.imt.ue_p_o_pusch
- alpha = self.parameters.imt.ue_alpha
- ue_power_dynamic_range = self.parameters.imt.ue_power_dynamic_range
- cl = self.coupling_loss_imt[bs,ue]
- self.ue.tx_power[ue] = np.minimum(p_cmax, 10*np.log10(m_pusch) + p_o_pusch + alpha*cl)
+ p_o_pusch = self.parameters.imt.ue.p_o_pusch
+ alpha = self.parameters.imt.ue.alpha
+ ue_power_dynamic_range = self.parameters.imt.ue.power_dynamic_range
+ cl = self.coupling_loss_imt[bs, ue]
+ self.ue.tx_power[ue] = np.minimum(
+ p_cmax, 10 * np.log10(m_pusch) + p_o_pusch + alpha * cl,
+ )
# apply the power dymanic range
- self.ue.tx_power[ue] = np.maximum(self.ue.tx_power[ue], p_cmax - ue_power_dynamic_range)
- if self.adjacent_channel:
- self.ue_power_diff = self.parameters.imt.ue_p_cmax - self.ue.tx_power
-
+ self.ue.tx_power[ue] = np.maximum(
+ self.ue.tx_power[ue], p_cmax - ue_power_dynamic_range,
+ )
+ if self.adjacent_channel:
+ self.ue_power_diff = self.parameters.imt.ue.p_cmax - self.ue.tx_power
def calculate_sinr(self):
"""
@@ -113,68 +124,112 @@ def calculate_sinr(self):
for bs in bs_active:
ue = self.link[bs]
- self.bs.rx_power[bs] = self.ue.tx_power[ue] - self.coupling_loss_imt[bs,ue]
+ self.bs.rx_power[bs] = self.ue.tx_power[ue] - \
+ self.coupling_loss_imt[bs, ue]
# create a list of BSs that serve the interfering UEs
bs_interf = [b for b in bs_active if b not in [bs]]
# calculate intra system interference
for bi in bs_interf:
ui = self.link[bi]
- interference = self.ue.tx_power[ui] - self.coupling_loss_imt[bs,ui]
- self.bs.rx_interference[bs] = 10*np.log10( \
- np.power(10, 0.1*self.bs.rx_interference[bs])
- + np.power(10, 0.1*interference))
+ interference = self.ue.tx_power[ui] - \
+ self.coupling_loss_imt[bs, ui]
+ self.bs.rx_interference[bs] = 10 * np.log10(
+ np.power(10, 0.1 * self.bs.rx_interference[bs]) +
+ np.power(10, 0.1 * interference),
+ )
# calculate N
+ # thermal noise in dBm
self.bs.thermal_noise[bs] = \
- 10*np.log10(self.parameters.imt.BOLTZMANN_CONSTANT*self.parameters.imt.noise_temperature*1e3) + \
- 10*np.log10(self.bs.bandwidth[bs] * 1e6) + \
+ 10 * np.log10(BOLTZMANN_CONSTANT * self.parameters.imt.noise_temperature * 1e3) + \
+ 10 * np.log10(self.bs.bandwidth[bs] * 1e6) + \
self.bs.noise_figure[bs]
# calculate I+N
self.bs.total_interference[bs] = \
- 10*np.log10(np.power(10, 0.1*self.bs.rx_interference[bs]) + \
- np.power(10, 0.1*self.bs.thermal_noise[bs]))
+ 10 * np.log10(
+ np.power(10, 0.1 * self.bs.rx_interference[bs]) +
+ np.power(10, 0.1 * self.bs.thermal_noise[bs]),
+ )
# calculate SNR and SINR
- self.bs.sinr[bs] = self.bs.rx_power[bs] - self.bs.total_interference[bs]
+ self.bs.sinr[bs] = self.bs.rx_power[bs] - \
+ self.bs.total_interference[bs]
self.bs.snr[bs] = self.bs.rx_power[bs] - self.bs.thermal_noise[bs]
-
def calculate_sinr_ext(self):
"""
- Calculates the downlink SINR for each UE taking into account the
+ Calculates the uplink SINR for each BS taking into account the
interference that is generated by the other system into IMT system.
"""
- self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system,
- self.bs,
- self.propagation_system)
+ self.coupling_loss_imt_system = \
+ self.calculate_coupling_loss_system_imt(
+ self.system,
+ self.bs)
+
+ in_band_interf = -500
+ if self.co_channel:
+ if self.overlapping_bandwidth > 0:
+ # Inteferer transmit power in dBm over the overlapping band (MHz)
+ in_band_interf = self.param_system.tx_power_density + \
+ 10 * np.log10(self.overlapping_bandwidth * 1e6) + 30
+
+ oob_power = -500
+ oob_interf_lin = 0
+ if self.adjacent_channel:
+ if self.parameters.imt.adjacent_interf_model == "SPECTRAL_MASK":
+ # Out-of-band power in the adjacent channel.
+ oob_power = self.system.spectral_mask.power_calc(self.parameters.imt.frequency,
+ self.parameters.imt.bandwidth)
+ oob_interf_lin = np.power(10, 0.1 * oob_power) / \
+ np.power(10, 0.1 * self.parameters.imt.bs_adjacent_ch_selectivity)
+ elif self.parameters.imt.adjacent_interf_model == "ACIR":
+ acir = -10 * np.log10(10**(-self.param_system.adjacent_ch_leak_ratio / 10) +
+ 10**(-self.parameters.imt.bs_adjacent_ch_selectivity / 10))
+ oob_power = self.param_system.tx_power_density + \
+ 10 * np.log10(self.param_system.bandwidth * 1e6) - \
+ acir + 30
+ oob_interf_lin = 10**(oob_power / 10)
+
+ ext_interference = 10 * np.log10(np.power(10, 0.1 * in_band_interf) + oob_interf_lin)
bs_active = np.where(self.bs.active)[0]
- tx_power = self.param_system.tx_power_density + 10*np.log10(self.bs.bandwidth*1e6) + 30
+ sys_active = np.where(self.system.active)[0]
for bs in bs_active:
- active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)]
- self.bs.ext_interference[bs] = tx_power[bs] - self.coupling_loss_imt_system[active_beams]
+ active_beams = \
+ [i for i in range(bs * self.parameters.imt.ue.k, (bs + 1) * self.parameters.imt.ue.k)]
- self.bs.sinr_ext[bs] = self.bs.rx_power[bs] \
- - (10*np.log10(np.power(10, 0.1*self.bs.total_interference[bs]) + np.power(10, 0.1*self.bs.ext_interference[bs])))
- self.bs.inr[bs] = self.bs.ext_interference[bs] - self.bs.thermal_noise[bs]
+ # Interference for each active system transmitter
+ bs_ext_interference = ext_interference - \
+ self.coupling_loss_imt_system[active_beams, :][:, sys_active]
+ # Sum all the interferers for each bs
+ self.bs.ext_interference[bs] = 10 * np.log10(np.sum(np.power(10, 0.1 * bs_ext_interference), axis=1))
+ self.bs.sinr_ext[bs] = self.bs.rx_power[bs] \
+ - (10 * np.log10(np.power(10, 0.1 * self.bs.total_interference[bs]) +
+ np.power(10, 0.1 * self.bs.ext_interference[bs],),))
+ self.bs.inr[bs] = self.bs.ext_interference[bs] - \
+ self.bs.thermal_noise[bs]
def calculate_external_interference(self):
"""
Calculates interference that IMT system generates on other system
"""
- if self.co_channel:
- self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system,
- self.ue,
- self.propagation_system)
+ if self.co_channel:
+ self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt(
+ self.system,
+ self.ue,
+ is_co_channel=True,
+ )
if self.adjacent_channel:
- self.coupling_loss_imt_system_adjacent = self.calculate_coupling_loss(self.system,
- self.ue,
- self.propagation_system,
- c_channel=False)
+ self.coupling_loss_imt_system_adjacent = \
+ self.calculate_coupling_loss_system_imt(
+ self.system,
+ self.ue,
+ is_co_channel=False,
+ )
# applying a bandwidth scaling factor since UE transmits on a portion
# of the satellite's bandwidth
@@ -182,105 +237,171 @@ def calculate_external_interference(self):
rx_interference = 0
bs_active = np.where(self.bs.active)[0]
+ sys_active = np.where(self.system.active)[0]
for bs in bs_active:
ue = self.link[bs]
if self.co_channel:
if self.overlapping_bandwidth:
acs = 0
- weights = self.calculate_bw_weights(self.parameters.imt.bandwidth,
- self.param_system.bandwidth,
- self.parameters.imt.ue_k)
+ weights = self.calculate_bw_weights(
+ self.ue.bandwidth[ue],
+ self.ue.center_freq[ue],
+ self.param_system.bandwidth,
+ self.param_system.frequency,
+ )
else:
acs = self.param_system.adjacent_ch_selectivity
- weights = np.ones(self.parameters.imt.ue_k)
+ weights = np.ones(self.parameters.imt.ue.k)
- interference_ue = self.ue.tx_power[ue] - self.coupling_loss_imt_system[ue]
- rx_interference += np.sum(weights*np.power(10, 0.1*interference_ue)) / 10**(acs/10.)
+ interference_ue = self.ue.tx_power[ue] - \
+ self.coupling_loss_imt_system[ue, sys_active]
+ rx_interference += np.sum(
+ weights * np.power(
+ 10,
+ 0.1 * interference_ue,
+ ),
+ ) / 10**(acs / 10.)
if self.adjacent_channel:
- # The unwanted emission is calculated in terms of TRP (after
- # antenna). In SHARC implementation, ohmic losses are already
- # included in coupling loss. Then, care has to be taken;
- # otherwise ohmic loss will be included twice.
- oob_power = self.ue.spectral_mask.power_calc(self.param_system.frequency,self.system.bandwidth)\
- - self.ue_power_diff[ue] \
- + self.parameters.imt.ue_ohmic_loss
- oob_interference_array = oob_power - self.coupling_loss_imt_system_adjacent[ue] \
- + 10*np.log10((self.param_system.bandwidth - self.overlapping_bandwidth)/
- self.param_system.bandwidth)
- rx_interference += np.sum(np.power(10,0.1*oob_interference_array))
-
- self.system.rx_interference = 10*np.log10(rx_interference)
+ # The unwanted emission is calculated in terms of TRP (after
+ # antenna). In SHARC implementation, ohmic losses are already
+ # included in coupling loss. Then, care has to be taken;
+ # otherwise ohmic loss will be included twice.
+ oob_power = self.ue.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth)\
+ - self.ue_power_diff[ue] \
+ + self.parameters.imt.ue.ohmic_loss
+ oob_interference_array = oob_power - self.coupling_loss_imt_system_adjacent[ue, sys_active] \
+ + 10 * np.log10(
+ (self.param_system.bandwidth - self.overlapping_bandwidth) /
+ self.param_system.bandwidth)
+ rx_interference += np.sum(
+ np.power(10, 0.1 * oob_interference_array,))
+
+ self.system.rx_interference = 10 * np.log10(rx_interference)
# calculate N
self.system.thermal_noise = \
- 10*np.log10(self.param_system.BOLTZMANN_CONSTANT* \
- self.system.noise_temperature*1e3) + \
- 10*math.log10(self.param_system.bandwidth * 1e6)
+ 10 * np.log10(
+ BOLTZMANN_CONSTANT *
+ self.system.noise_temperature * 1e3,
+ ) + \
+ 10 * math.log10(self.param_system.bandwidth * 1e6)
# calculate INR at the system
- self.system.inr = np.array([self.system.rx_interference - self.system.thermal_noise])
+ self.system.inr = np.array(
+ [self.system.rx_interference - self.system.thermal_noise],
+ )
# Calculate PFD at the system
- if self.system.station_type is StationType.RAS:
- self.system.pfd = 10*np.log10(10**(self.system.rx_interference/10)/self.system.antenna[0].effective_area)
-
+ # TODO: generalize this a bit more if needed
+ if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1:
+ self.system.pfd = 10 * \
+ np.log10(
+ 10**(self.system.rx_interference / 10) /
+ self.system.antenna[0].effective_area,
+ )
def collect_results(self, write_to_file: bool, snapshot_number: int):
if not self.parameters.imt.interfered_with and np.any(self.bs.active):
self.results.system_inr.extend(self.system.inr.tolist())
- self.results.system_ul_interf_power.extend([self.system.rx_interference])
- if self.system.station_type is StationType.RAS:
+ self.results.system_ul_interf_power.extend(
+ [self.system.rx_interference],
+ )
+ self.results.system_ul_interf_power_per_mhz.extend(
+ [self.system.rx_interference - 10 * math.log10(self.system.bandwidth)],
+ )
+ # TODO: generalize this a bit more if needed
+ if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1:
self.results.system_pfd.extend([self.system.pfd])
+ sys_active = np.where(self.system.active)[0]
bs_active = np.where(self.bs.active)[0]
for bs in bs_active:
ue = self.link[bs]
- self.results.imt_path_loss.extend(self.path_loss_imt[bs,ue])
- self.results.imt_coupling_loss.extend(self.coupling_loss_imt[bs,ue])
-
- self.results.imt_bs_antenna_gain.extend(self.imt_bs_antenna_gain[bs,ue])
- self.results.imt_ue_antenna_gain.extend(self.imt_ue_antenna_gain[bs,ue])
-
- tput = self.calculate_imt_tput(self.bs.sinr[bs],
- self.parameters.imt.ul_sinr_min,
- self.parameters.imt.ul_sinr_max,
- self.parameters.imt.ul_attenuation_factor)
+ self.results.imt_path_loss.extend(self.path_loss_imt[bs, ue])
+ self.results.imt_coupling_loss.extend(
+ self.coupling_loss_imt[bs, ue],
+ )
+
+ self.results.imt_bs_antenna_gain.extend(
+ self.imt_bs_antenna_gain[bs, ue],
+ )
+ self.results.imt_ue_antenna_gain.extend(
+ self.imt_ue_antenna_gain[bs, ue],
+ )
+
+ tput = self.calculate_imt_tput(
+ self.bs.sinr[bs],
+ self.parameters.imt.uplink.sinr_min,
+ self.parameters.imt.uplink.sinr_max,
+ self.parameters.imt.uplink.attenuation_factor,
+ )
self.results.imt_ul_tput.extend(tput.tolist())
if self.parameters.imt.interfered_with:
- tput_ext = self.calculate_imt_tput(self.bs.sinr_ext[bs],
- self.parameters.imt.ul_sinr_min,
- self.parameters.imt.ul_sinr_max,
- self.parameters.imt.ul_attenuation_factor)
+ tput_ext = self.calculate_imt_tput(
+ self.bs.sinr_ext[bs],
+ self.parameters.imt.uplink.sinr_min,
+ self.parameters.imt.uplink.sinr_max,
+ self.parameters.imt.uplink.attenuation_factor,
+ )
self.results.imt_ul_tput_ext.extend(tput_ext.tolist())
- self.results.imt_ul_sinr_ext.extend(self.bs.sinr_ext[bs].tolist())
+ self.results.imt_ul_sinr_ext.extend(
+ self.bs.sinr_ext[bs].tolist(),
+ )
self.results.imt_ul_inr.extend(self.bs.inr[bs].tolist())
- active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)]
- self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,active_beams])
- self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,active_beams])
- self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,active_beams])
+ active_beams = np.array([
+ i for i in range(
+ bs * self.parameters.imt.ue.k, (bs + 1) * self.parameters.imt.ue.k,
+ )
+ ])
+ self.results.system_imt_antenna_gain.extend(
+ self.system_imt_antenna_gain[np.ix_(sys_active, active_beams)].flatten(),
+ )
+ self.results.imt_system_antenna_gain.extend(
+ self.imt_system_antenna_gain[np.ix_(sys_active, active_beams)].flatten(),
+ )
+ self.results.imt_system_path_loss.extend(
+ self.imt_system_path_loss[np.ix_(sys_active, active_beams)].flatten(),
+ )
if self.param_system.channel_model == "HDFSS":
- self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,bs])
- self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,bs])
- else:
- self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,ue])
- self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,ue])
- self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,ue])
+ self.results.imt_system_build_entry_loss.extend(
+ self.imt_system_build_entry_loss[np.ix_(sys_active, active_beams)],
+ )
+ self.results.imt_system_diffraction_loss.extend(
+ self.imt_system_diffraction_loss[np.ix_(sys_active, active_beams)],
+ )
+ else: # IMT is the interferer
+ self.results.system_imt_antenna_gain.extend(
+ self.system_imt_antenna_gain[np.ix_(sys_active, ue)],
+ )
+ self.results.imt_system_antenna_gain.extend(
+ self.imt_system_antenna_gain[np.ix_(sys_active, ue)],
+ )
+ self.results.imt_system_path_loss.extend(
+ self.imt_system_path_loss[np.ix_(sys_active, ue)],
+ )
if self.param_system.channel_model == "HDFSS":
- self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,ue])
- self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,ue])
+ self.results.imt_system_build_entry_loss.extend(
+ self.imt_system_build_entry_loss[np.ix_(sys_active, ue)],
+ )
+ self.results.imt_system_diffraction_loss.extend(
+ self.imt_system_diffraction_loss[np.ix_(sys_active, ue)],
+ )
self.results.imt_ul_tx_power.extend(self.ue.tx_power[ue].tolist())
- imt_ul_tx_power_density = 10*np.log10(np.power(10, 0.1*self.ue.tx_power[ue])/(self.num_rb_per_ue*self.parameters.imt.rb_bandwidth*1e6))
- self.results.imt_ul_tx_power_density.extend(imt_ul_tx_power_density.tolist())
+ imt_ul_tx_power_density = 10 * np.log10(
+ np.power(10, 0.1 * self.ue.tx_power[ue]) / (
+ self.num_rb_per_ue * self.parameters.imt.rb_bandwidth * 1e6
+ ),
+ )
+ self.results.imt_ul_tx_power_density.extend(
+ imt_ul_tx_power_density.tolist(),
+ )
self.results.imt_ul_sinr.extend(self.bs.sinr[bs].tolist())
self.results.imt_ul_snr.extend(self.bs.snr[bs].tolist())
if write_to_file:
self.results.write_files(snapshot_number)
self.notify_observers(source=__name__, results=self.results)
-
-
-
diff --git a/sharc/station.py b/sharc/station.py
index e9ba8a06a..4e327f027 100644
--- a/sharc/station.py
+++ b/sharc/station.py
@@ -7,8 +7,9 @@
from sharc.support.enumerations import StationType
+
class Station(object):
-
+
def __init__(self):
self.id = -1
self.x = 0
@@ -33,29 +34,30 @@ def __init__(self):
self.sinr_ext = 0
self.inr = 0
self.station_type = StationType.NONE
-
-
+
def __eq__(self, other):
if isinstance(other, self.__class__):
- equal = (self.id == other.id and
+ equal = (
+ self.id == other.id and
self.x == other.x and
self.y == other.y and
- self.height == other.height)
+ self.height == other.height
+ )
return equal
else:
return NotImplemented
-
def __ne__(self, other):
if isinstance(other, self.__class__):
- not_equal = (self.id != other.id or
+ not_equal = (
+ self.id != other.id or
self.x != other.x or
self.y != other.y or
- self.height != other.height)
+ self.height != other.height
+ )
return not_equal
else:
return NotImplemented
-
-
+
def __hash__(self):
return hash(self.id, self.x, self.y, self.height)
diff --git a/sharc/station_factory.py b/sharc/station_factory.py
index 2e58dd7db..fa7d4424e 100644
--- a/sharc/station_factory.py
+++ b/sharc/station_factory.py
@@ -5,24 +5,33 @@
@author: edgar
"""
+from warnings import warn
import numpy as np
import sys
import math
from sharc.support.enumerations import StationType
from sharc.parameters.parameters import Parameters
-from sharc.parameters.parameters_imt import ParametersImt
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
-from sharc.parameters.parameters_eess_passive import ParametersEessPassive
+from sharc.parameters.imt.parameters_imt import ParametersImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.parameters_space_station import ParametersSpaceStation
+from sharc.parameters.parameters_eess_ss import ParametersEessSS
+from sharc.parameters.parameters_metsat_ss import ParametersMetSatSS
from sharc.parameters.parameters_fs import ParametersFs
from sharc.parameters.parameters_fss_ss import ParametersFssSs
from sharc.parameters.parameters_fss_es import ParametersFssEs
from sharc.parameters.parameters_haps import ParametersHaps
from sharc.parameters.parameters_rns import ParametersRns
from sharc.parameters.parameters_ras import ParametersRas
+from sharc.parameters.parameters_single_earth_station import ParametersSingleEarthStation
+from sharc.parameters.parameters_mss_ss import ParametersMssSs
+from sharc.parameters.parameters_mss_d2d import ParametersMssD2d
+from sharc.parameters.parameters_single_space_station import ParametersSingleSpaceStation
+from sharc.parameters.constants import EARTH_RADIUS
from sharc.station_manager import StationManager
from sharc.mask.spectral_mask_imt import SpectralMaskImt
from sharc.antenna.antenna import Antenna
+from sharc.antenna.antenna_factory import AntennaFactory
from sharc.antenna.antenna_fss_ss import AntennaFssSs
from sharc.antenna.antenna_omni import AntennaOmni
from sharc.antenna.antenna_f699 import AntennaF699
@@ -32,101 +41,172 @@
from sharc.antenna.antenna_rs1861_9a import AntennaRS1861_9A
from sharc.antenna.antenna_rs1861_9b import AntennaRS1861_9B
from sharc.antenna.antenna_rs1861_9c import AntennaRS1861_9C
+from sharc.antenna.antenna_rs2043 import AntennaRS2043
from sharc.antenna.antenna_s465 import AntennaS465
+from sharc.antenna.antenna_rra7_3 import AntennaReg_RR_A7_3
from sharc.antenna.antenna_modified_s465 import AntennaModifiedS465
from sharc.antenna.antenna_s580 import AntennaS580
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_sa509 import AntennaSA509
+from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Leo, AntennaS1528Taylor
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
+from sharc.antenna.antenna_multiple_transceiver import AntennaMultipleTransceiver
from sharc.topology.topology import Topology
+from sharc.topology.topology_ntn import TopologyNTN
from sharc.topology.topology_macrocell import TopologyMacrocell
+from sharc.topology.topology_imt_mss_dc import TopologyImtMssDc
from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp
+from sharc.mask.spectral_mask_mss import SpectralMaskMSS
+from sharc.satellite.ngso.orbit_model import OrbitModel
+from sharc.satellite.utils.sat_utils import calc_elevation, lla2ecef
+from sharc.support.sharc_geom import rotate_angles_based_on_new_nadir, GeometryConverter
+
+from sharc.parameters.constants import SPEED_OF_LIGHT
class StationFactory(object):
@staticmethod
- def generate_imt_base_stations(param: ParametersImt,
- param_ant: ParametersAntennaImt,
- topology: Topology,
- random_number_gen: np.random.RandomState):
- par = param_ant.get_antenna_parameters(StationType.IMT_BS)
+ def generate_imt_base_stations(
+ param: ParametersImt,
+ param_ant_bs: ParametersAntennaImt,
+ topology: Topology,
+ random_number_gen: np.random.RandomState,
+ ):
+ param_ant = param_ant_bs.get_antenna_parameters()
num_bs = topology.num_base_stations
imt_base_stations = StationManager(num_bs)
imt_base_stations.station_type = StationType.IMT_BS
- # now we set the coordinates
- imt_base_stations.x = topology.x
- imt_base_stations.y = topology.y
- imt_base_stations.azimuth = topology.azimuth
- imt_base_stations.elevation = -par.downtilt*np.ones(num_bs)
- if param.topology == 'INDOOR':
+ if param.topology.type == "NTN":
+ imt_base_stations.x = topology.space_station_x * np.ones(num_bs)
+ imt_base_stations.y = topology.space_station_y * np.ones(num_bs)
+ imt_base_stations.z = topology.space_station_z * np.ones(num_bs)
+ imt_base_stations.height = imt_base_stations.z
+ imt_base_stations.elevation = topology.elevation
+ imt_base_stations.is_space_station = True
+ elif param.topology.type == "MSS_DC":
+ imt_base_stations.x = topology.space_station_x * np.ones(num_bs)
+ imt_base_stations.y = topology.space_station_y * np.ones(num_bs)
+ imt_base_stations.z = topology.space_station_z * np.ones(num_bs)
imt_base_stations.height = topology.height
+ imt_base_stations.elevation = topology.elevation
+ imt_base_stations.is_space_station = True
else:
- imt_base_stations.height = param.bs_height*np.ones(num_bs)
-
- imt_base_stations.active = random_number_gen.rand(num_bs) < param.bs_load_probability
- imt_base_stations.tx_power = param.bs_conducted_power*np.ones(num_bs)
- imt_base_stations.rx_power = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.rx_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.ext_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.total_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
-
- imt_base_stations.snr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.sinr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.sinr_ext = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
- imt_base_stations.inr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)])
+ imt_base_stations.x = topology.x
+ imt_base_stations.y = topology.y
+ imt_base_stations.z = topology.z + param.bs.height
+ imt_base_stations.elevation = -param_ant.downtilt * np.ones(num_bs)
+ if param.topology.type == 'INDOOR':
+ imt_base_stations.height = topology.height
+ else:
+ imt_base_stations.height = param.bs.height * np.ones(num_bs)
- imt_base_stations.antenna = np.empty(num_bs, dtype=AntennaBeamformingImt)
+ imt_base_stations.azimuth = topology.azimuth
+ imt_base_stations.active = random_number_gen.rand(
+ num_bs,
+ ) < param.bs.load_probability
+ imt_base_stations.tx_power = param.bs.conducted_power * np.ones(num_bs)
+ imt_base_stations.rx_power = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.rx_interference = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.ext_interference = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.total_interference = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+
+ imt_base_stations.snr = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.sinr = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.sinr_ext = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+ imt_base_stations.inr = dict(
+ [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)],
+ )
+
+ imt_base_stations.antenna = np.empty(
+ num_bs, dtype=Antenna,
+ )
for i in range(num_bs):
imt_base_stations.antenna[i] = \
- AntennaBeamformingImt(par, imt_base_stations.azimuth[i],\
- imt_base_stations.elevation[i])
+ AntennaFactory.create_antenna(
+ param.bs.antenna, imt_base_stations.azimuth[i],
+ imt_base_stations.elevation[i],)
- #imt_base_stations.antenna = [AntennaOmni(0) for bs in range(num_bs)]
- imt_base_stations.bandwidth = param.bandwidth*np.ones(num_bs)
- imt_base_stations.center_freq = param.frequency*np.ones(num_bs)
- imt_base_stations.noise_figure = param.bs_noise_figure*np.ones(num_bs)
- imt_base_stations.thermal_noise = -500*np.ones(num_bs)
+ # imt_base_stations.antenna = [AntennaOmni(0) for bs in range(num_bs)]
+ imt_base_stations.bandwidth = param.bandwidth * np.ones(num_bs)
+ imt_base_stations.center_freq = param.frequency * np.ones(num_bs)
+ imt_base_stations.noise_figure = param.bs.noise_figure * \
+ np.ones(num_bs)
+ imt_base_stations.thermal_noise = -500 * np.ones(num_bs)
if param.spectral_mask == "IMT-2020":
- imt_base_stations.spectral_mask = SpectralMaskImt(StationType.IMT_BS,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions,
- scenario = param.topology)
+ imt_base_stations.spectral_mask = SpectralMaskImt(
+ StationType.IMT_BS,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ scenario=param.topology.type,
+ )
elif param.spectral_mask == "3GPP E-UTRA":
- imt_base_stations.spectral_mask = SpectralMask3Gpp(StationType.IMT_BS,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions)
-
- if param.topology == 'MACROCELL' or param.topology == 'HOTSPOT':
- imt_base_stations.intesite_dist = param.intersite_distance
+ imt_base_stations.spectral_mask = SpectralMask3Gpp(
+ StationType.IMT_BS,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ )
+
+ if param.topology.type == 'MACROCELL':
+ imt_base_stations.intersite_dist = param.topology.macrocell.intersite_distance
+ elif param.topology.type == 'HOTSPOT':
+ imt_base_stations.intersite_dist = param.topology.hotspot.intersite_distance
return imt_base_stations
@staticmethod
- def generate_imt_ue(param: ParametersImt,
- param_ant: ParametersAntennaImt,
- topology: Topology,
- random_number_gen: np.random.RandomState)-> StationManager:
-
- if param.topology == "INDOOR":
- return StationFactory.generate_imt_ue_indoor(param, param_ant, random_number_gen, topology)
+ def generate_imt_ue(
+ param: ParametersImt,
+ ue_param_ant: ParametersAntennaImt,
+ topology: Topology,
+ random_number_gen: np.random.RandomState,
+ ) -> StationManager:
+
+ if param.topology.type == "INDOOR":
+ return StationFactory.generate_imt_ue_indoor(param, ue_param_ant, random_number_gen, topology)
else:
- return StationFactory.generate_imt_ue_outdoor(param, param_ant, random_number_gen, topology)
+ return StationFactory.generate_imt_ue_outdoor(param, ue_param_ant, random_number_gen, topology)
+ @staticmethod
+ def generate_ras_station(
+ param: ParametersRas,
+ random_number_gen: np.random.RandomState,
+ topology: Topology,
+ ) -> StationManager:
+ return StationFactory.generate_single_earth_station(
+ param, random_number_gen,
+ StationType.RAS, topology
+ )
@staticmethod
- def generate_imt_ue_outdoor(param: ParametersImt,
- param_ant: ParametersAntennaImt,
- random_number_gen: np.random.RandomState,
- topology: Topology) -> StationManager:
+ def generate_imt_ue_outdoor(
+ param: ParametersImt,
+ ue_param_ant: ParametersAntennaImt,
+ random_number_gen: np.random.RandomState,
+ topology: Topology,
+ ) -> StationManager:
num_bs = topology.num_base_stations
- num_ue_per_bs = param.ue_k*param.ue_k_m
+ num_ue_per_bs = param.ue.k * param.ue.k_m
num_ue = num_bs * num_ue_per_bs
@@ -135,35 +215,58 @@ def generate_imt_ue_outdoor(param: ParametersImt,
ue_x = list()
ue_y = list()
+ ue_z = list()
+
+ imt_ue.height = param.ue.height * np.ones(num_ue)
+
+ # TODO: Sanitaze the azimuth_range parameter
+ azimuth_range = param.ue.azimuth_range
+ if (not isinstance(azimuth_range, tuple)) or len(azimuth_range) != 2:
+ raise ValueError("Invalid type or length for parameter azimuth_range")
# Calculate UE pointing
- azimuth_range = (-60, 60)
- azimuth = (azimuth_range[1] - azimuth_range[0])*random_number_gen.random_sample(num_ue) + azimuth_range[0]
+ azimuth = (azimuth_range[1] - azimuth_range[0]) * \
+ random_number_gen.random_sample(num_ue) + azimuth_range[0]
# Remove the randomness from azimuth and you will have a perfect pointing
elevation_range = (-90, 90)
- elevation = (elevation_range[1] - elevation_range[0])*random_number_gen.random_sample(num_ue) + \
- elevation_range[0]
-
- if param.ue_distribution_type.upper() == "UNIFORM":
-
- if not (type(topology) is TopologyMacrocell):
- sys.stderr.write("ERROR\nUniform UE distribution is currently supported only with Macrocell topology")
- sys.exit(1)
-
- [ue_x, ue_y, theta, distance] = StationFactory.get_random_position(num_ue, topology, random_number_gen,
- param.minimum_separation_distance_bs_ue,
- deterministic_cell=True)
- psi = np.degrees(np.arctan((param.bs_height - param.ue_height) / distance))
-
- imt_ue.azimuth = (azimuth + theta + np.pi/2)
+ elevation = (elevation_range[1] - elevation_range[0]) * random_number_gen.random_sample(num_ue) + \
+ elevation_range[0]
+
+ if param.ue.distribution_type.upper() == "UNIFORM" or \
+ param.ue.distribution_type.upper() == "CELL" or \
+ param.ue.distribution_type.upper() == "UNIFORM_IN_CELL":
+
+ central_cell = False
+ deterministic_cell = False
+ if param.ue.distribution_type.upper() != "UNIFORM":
+ deterministic_cell = True
+ if param.ue.distribution_type.upper() == "CELL":
+ central_cell = True
+ else:
+ if not (type(topology) is TopologyMacrocell):
+ sys.stderr.write(
+ "ERROR\nUniform UE distribution is currently supported only with Macrocell topology",
+ )
+ sys.exit(1)
+
+ [ue_x, ue_y, ue_z, theta, distance] = StationFactory.get_random_position(
+ num_ue, topology, random_number_gen,
+ param.minimum_separation_distance_bs_ue,
+ central_cell=central_cell,
+ deterministic_cell=deterministic_cell,
+ )
+ psi = np.degrees(
+ np.arctan((param.bs.height - param.ue.height) / distance),
+ )
+
+ imt_ue.azimuth = (azimuth + theta + np.pi / 2)
imt_ue.elevation = elevation + psi
-
- elif param.ue_distribution_type.upper() == "ANGLE_AND_DISTANCE":
+ elif param.ue.distribution_type.upper() == "ANGLE_AND_DISTANCE":
# The Rayleigh and Normal distribution parameters (mean, scale and cutoff)
# were agreed in TG 5/1 meeting (May 2017).
- if param.ue_distribution_distance.upper() == "RAYLEIGH":
+ if param.ue.distribution_distance.upper() == "RAYLEIGH":
# For the distance between UE and BS, it is desired that 99% of UE's
# are located inside the [soft] cell edge, i.e. Prob(d -angle_cutoff))[0][:num_ue]
angle = angle_n[idx]
- elif param.ue_distribution_azimuth.upper() == "UNIFORM":
- azimuth_range = (-60, 60)
+ elif param.ue.distribution_azimuth.upper() == "UNIFORM":
angle = (azimuth_range[1] - azimuth_range[0]) * random_number_gen.random_sample(num_ue) \
- + azimuth_range[0]
+ + azimuth_range[0]
else:
- sys.stderr.write("ERROR\nInvalid UE azimuth distribution: " + param.ue_distribution_distance)
+ sys.stderr.write(
+ "ERROR\nInvalid UE azimuth distribution: " + param.ue.distribution_distance,
+ )
sys.exit(1)
for bs in range(num_bs):
- idx = [i for i in range(bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs)]
+ idx = [
+ i for i in range(
+ bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs,
+ )
+ ]
# theta is the horizontal angle of the UE wrt the serving BS
theta = topology.azimuth[bs] + angle[idx]
# calculate UE position in x-y coordinates
- x = topology.x[bs] + radius[idx] * np.cos(np.radians(theta))
- y = topology.y[bs] + radius[idx] * np.sin(np.radians(theta))
+ x = radius[idx] * np.cos(np.radians(theta))
+ y = radius[idx] * np.sin(np.radians(theta))
+ z = np.zeros_like(x)
+ x, y, z = topology.transform_ue_xyz(
+ bs, x, y, z
+ )
ue_x.extend(x)
ue_y.extend(y)
+ ue_z.extend(z)
# calculate UE azimuth wrt serving BS
imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180) % 360
# calculate elevation angle
# psi is the vertical angle of the UE wrt the serving BS
- distance = np.sqrt((topology.x[bs] - x) ** 2 + (topology.y[bs] - y) ** 2)
- psi = np.degrees(np.arctan((param.bs_height - param.ue_height) / distance))
+ distance = np.sqrt(
+ (topology.x[bs] - x) ** 2 + (topology.y[bs] - y) ** 2,
+ )
+ psi = np.degrees(
+ np.arctan((param.bs.height - param.ue.height) / distance),
+ )
imt_ue.elevation[idx] = elevation[idx] + psi
+
else:
- sys.stderr.write("ERROR\nInvalid UE distribution type: " + param.ue_distribution_type)
+ sys.stderr.write(
+ "ERROR\nInvalid UE distribution type: " + param.ue.distribution_type,
+ )
sys.exit(1)
imt_ue.x = np.array(ue_x)
imt_ue.y = np.array(ue_y)
+ imt_ue.z = np.array(ue_z) + param.ue.height
imt_ue.active = np.zeros(num_ue, dtype=bool)
- imt_ue.height = param.ue_height*np.ones(num_ue)
- imt_ue.indoor = random_number_gen.random_sample(num_ue) <= (param.ue_indoor_percent/100)
- imt_ue.rx_interference = -500*np.ones(num_ue)
- imt_ue.ext_interference = -500*np.ones(num_ue)
+ imt_ue.indoor = random_number_gen.random_sample(
+ num_ue,
+ ) <= (param.ue.indoor_percent / 100)
+ imt_ue.rx_interference = -500 * np.ones(num_ue)
+ imt_ue.ext_interference = -500 * np.ones(num_ue)
# TODO: this piece of code works only for uplink
- par = param_ant.get_antenna_parameters(StationType.IMT_UE)
+ par = ue_param_ant.get_antenna_parameters()
for i in range(num_ue):
- imt_ue.antenna[i] = AntennaBeamformingImt(par, imt_ue.azimuth[i],
- imt_ue.elevation[i])
+ imt_ue.antenna[i] = AntennaFactory.create_antenna(
+ param.ue.antenna, imt_ue.azimuth[i],
+ imt_ue.elevation[i],
+ )
- #imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)]
- imt_ue.bandwidth = param.bandwidth*np.ones(num_ue)
- imt_ue.center_freq = param.frequency*np.ones(num_ue)
- imt_ue.noise_figure = param.ue_noise_figure*np.ones(num_ue)
+ # imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)]
+ imt_ue.bandwidth = param.bandwidth * np.ones(num_ue)
+ imt_ue.center_freq = param.frequency * np.ones(num_ue)
+ imt_ue.noise_figure = param.ue.noise_figure * np.ones(num_ue)
if param.spectral_mask == "IMT-2020":
- imt_ue.spectral_mask = SpectralMaskImt(StationType.IMT_UE,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions,
- scenario = "OUTDOOR")
+ imt_ue.spectral_mask = SpectralMaskImt(
+ StationType.IMT_UE,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ scenario="OUTDOOR",
+ )
elif param.spectral_mask == "3GPP E-UTRA":
- imt_ue.spectral_mask = SpectralMask3Gpp(StationType.IMT_UE,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions)
+ imt_ue.spectral_mask = SpectralMask3Gpp(
+ StationType.IMT_UE,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ )
imt_ue.spectral_mask.set_mask()
- if param.topology == 'MACROCELL' or param.topology == 'HOTSPOT':
- imt_ue.intersite_dist = param.intersite_distance
+ if param.topology.type == 'MACROCELL':
+ imt_ue.intersite_dist = param.topology.macrocell.intersite_distance
+ elif param.topology.type == 'HOTSPOT':
+ imt_ue.intersite_dist = param.topology.hotspot.intersite_distance
return imt_ue
-
@staticmethod
- def generate_imt_ue_indoor(param: ParametersImt,
- param_ant: ParametersAntennaImt,
- random_number_gen: np.random.RandomState,
- topology: Topology) -> StationManager:
+ def generate_imt_ue_indoor(
+ param: ParametersImt,
+ ue_param_ant: ParametersAntennaImt,
+ random_number_gen: np.random.RandomState,
+ topology: Topology,
+ ) -> StationManager:
num_bs = topology.num_base_stations
- num_ue_per_bs = param.ue_k*param.ue_k_m
- num_ue = num_bs*num_ue_per_bs
+ num_ue_per_bs = param.ue.k * param.ue.k_m
+ num_ue = num_bs * num_ue_per_bs
imt_ue = StationManager(num_ue)
imt_ue.station_type = StationType.IMT_UE
@@ -282,17 +421,27 @@ def generate_imt_ue_indoor(param: ParametersImt,
# Calculate UE pointing
azimuth_range = (-60, 60)
- azimuth = (azimuth_range[1] - azimuth_range[0])*random_number_gen.random_sample(num_ue) + azimuth_range[0]
+ azimuth = (azimuth_range[1] - azimuth_range[0]) * \
+ random_number_gen.random_sample(num_ue) + azimuth_range[0]
# Remove the randomness from azimuth and you will have a perfect pointing
- #azimuth = np.zeros(num_ue)
+ # azimuth = np.zeros(num_ue)
elevation_range = (-90, 90)
- elevation = (elevation_range[1] - elevation_range[0])*random_number_gen.random_sample(num_ue) + elevation_range[0]
+ elevation = (elevation_range[1] - elevation_range[0]) * \
+ random_number_gen.random_sample(num_ue) + elevation_range[0]
- delta_x = (topology.b_w/math.sqrt(topology.ue_indoor_percent) - topology.b_w)/2
- delta_y = (topology.b_d/math.sqrt(topology.ue_indoor_percent) - topology.b_d)/2
+ delta_x = (
+ topology.b_w / math.sqrt(topology.ue_indoor_percent) - topology.b_w
+ ) / 2
+ delta_y = (
+ topology.b_d / math.sqrt(topology.ue_indoor_percent) - topology.b_d
+ ) / 2
for bs in range(num_bs):
- idx = [i for i in range(bs*num_ue_per_bs, bs*num_ue_per_bs + num_ue_per_bs)]
+ idx = [
+ i for i in range(
+ bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs,
+ )
+ ]
# Right most cell of first floor
if bs % topology.num_cells == 0 and bs < topology.total_bs_level:
x_min = topology.x[bs] - topology.cell_radius - delta_x
@@ -308,43 +457,56 @@ def generate_imt_ue_indoor(param: ParametersImt,
# First floor
if bs < topology.total_bs_level:
- y_min = topology.y[bs] - topology.b_d/2 - delta_y
- y_max = topology.y[bs] + topology.b_d/2 + delta_y
+ y_min = topology.y[bs] - topology.b_d / 2 - delta_y
+ y_max = topology.y[bs] + topology.b_d / 2 + delta_y
# Higher floors
else:
- y_min = topology.y[bs] - topology.b_d/2
- y_max = topology.y[bs] + topology.b_d/2
-
- x = (x_max - x_min)*random_number_gen.random_sample(num_ue_per_bs) + x_min
- y = (y_max - y_min)*random_number_gen.random_sample(num_ue_per_bs) + y_min
- z = [topology.height[bs] - topology.b_h + param.ue_height for k in range(num_ue_per_bs)]
+ y_min = topology.y[bs] - topology.b_d / 2
+ y_max = topology.y[bs] + topology.b_d / 2
+
+ x = (x_max - x_min) * \
+ random_number_gen.random_sample(num_ue_per_bs) + x_min
+ y = (y_max - y_min) * \
+ random_number_gen.random_sample(num_ue_per_bs) + y_min
+ z = [
+ topology.height[bs] - topology.b_h +
+ param.ue.height for k in range(num_ue_per_bs)
+ ]
ue_x.extend(x)
ue_y.extend(y)
ue_z.extend(z)
# theta is the horizontal angle of the UE wrt the serving BS
- theta = np.degrees(np.arctan2(y - topology.y[bs], x - topology.x[bs]))
+ theta = np.degrees(
+ np.arctan2(
+ y - topology.y[bs], x - topology.x[bs],
+ ),
+ )
# calculate UE azimuth wrt serving BS
- imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180)%360
+ imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180) % 360
# calculate elevation angle
# psi is the vertical angle of the UE wrt the serving BS
- distance = np.sqrt((topology.x[bs] - x)**2 + (topology.y[bs] - y)**2)
- psi = np.degrees(np.arctan((param.bs_height - param.ue_height)/distance))
+ distance = np.sqrt(
+ (topology.x[bs] - x)**2 + (topology.y[bs] - y)**2,
+ )
+ psi = np.degrees(
+ np.arctan((param.bs.height - param.ue.height) / distance),
+ )
imt_ue.elevation[idx] = elevation[idx] + psi
# check if UE is indoor
if bs % topology.num_cells == 0:
out = (x < topology.x[bs] - topology.cell_radius) | \
- (y > topology.y[bs] + topology.b_d/2) | \
- (y < topology.y[bs] - topology.b_d/2)
+ (y > topology.y[bs] + topology.b_d / 2) | \
+ (y < topology.y[bs] - topology.b_d / 2)
elif bs % topology.num_cells == topology.num_cells - 1:
out = (x > topology.x[bs] + topology.cell_radius) | \
- (y > topology.y[bs] + topology.b_d/2) | \
- (y < topology.y[bs] - topology.b_d/2)
+ (y > topology.y[bs] + topology.b_d / 2) | \
+ (y < topology.y[bs] - topology.b_d / 2)
else:
- out = (y > topology.y[bs] + topology.b_d/2) | \
- (y < topology.y[bs] - topology.b_d/2)
+ out = (y > topology.y[bs] + topology.b_d / 2) | \
+ (y < topology.y[bs] - topology.b_d / 2)
imt_ue.indoor[idx] = ~ out
imt_ue.x = np.array(ue_x)
@@ -352,116 +514,250 @@ def generate_imt_ue_indoor(param: ParametersImt,
imt_ue.height = np.array(ue_z)
imt_ue.active = np.zeros(num_ue, dtype=bool)
- imt_ue.rx_interference = -500*np.ones(num_ue)
- imt_ue.ext_interference = -500*np.ones(num_ue)
+ imt_ue.rx_interference = -500 * np.ones(num_ue)
+ imt_ue.ext_interference = -500 * np.ones(num_ue)
# TODO: this piece of code works only for uplink
- par = param_ant.get_antenna_parameters(StationType.IMT_UE)
+ par = ue_param_ant.get_antenna_parameters()
for i in range(num_ue):
- imt_ue.antenna[i] = AntennaBeamformingImt(par, imt_ue.azimuth[i],
- imt_ue.elevation[i])
+ imt_ue.antenna[i] = AntennaBeamformingImt(
+ par, imt_ue.azimuth[i],
+ imt_ue.elevation[i], ue_param_ant.subarray
+ )
- #imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)]
- imt_ue.bandwidth = param.bandwidth*np.ones(num_ue)
- imt_ue.center_freq = param.frequency*np.ones(num_ue)
- imt_ue.noise_figure = param.ue_noise_figure*np.ones(num_ue)
+ # imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)]
+ imt_ue.bandwidth = param.bandwidth * np.ones(num_ue)
+ imt_ue.center_freq = param.frequency * np.ones(num_ue)
+ imt_ue.noise_figure = param.ue.noise_figure * np.ones(num_ue)
if param.spectral_mask == "IMT-2020":
- imt_ue.spectral_mask = SpectralMaskImt(StationType.IMT_UE,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions,
- scenario = "INDOOR")
+ imt_ue.spectral_mask = SpectralMaskImt(
+ StationType.IMT_UE,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ scenario="INDOOR",
+ )
elif param.spectral_mask == "3GPP E-UTRA":
- imt_ue.spectral_mask = SpectralMask3Gpp(StationType.IMT_UE,
- param.frequency,
- param.bandwidth,
- param.spurious_emissions)
+ imt_ue.spectral_mask = SpectralMask3Gpp(
+ StationType.IMT_UE,
+ param.frequency,
+ param.bandwidth,
+ param.spurious_emissions,
+ )
imt_ue.spectral_mask.set_mask()
return imt_ue
-
@staticmethod
- def generate_system(parameters: Parameters, topology: Topology, random_number_gen: np.random.RandomState ):
- if parameters.general.system == "EESS_PASSIVE":
- return StationFactory.generate_eess_passive_sensor(parameters.eess_passive)
- if parameters.general.system == "FSS_ES":
+ def generate_system(
+ parameters: Parameters,
+ topology: Topology,
+ random_number_gen: np.random.RandomState,
+ geometry_converter=GeometryConverter()
+ ):
+ if parameters.imt.topology.type == 'MACROCELL':
+ intersite_dist = parameters.imt.topology.macrocell.intersite_distance
+ elif parameters.imt.topology.type == 'HOTSPOT':
+ intersite_dist = parameters.imt.topology.hotspot.intersite_distance
+
+ if parameters.general.system == "METSAT_SS":
+ return StationFactory.generate_metsat_ss(parameters.metsat_ss)
+ elif parameters.general.system == "EESS_SS":
+ return StationFactory.generate_eess_space_station(parameters.eess_ss)
+ elif parameters.general.system == "FSS_ES":
return StationFactory.generate_fss_earth_station(parameters.fss_es, random_number_gen, topology)
+ elif parameters.general.system == "SINGLE_EARTH_STATION":
+ return StationFactory.generate_single_earth_station(parameters.single_earth_station, random_number_gen,
+ StationType.SINGLE_EARTH_STATION, topology)
+ elif parameters.general.system == "SINGLE_SPACE_STATION":
+ return StationFactory.generate_single_space_station(parameters.single_space_station)
+ elif parameters.general.system == "RAS":
+ return StationFactory.generate_ras_station(parameters.ras, random_number_gen, topology)
elif parameters.general.system == "FSS_SS":
return StationFactory.generate_fss_space_station(parameters.fss_ss)
elif parameters.general.system == "FS":
return StationFactory.generate_fs_station(parameters.fs)
elif parameters.general.system == "HAPS":
- return StationFactory.generate_haps(parameters.haps, parameters.imt.intersite_distance, random_number_gen)
+ return StationFactory.generate_haps(parameters.haps, intersite_dist, random_number_gen)
elif parameters.general.system == "RNS":
return StationFactory.generate_rns(parameters.rns, random_number_gen)
- elif parameters.general.system == "RAS":
- return StationFactory.generate_ras_station(parameters.ras)
+ elif parameters.general.system == "MSS_SS":
+ return StationFactory.generate_mss_ss(parameters.mss_ss)
+ elif parameters.general.system == "MSS_D2D":
+ return StationFactory.generate_mss_d2d(parameters.mss_d2d, random_number_gen, geometry_converter)
else:
- sys.stderr.write("ERROR\nInvalid system: " + parameters.general.system)
+ sys.stderr.write(
+ "ERROR\nInvalid system: " +
+ parameters.general.system,
+ )
sys.exit(1)
+ @staticmethod
+ def generate_single_space_station(param: ParametersSingleSpaceStation, simplify_dist_to_y=True):
+ """
+ Creates a single satellite based on parameters.
+ In case simplify_dist_to_y == True (default) satellite will be only on y axis
+ """
+ space_station = StationManager(1)
+ space_station.station_type = StationType.SINGLE_SPACE_STATION
+ space_station.is_space_station = True
+
+ # now we set the coordinates according to
+ # ITU-R P619-1, Attachment A
+
+ # calculate distances to the centre of the Earth
+ dist_sat_centre_earth_km = (EARTH_RADIUS + param.geometry.altitude) / 1000
+ dist_imt_centre_earth_km = (
+ EARTH_RADIUS + param.geometry.es_altitude
+ ) / 1000
+
+ # calculate Cartesian coordinates of satellite, with origin at centre of the Earth
+ sat_lat_rad = param.geometry.location.fixed.lat_deg * np.pi / 180.
+ imt_long_diff_rad = (param.geometry.location.fixed.long_deg - param.geometry.es_long_deg) * np.pi / 180.
+ x1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad)
+ y1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad)
+ z1 = dist_sat_centre_earth_km * np.sin(sat_lat_rad)
+
+ # rotate axis and calculate coordinates with origin at IMT system
+ imt_lat_rad = param.geometry.es_lat_deg * np.pi / 180.
+ space_station.x = np.array(
+ [x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)],
+ ) * 1000
+ space_station.y = np.array([y1]) * 1000
+ space_station.height = np.array([
+ (
+ z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad) -
+ dist_imt_centre_earth_km
+ ) * 1000,
+ ])
+
+ # putting on y axis
+ if simplify_dist_to_y:
+ space_station.y = np.sqrt(space_station.x * space_station.x + space_station.y * space_station.y)
+ space_station.x = np.zeros_like(space_station.x)
+
+ if param.geometry.azimuth.type == "POINTING_AT_IMT":
+ space_station.azimuth = np.rad2deg(np.arctan2(-space_station.y, -space_station.x))
+ elif param.geometry.azimuth.type == "FIXED":
+ space_station.azimuth = param.geometry.azimuth.fixed
+ else:
+ raise ValueError(f"Did not recognize azimuth type of {param.geometry.azimuth.type}")
+
+ if param.geometry.azimuth.type == "POINTING_AT_IMT":
+ gnd_elev = np.rad2deg(np.arctan2(
+ space_station.height,
+ np.sqrt(space_station.y * space_station.y + space_station.x * space_station.x)
+ ))
+ space_station.elevation = -gnd_elev
+ elif param.geometry.azimuth.type == "FIXED":
+ space_station.elevation = param.geometry.elevation.fixed
+ else:
+ raise ValueError(f"Did not recognize elevation type of {param.geometry.elevation.type}")
+
+ space_station.active = np.array([True])
+ space_station.tx_power = np.array(
+ [param.tx_power_density + 10 *
+ math.log10(param.bandwidth * 1e6) + 30],
+ )
+ space_station.rx_interference = -500
+
+ space_station.antenna = np.array([
+ AntennaFactory.create_antenna(param.antenna, space_station.azimuth[0],
+ space_station.elevation[0])
+ ])
+
+ space_station.z = space_station.height
+ space_station.bandwidth = param.bandwidth
+ space_station.noise_temperature = param.noise_temperature
+ space_station.thermal_noise = -500
+ space_station.total_interference = -500
+
+ return space_station
@staticmethod
def generate_fss_space_station(param: ParametersFssSs):
fss_space_station = StationManager(1)
fss_space_station.station_type = StationType.FSS_SS
+ fss_space_station.is_space_station = True
# now we set the coordinates according to
# ITU-R P619-1, Attachment A
# calculate distances to the centre of the Earth
- dist_sat_centre_earth_km = (param.EARTH_RADIUS + param.altitude)/1000
- dist_imt_centre_earth_km = (param.EARTH_RADIUS + param.imt_altitude)/1000
+ dist_sat_centre_earth_km = (EARTH_RADIUS + param.altitude) / 1000
+ dist_imt_centre_earth_km = (
+ EARTH_RADIUS + param.earth_station_alt_m
+ ) / 1000
# calculate Cartesian coordinates of satellite, with origin at centre of the Earth
sat_lat_rad = param.lat_deg * np.pi / 180.
- imt_long_diff_rad = param.imt_long_diff_deg * np.pi / 180.
- x1 = dist_sat_centre_earth_km * np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad)
- y1 = dist_sat_centre_earth_km * np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad)
+ imt_long_diff_rad = param.earth_station_long_diff_deg * np.pi / 180.
+ x1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad)
+ y1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad)
z1 = dist_sat_centre_earth_km * np.sin(sat_lat_rad)
# rotate axis and calculate coordinates with origin at IMT system
- imt_lat_rad = param.imt_lat_deg * np.pi / 180.
- fss_space_station.x = np.array([x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)]) * 1000
+ imt_lat_rad = param.earth_station_lat_deg * np.pi / 180.
+ fss_space_station.x = np.array(
+ [x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)],
+ ) * 1000
fss_space_station.y = np.array([y1]) * 1000
- fss_space_station.height = np.array([(z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad)
- - dist_imt_centre_earth_km) * 1000])
+ fss_space_station.height = np.array([
+ (
+ z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad) -
+ dist_imt_centre_earth_km
+ ) * 1000,
+ ])
+ fss_space_station.z = fss_space_station.height
- fss_space_station.azimuth = param.azimuth
- fss_space_station.elevation = param.elevation
+ fss_space_station.azimuth = np.array([param.azimuth])
+ fss_space_station.elevation = np.array([param.elevation])
fss_space_station.active = np.array([True])
- fss_space_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30])
- fss_space_station.rx_interference = -500
+ fss_space_station.tx_power = np.array(
+ [param.tx_power_density + 10 *
+ math.log10(param.bandwidth * 1e6) + 30],
+ )
+ fss_space_station.rx_interference = np.array([-500])
if param.antenna_pattern == "OMNI":
- fss_space_station.antenna = np.array([AntennaOmni(param.antenna_gain)])
+ fss_space_station.antenna = np.array(
+ [AntennaOmni(param.antenna_gain)],
+ )
elif param.antenna_pattern == "ITU-R S.672":
fss_space_station.antenna = np.array([AntennaS672(param)])
elif param.antenna_pattern == "ITU-R S.1528":
- fss_space_station.antenna = np.array([AntennaS1528(param)])
+ fss_space_station.antenna = np.array([AntennaS1528(param.antenna_s1528)])
elif param.antenna_pattern == "FSS_SS":
fss_space_station.antenna = np.array([AntennaFssSs(param)])
else:
- sys.stderr.write("ERROR\nInvalid FSS SS antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid FSS SS antenna pattern: " + param.antenna_pattern,
+ )
sys.exit(1)
- fss_space_station.bandwidth = param.bandwidth
- fss_space_station.noise_temperature = param.noise_temperature
- fss_space_station.thermal_noise = -500
- fss_space_station.total_interference = -500
+ fss_space_station.bandwidth = np.array([param.bandwidth])
+ fss_space_station.noise_temperature = np.array([param.noise_temperature])
+ fss_space_station.thermal_noise = np.array([-500])
+ fss_space_station.total_interference = np.array([-500])
return fss_space_station
-
@staticmethod
-
def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.random.RandomState, *args):
"""
+ @deprecated
+
+ Since this creates a Single Earth Station, you should use StationFactory.generate_single_earth_station instead.
+ This will be deleted in the future.
+ ----------------------------------
Generates FSS Earth Station.
Arguments:
@@ -469,7 +765,13 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran
random_number_gen: np.random.RandomState
topology (optional): Topology
"""
- if len(args): topology = args[0]
+ warn(
+ "This is deprecated, use StationFactory.generate_single_earth_station() instead; date=2024-10-11",
+ DeprecationWarning, stacklevel=2,
+ )
+
+ if len(args):
+ topology = args[0]
fss_earth_station = StationManager(1)
fss_earth_station.station_type = StationType.FSS_ES
@@ -478,49 +780,72 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran
fss_earth_station.x = np.array([param.x])
fss_earth_station.y = np.array([param.y])
elif param.location.upper() == "CELL":
- x, y, dummy1, dummy2 = StationFactory.get_random_position(1, topology, random_number_gen,
- param.min_dist_to_bs, True)
+ x, y, _, _ = StationFactory.get_random_position(
+ 1, topology, random_number_gen,
+ param.min_dist_to_bs, True,
+ )
fss_earth_station.x = np.array(x)
fss_earth_station.y = np.array(y)
elif param.location.upper() == "NETWORK":
- x, y, dummy1, dummy2 = StationFactory.get_random_position(1, topology, random_number_gen,
- param.min_dist_to_bs, False)
+ x, y, _, _ = StationFactory.get_random_position(
+ 1, topology, random_number_gen,
+ param.min_dist_to_bs, False,
+ )
fss_earth_station.x = np.array(x)
fss_earth_station.y = np.array(y)
elif param.location.upper() == "UNIFORM_DIST":
# FSS ES is randomly (uniform) created inside a circle of radius
# equal to param.max_dist_to_bs
if param.min_dist_to_bs < 0:
- sys.stderr.write("ERROR\nInvalid minimum distance from FSS ES to BS: {}".format(param.min_dist_to_bs))
+ sys.stderr.write(
+ "ERROR\nInvalid minimum distance from FSS ES to BS: {}".format(
+ param.min_dist_to_bs,
+ ),
+ )
sys.exit(1)
- while(True):
- dist_x = random_number_gen.uniform(-param.max_dist_to_bs, param.max_dist_to_bs)
- dist_y = random_number_gen.uniform(-param.max_dist_to_bs, param.max_dist_to_bs)
+ while (True):
+ dist_x = random_number_gen.uniform(
+ -param.max_dist_to_bs, param.max_dist_to_bs,
+ )
+ dist_y = random_number_gen.uniform(
+ -param.max_dist_to_bs, param.max_dist_to_bs,
+ )
radius = np.sqrt(dist_x**2 + dist_y**2)
if (radius > param.min_dist_to_bs) & (radius < param.max_dist_to_bs):
break
fss_earth_station.x[0] = dist_x
fss_earth_station.y[0] = dist_y
else:
- sys.stderr.write("ERROR\nFSS-ES location type {} not supported".format(param.location))
+ sys.stderr.write(
+ "ERROR\nFSS-ES location type {} not supported".format(
+ param.location),
+ )
sys.exit(1)
+ fss_earth_station.z = np.array([param.height])
fss_earth_station.height = np.array([param.height])
if param.azimuth.upper() == "RANDOM":
- fss_earth_station.azimuth = random_number_gen.uniform(-180., 180.)
+ fss_earth_station.azimuth = np.array([random_number_gen.uniform(-180., 180.)])
else:
- fss_earth_station.azimuth = float(param.azimuth)
+ fss_earth_station.azimuth = np.array([float(param.azimuth)])
- elevation = random_number_gen.uniform(param.elevation_min, param.elevation_max)
+ elevation = random_number_gen.uniform(
+ param.elevation_min, param.elevation_max,
+ )
fss_earth_station.elevation = np.array([elevation])
fss_earth_station.active = np.array([True])
- fss_earth_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30])
- fss_earth_station.rx_interference = -500
+ fss_earth_station.tx_power = np.array(
+ [param.tx_power_density + 10 *
+ math.log10(param.bandwidth * 1e6) + 30],
+ )
+ fss_earth_station.rx_interference = np.array([-500])
if param.antenna_pattern.upper() == "OMNI":
- fss_earth_station.antenna = np.array([AntennaOmni(param.antenna_gain)])
+ fss_earth_station.antenna = np.array(
+ [AntennaOmni(param.antenna_gain)],
+ )
elif param.antenna_pattern.upper() == "ITU-R S.1855":
fss_earth_station.antenna = np.array([AntennaS1855(param)])
elif param.antenna_pattern.upper() == "ITU-R S.465":
@@ -530,32 +855,205 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran
elif param.antenna_pattern.upper() == "ITU-R S.580":
fss_earth_station.antenna = np.array([AntennaS580(param)])
else:
- sys.stderr.write("ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern,
+ )
sys.exit(1)
- fss_earth_station.noise_temperature = param.noise_temperature
+ fss_earth_station.noise_temperature = np.array([param.noise_temperature])
fss_earth_station.bandwidth = np.array([param.bandwidth])
- fss_earth_station.noise_temperature = param.noise_temperature
- fss_earth_station.thermal_noise = -500
- fss_earth_station.total_interference = -500
+ fss_earth_station.noise_temperature = np.array([param.noise_temperature])
+ fss_earth_station.thermal_noise = np.array([-500])
+ fss_earth_station.total_interference = np.array([-500])
+ fss_earth_station.rx_interference = np.array([-500])
+ fss_earth_station.rx_power = np.array([-500])
return fss_earth_station
+ @staticmethod
+ def generate_single_earth_station(
+ param: ParametersSingleEarthStation, random_number_gen: np.random.RandomState,
+ station_type=StationType.SINGLE_EARTH_STATION, topology=None,
+ ):
+ """
+ Generates a Single Earth Station.
+
+ Arguments:
+ param: ParametersSingleEarthStation
+ random_number_gen: np.random.RandomState
+ topology (optional): Topology
+ """
+ single_earth_station = StationManager(1)
+ single_earth_station.station_type = StationType.SINGLE_EARTH_STATION
+
+ match param.geometry.location.type:
+ case "FIXED":
+ single_earth_station.x = np.array(
+ [param.geometry.location.fixed.x],
+ )
+ single_earth_station.y = np.array(
+ [param.geometry.location.fixed.y],
+ )
+ case "CELL":
+ x, y, _, _ = StationFactory.get_random_position(
+ 1, topology, random_number_gen,
+ param.geometry.location.cell.min_dist_to_bs, True,
+ )
+ single_earth_station.x = np.array(x)
+ single_earth_station.y = np.array(y)
+ case "NETWORK":
+ x, y, _, _ = StationFactory.get_random_position(
+ 1, topology, random_number_gen,
+ param.geometry.location.network.min_dist_to_bs, False,
+ )
+ single_earth_station.x = np.array(x)
+ single_earth_station.y = np.array(y)
+ case "UNIFORM_DIST":
+ # ES is randomly (uniform) created inside a circle of radius
+ # equal to param.max_dist_to_bs
+ if param.geometry.location.uniform_dist.min_dist_to_bs < 0:
+ sys.stderr.write(
+ "ERROR\nInvalid minimum distance from Single ES to BS: {}".format(
+ param.geometry.location.uniform_dist.min_dist_to_bs,
+ ),
+ )
+ sys.exit(1)
+ while (True):
+ dist_x = random_number_gen.uniform(
+ -param.geometry.location.uniform_dist.max_dist_to_bs,
+ param.geometry.location.uniform_dist.max_dist_to_bs,
+ )
+ dist_y = random_number_gen.uniform(
+ -param.geometry.location.uniform_dist.max_dist_to_bs,
+ param.geometry.location.uniform_dist.max_dist_to_bs,
+ )
+ radius = np.sqrt(dist_x**2 + dist_y**2)
+ if (radius > param.geometry.location.uniform_dist.min_dist_to_bs) & \
+ (radius < param.geometry.location.uniform_dist.max_dist_to_bs):
+ break
+ single_earth_station.x[0] = dist_x
+ single_earth_station.y[0] = dist_y
+ case _:
+ sys.stderr.write(
+ "ERROR\nSingle-ES location type {} not supported".format(
+ param.geometry.location.type),
+ )
+ sys.exit(1)
+
+ single_earth_station.z = np.array([param.geometry.height])
+ single_earth_station.height = np.array([param.geometry.height])
+
+ if param.geometry.azimuth.type == "UNIFORM_DIST":
+ if param.geometry.azimuth.uniform_dist.min < -180:
+ sys.stderr.write(
+ "ERROR\nInvalid minimum azimuth: {} < -180".format(
+ param.geometry.azimuth.uniform_dist.min),
+ )
+ sys.exit(1)
+ if param.geometry.azimuth.uniform_dist.max > 180:
+ sys.stderr.write(
+ "ERROR\nInvalid maximum azimuth: {} > 180".format(
+ param.geometry.azimuth.uniform_dist.max,
+ ),
+ )
+ sys.exit(1)
+ single_earth_station.azimuth = np.array([
+ random_number_gen.uniform(
+ param.geometry.azimuth.uniform_dist.min, param.geometry.azimuth.uniform_dist.max,
+ ),
+ ])
+ else:
+ single_earth_station.azimuth = np.array(
+ [param.geometry.azimuth.fixed],
+ )
+
+ if param.geometry.elevation.type == "UNIFORM_DIST":
+ single_earth_station.elevation = np.array([
+ random_number_gen.uniform(
+ param.geometry.elevation.uniform_dist.min, param.geometry.elevation.uniform_dist.max,
+ ),
+ ])
+ else:
+ single_earth_station.elevation = np.array(
+ [param.geometry.elevation.fixed],
+ )
+
+ match param.antenna.pattern:
+ case "OMNI":
+ single_earth_station.antenna = np.array(
+ [AntennaOmni(param.antenna.gain)],
+ )
+ case "ITU-R S.465":
+ single_earth_station.antenna = np.array(
+ [AntennaS465(param.antenna.itu_r_s_465)],
+ )
+ case "ITU-R Reg. RR. Appendice 7 Annex 3":
+ single_earth_station.antenna = np.array(
+ [AntennaReg_RR_A7_3(param.antenna.itu_reg_rr_a7_3)],
+ )
+ case "ITU-R S.1855":
+ single_earth_station.antenna = np.array(
+ [AntennaS1855(param.antenna.itu_r_s_1855)],
+ )
+ case "MODIFIED ITU-R S.465":
+ single_earth_station.antenna = np.array(
+ [AntennaModifiedS465(param.antenna.itu_r_s_465_modified)],
+ )
+ case "ITU-R S.580":
+ single_earth_station.antenna = np.array(
+ [AntennaS580(param.antenna.itu_r_s_580)],
+ )
+ case _:
+ sys.stderr.write(
+ "ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern,
+ )
+ sys.exit(1)
+
+ single_earth_station.active = np.array([True])
+ single_earth_station.bandwidth = np.array([param.bandwidth])
+
+ single_earth_station.tx_power = np.array(
+ [param.tx_power_density + 10 *
+ math.log10(param.bandwidth * 1e6) + 30],
+ )
+
+ single_earth_station.noise_temperature = np.array([param.noise_temperature])
+
+ # TODO: check why this would not be set on the StationManager() constructor itself?
+ single_earth_station.rx_interference = np.array([-500])
+ single_earth_station.thermal_noise = np.array([-500])
+ single_earth_station.total_interference = np.array([-500])
+
+ return single_earth_station
@staticmethod
def generate_fs_station(param: ParametersFs):
+ """
+ @deprecated
+ Since this creates a Single Earth Station, you should use StationFactory.generate_single_earth_station instead.
+ This will be deleted in the future
+ """
+ warn(
+ "This is deprecated, use StationFactory.generate_single_earth_station() instead; date=2024-10-11",
+ DeprecationWarning, stacklevel=2,
+ )
+
fs_station = StationManager(1)
fs_station.station_type = StationType.FS
fs_station.x = np.array([param.x])
fs_station.y = np.array([param.y])
+ fs_station.z = np.array([param.height])
fs_station.height = np.array([param.height])
fs_station.azimuth = np.array([param.azimuth])
fs_station.elevation = np.array([param.elevation])
fs_station.active = np.array([True])
- fs_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30])
+ fs_station.tx_power = np.array(
+ [param.tx_power_density + 10 *
+ math.log10(param.bandwidth * 1e6) + 30],
+ )
fs_station.rx_interference = -500
if param.antenna_pattern == "OMNI":
@@ -563,7 +1061,9 @@ def generate_fs_station(param: ParametersFs):
elif param.antenna_pattern == "ITU-R F.699":
fs_station.antenna = np.array([AntennaF699(param)])
else:
- sys.stderr.write("ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern,
+ )
sys.exit(1)
fs_station.noise_temperature = param.noise_temperature
@@ -571,12 +1071,12 @@ def generate_fs_station(param: ParametersFs):
return fs_station
-
@staticmethod
- def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_gen: np.random.RandomState()):
+ def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_gen: np.random.RandomState):
num_haps = 1
haps = StationManager(num_haps)
haps.station_type = StationType.HAPS
+ haps.is_space_station = True
# d = intersite_distance
# h = (d/3)*math.sqrt(3)/2
@@ -584,15 +1084,16 @@ def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_
# haps.y = np.array([0, 9*h, 15*h, 6*h, -9*h, -15*h, -6*h])
haps.x = np.array([0])
haps.y = np.array([0])
+ haps.z = param.altitude * np.ones(num_haps)
- haps.height = param.altitude * np.ones(num_haps)
+ haps.height = haps.z
- elev_max = 68.19 # corresponds to 50 km radius and 20 km altitude
+ elev_max = 68.19 # corresponds to 50 km radius and 20 km altitude
haps.azimuth = 360 * random_number_gen.random_sample(num_haps)
haps.elevation = ((270 + elev_max) - (270 - elev_max)) * random_number_gen.random_sample(num_haps) + \
(270 - elev_max)
- haps.active = np.ones(num_haps, dtype = bool)
+ haps.active = np.ones(num_haps, dtype=bool)
haps.antenna = np.empty(num_haps, dtype=Antenna)
@@ -603,39 +1104,49 @@ def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_
for i in range(num_haps):
haps.antenna[i] = AntennaF1891(param)
else:
- sys.stderr.write("ERROR\nInvalid HAPS (airbone) antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid HAPS (airbone) antenna pattern: " +
+ param.antenna_pattern,
+ )
sys.exit(1)
haps.bandwidth = np.array([param.bandwidth])
return haps
-
@staticmethod
- def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState()):
+ def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState):
num_rns = 1
rns = StationManager(num_rns)
rns.station_type = StationType.RNS
+ rns.is_space_station = True
rns.x = np.array([param.x])
rns.y = np.array([param.y])
+ rns.z = np.array([param.altitude])
rns.height = np.array([param.altitude])
# minimum and maximum values for azimuth and elevation
azimuth = np.array([-30, 30])
elevation = np.array([-30, 5])
- rns.azimuth = 90 + (azimuth[1] - azimuth[0]) * random_number_gen.random_sample(num_rns) + azimuth[0]
- rns.elevation = (elevation[1] - elevation[0]) * random_number_gen.random_sample(num_rns) + elevation[0]
+ rns.azimuth = 90 + (azimuth[1] - azimuth[0]) * \
+ random_number_gen.random_sample(num_rns) + azimuth[0]
+ rns.elevation = (elevation[1] - elevation[0]) * \
+ random_number_gen.random_sample(num_rns) + elevation[0]
- rns.active = np.ones(num_rns, dtype = bool)
+ rns.active = np.ones(num_rns, dtype=bool)
if param.antenna_pattern == "OMNI":
rns.antenna = np.array([AntennaOmni(param.antenna_gain)])
elif param.antenna_pattern == "ITU-R M.1466":
- rns.antenna = np.array([AntennaM1466(param.antenna_gain, rns.azimuth, rns.elevation)])
+ rns.antenna = np.array(
+ [AntennaM1466(param.antenna_gain, rns.azimuth, rns.elevation)],
+ )
else:
- sys.stderr.write("ERROR\nInvalid RNS antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid RNS antenna pattern: " + param.antenna_pattern,
+ )
sys.exit(1)
rns.bandwidth = np.array([param.bandwidth])
@@ -646,231 +1157,535 @@ def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState(
return rns
-
@staticmethod
- def generate_ras_station(param: ParametersRas):
- ras_station = StationManager(1)
- ras_station.station_type = StationType.RAS
-
- ras_station.x = np.array([param.x])
- ras_station.y = np.array([param.y])
- ras_station.height = np.array([param.height])
-
- ras_station.azimuth = np.array([param.azimuth])
- ras_station.elevation = np.array([param.elevation])
-
- ras_station.active = np.array([True])
- ras_station.rx_interference = -500
-
- if param.antenna_pattern == "OMNI":
- ras_station.antenna = np.array([AntennaOmni(param.antenna_gain)])
- ras_station.antenna[0].effective_area = param.SPEED_OF_LIGHT**2/(4*np.pi*(param.frequency*1e6)**2)
- elif param.antenna_pattern == "ITU-R SA.509":
- ras_station.antenna = np.array([AntennaSA509(param)])
- else:
- sys.stderr.write("ERROR\nInvalid RAS antenna pattern: " + param.antenna_pattern)
- sys.exit(1)
-
- ras_station.noise_temperature = np.array(param.antenna_noise_temperature + \
- param.receiver_noise_temperature)
- ras_station.bandwidth = np.array(param.bandwidth)
-
- return ras_station
+ def generate_eess_space_station(param: ParametersEessSS):
+ if param.distribution_enable:
+ if param.distribution_type == "UNIFORM":
+ param.nadir_angle = np.random.uniform(
+ param.nadir_angle_distribution[0],
+ param.nadir_angle_distribution[1],
+ )
+ return StationFactory.generate_space_station(param, StationType.EESS_SS)
@staticmethod
- def generate_eess_passive_sensor(param: ParametersEessPassive):
- eess_passive_sensor = StationManager(1)
- eess_passive_sensor.station_type = StationType.EESS_PASSIVE
+ def generate_metsat_ss(param: ParametersMetSatSS):
+ return StationFactory.generate_space_station(param, StationType.METSAT_SS)
- # incidence angle according to Rec. ITU-R RS.1861-0
- incidence_angle = math.degrees(math.asin(
- math.sin(math.radians(param.nadir_angle))*(1 + (param.altitude/param.EARTH_RADIUS))))
+ @staticmethod
+ def generate_space_station(param: ParametersSpaceStation, station_type: StationType):
+ # this method uses off-nadir angle and altitude to infer the entire geometry
+ # TODO: make this usable on more space station cases (initially only works for metsat and eess)
+ space_station = StationManager(1)
+ space_station.is_space_station = True
+ space_station.station_type = station_type
+ # assert param.station_type is not None
+
+ incidence_angle = math.degrees(
+ math.asin(
+ math.sin(math.radians(param.nadir_angle)) *
+ (1 + (param.altitude / EARTH_RADIUS)),
+ ),
+ )
# distance to field of view centre according to Rec. ITU-R RS.1861-0
- distance = param.EARTH_RADIUS * \
- math.sin(math.radians(incidence_angle - param.nadir_angle)) / \
- math.sin(math.radians(param.nadir_angle))
+ distance = EARTH_RADIUS * \
+ math.sin(math.radians(incidence_angle - param.nadir_angle)) / \
+ math.sin(math.radians(param.nadir_angle))
# Elevation at ground (centre of the footprint)
theta_grd_elev = 90 - incidence_angle
- eess_passive_sensor.x = np.array([0])
- eess_passive_sensor.y = np.array([distance * math.cos(math.radians(theta_grd_elev))])
- eess_passive_sensor.height = np.array([distance * math.sin(math.radians(theta_grd_elev))])
+ space_station.x = np.array([0])
+ space_station.y = np.array(
+ [distance * math.cos(math.radians(theta_grd_elev))],
+ )
+ space_station.height = np.array(
+ [distance * math.sin(math.radians(theta_grd_elev))],
+ )
+ space_station.z = space_station.height
# Elevation and azimuth at sensor wrt centre of the footprint
# It is assumed the sensor is at y-axis, hence azimuth is 270 deg
- eess_passive_sensor.azimuth = 270
- eess_passive_sensor.elevation = -theta_grd_elev
+ space_station.azimuth = np.array([270])
+ space_station.elevation = np.array([-theta_grd_elev])
- eess_passive_sensor.active = np.array([True])
- eess_passive_sensor.rx_interference = -500
+ space_station.active = np.array([True])
+ space_station.rx_interference = np.array([-500])
if param.antenna_pattern == "OMNI":
- eess_passive_sensor.antenna = np.array([AntennaOmni(param.antenna_gain)])
+ space_station.antenna = np.array([AntennaOmni(param.antenna_gain)])
elif param.antenna_pattern == "ITU-R RS.1813":
- eess_passive_sensor.antenna = np.array([AntennaRS1813(param)])
+ space_station.antenna = np.array([AntennaRS1813(param)])
elif param.antenna_pattern == "ITU-R RS.1861 9a":
- eess_passive_sensor.antenna = np.array([AntennaRS1861_9A(param)])
+ space_station.antenna = np.array([AntennaRS1861_9A(param)])
elif param.antenna_pattern == "ITU-R RS.1861 9b":
- eess_passive_sensor.antenna = np.array([AntennaRS1861_9B(param)])
+ space_station.antenna = np.array([AntennaRS1861_9B(param)])
elif param.antenna_pattern == "ITU-R RS.1861 9c":
- eess_passive_sensor.antenna = np.array([AntennaRS1861_9C()])
+ space_station.antenna = np.array([AntennaRS1861_9C()])
+ elif param.antenna_pattern == "ITU-R RS.2043":
+ space_station.antenna = np.array([AntennaRS2043()])
+ elif param.antenna_pattern == "ITU-R S.672":
+ space_station.antenna = np.array([AntennaS672(param)])
else:
- sys.stderr.write("ERROR\nInvalid EESS PASSIVE antenna pattern: " + param.antenna_pattern)
+ sys.stderr.write(
+ "ERROR\nInvalid EESS PASSIVE antenna pattern: " + param.antenna_pattern,
+ )
sys.exit(1)
- eess_passive_sensor.bandwidth = param.bandwidth
- # Noise temperature is not an input parameter for EESS passive.
+ space_station.bandwidth = np.array([param.bandwidth])
+ # Noise temperature is not an input parameter for yet used systems.
# It is included here to calculate the useless I/N values
- eess_passive_sensor.noise_temperature = 250
- eess_passive_sensor.thermal_noise = -500
- eess_passive_sensor.total_interference = -500
+ space_station.noise_temperature = np.array([500.0])
- return eess_passive_sensor
+ return space_station
+ @staticmethod
+ def generate_mss_ss(param_mss: ParametersMssSs):
+ # We borrow the TopologyNTN geometry as it's the same for MSS_SS
+ ntn_topology = TopologyNTN(param_mss.intersite_distance,
+ param_mss.cell_radius,
+ param_mss.altitude,
+ param_mss.azimuth,
+ param_mss.elevation,
+ param_mss.num_sectors)
+ ntn_topology.calculate_coordinates()
+
+ num_bs = ntn_topology.num_base_stations
+ mss_ss = StationManager(n=num_bs)
+ mss_ss.station_type = StationType.MSS_SS
+ mss_ss.x = ntn_topology.space_station_x * np.ones(num_bs) + param_mss.x
+ mss_ss.y = ntn_topology.space_station_y * np.ones(num_bs) + param_mss.y
+ mss_ss.z = ntn_topology.space_station_z * np.ones(num_bs)
+ mss_ss.height = ntn_topology.space_station_z * np.ones(num_bs)
+ mss_ss.elevation = ntn_topology.elevation
+ mss_ss.is_space_station = True
+ mss_ss.azimuth = ntn_topology.azimuth
+ mss_ss.active = np.ones(num_bs, dtype=int)
+ mss_ss.tx_power = np.ones(num_bs, dtype=int) * param_mss.tx_power_density + 10 * np.log10(param_mss.bandwidth * 10**6)
+ mss_ss.antenna = np.empty(num_bs, dtype=AntennaS1528Leo)
+
+ for i in range(num_bs):
+ if param_mss.antenna_pattern == "ITU-R-S.1528-LEO":
+ mss_ss.antenna[i] = AntennaS1528Leo(param_mss.antenna_s1528)
+ elif param_mss.antenna_pattern == "ITU-R-S.1528-Section1.2":
+ mss_ss.antenna[i] = AntennaS1528(param_mss.antenna_s1528)
+ elif param_mss.antenna_pattern == "ITU-R-S.1528-Taylor":
+ mss_ss.antenna[i] = AntennaS1528Taylor(param_mss.antenna_s1528)
+ else:
+ raise ValueError("generate_mss_ss: Invalid antenna type: {param_mss.antenna_pattern}")
+
+ if param_mss.spectral_mask == "IMT-2020":
+ mss_ss.spectral_mask = SpectralMaskImt(StationType.IMT_BS,
+ param_mss.frequency,
+ param_mss.bandwidth,
+ param_mss.spurious_emissions,
+ scenario="OUTDOOR")
+ elif param_mss.spectral_mask == "3GPP E-UTRA":
+ mss_ss.spectral_mask = SpectralMask3Gpp(StationType.IMT_BS,
+ param_mss.frequency,
+ param_mss.bandwidth,
+ param_mss.spurious_emissions,
+ scenario="OUTDOOR")
+ elif params.spectral_mask == "MSS":
+ mss_ss.spectral_mask = SpectralMaskMSS(params.frequency,
+ params.bandwidth,
+ params.spurious_emissions)
+ else:
+ raise ValueError(f"Invalid or not implemented spectral mask - {param_mss.spectral_mask}")
+ mss_ss.spectral_mask.set_mask(param_mss.tx_power_density + 10 * np.log10(param_mss.bandwidth * 1e6))
+ return mss_ss
+
+ def generate_mss_d2d(
+ params: ParametersMssD2d,
+ random_number_gen: np.random.RandomState,
+ geometry_converter: GeometryConverter,
+ also_generate_inactive: bool = False
+ ):
+ """
+ Generate the MSS D2D constellation with support for multiple orbits and base station visibility.
+
+ Parameters
+ ----------
+ params : ParametersMssD2d
+ Parameters for the MSS D2D system, including orbits and antenna configuration.
+ random_number_gen : np.random.RandomState
+ Random number generator for generating satellite positions.
+ geometry_converter : GeometryConverter
+ A converter that has already set a reference for coordinates transformation
+
+ Returns
+ -------
+ StationManager
+ A StationManager object containing satellite configurations and positions.
+ """
+ geometry_converter.validate()
+
+ # Initialize the StationManager for the MSS D2D system
+ mss_d2d_values = TopologyImtMssDc.get_coordinates(
+ geometry_converter,
+ params,
+ random_number_gen,
+ not also_generate_inactive
+ )
+
+ total_satellites = mss_d2d_values["num_satellites"]
+
+ mss_d2d = StationManager(n=total_satellites)
+ mss_d2d.station_type = StationType.MSS_D2D # Set the station type to MSS D2D
+ mss_d2d.is_space_station = True # Indicate that the station is in space
+
+ if params.spectral_mask == "IMT-2020":
+ mss_d2d.spectral_mask = SpectralMaskImt(StationType.IMT_BS,
+ params.frequency,
+ params.bandwidth,
+ params.spurious_emissions,
+ scenario="OUTDOOR")
+ elif params.spectral_mask == "3GPP E-UTRA":
+ mss_d2d.spectral_mask = SpectralMask3Gpp(StationType.IMT_BS,
+ params.frequency,
+ params.bandwidth,
+ params.spurious_emissions,
+ scenario="OUTDOOR")
+ elif params.spectral_mask == "MSS":
+ mss_d2d.spectral_mask = SpectralMaskMSS(params.frequency,
+ params.bandwidth,
+ params.spurious_emissions)
+ else:
+ raise ValueError(f"Invalid or not implemented spectral mask - {params.spectral_mask}")
+ mss_d2d.spectral_mask.set_mask(params.tx_power_density + 10 * np.log10(params.bandwidth * 1e6))
+
+ # Configure satellite positions in the StationManager
+ mss_d2d.x = mss_d2d_values["sat_x"]
+ mss_d2d.y = mss_d2d_values["sat_y"]
+ mss_d2d.z = mss_d2d_values["sat_z"]
+ mss_d2d.elevation = mss_d2d_values["sat_antenna_elev"]
+ mss_d2d.azimuth = mss_d2d_values["sat_antenna_azim"]
+ mss_d2d.height = mss_d2d_values["sat_alt"]
+
+ mss_d2d.active = np.zeros(total_satellites, dtype=bool)
+
+ if mss_d2d_values["num_active_satellites"] != mss_d2d_values["num_satellites"]:
+ mss_d2d.active[mss_d2d_values["active_satellites_idxs"]] = random_number_gen.uniform(
+ size=len(mss_d2d_values["active_satellites_idxs"])
+ ) < params.beams_load_factor
+ else:
+ # Set active satellite flags
+ mss_d2d.active = random_number_gen.uniform(
+ size=total_satellites
+ ) < params.beams_load_factor
+
+ # Initialize satellites antennas
+ # we need to initialize them after coordinates transformation because of
+ # repeated state (elevation and azimuth) inside multiple transceiver implementation
+ mss_d2d.antenna = np.empty(total_satellites, dtype=AntennaS1528Leo)
+ if params.antenna_pattern == "ITU-R-S.1528-LEO":
+ antenna_pattern = AntennaS1528Leo(params.antenna_s1528)
+ elif params.antenna_pattern == "ITU-R-S.1528-Section1.2":
+ antenna_pattern = AntennaS1528(params.antenna_s1528)
+ elif params.antenna_pattern == "ITU-R-S.1528-Taylor":
+ antenna_pattern = AntennaS1528Taylor(params.antenna_s1528)
+ else:
+ raise ValueError("generate_mss_ss: Invalid antenna type: {param_mss.antenna_pattern}")
+
+ for i in range(mss_d2d.num_stations):
+ mss_d2d.antenna[i] = antenna_pattern
+
+ return mss_d2d # Return the configured StationManager
@staticmethod
- def get_random_position( num_stas: int, topology: Topology,
- random_number_gen: np.random.RandomState,
- min_dist_to_bs = 0, central_cell = False,
- deterministic_cell = False):
- hexagon_radius = topology.intersite_distance / 3
+ def get_random_position(num_stas: int,
+ topology: Topology,
+ random_number_gen: np.random.RandomState,
+ min_dist_to_bs=0.,
+ central_cell=False,
+ deterministic_cell=False):
+ """
+ Generate UE random-possitions inside the topolgy area.
+
+ Parameters
+ ----------
+ num_stas : int
+ Number of UE stations
+ topology : Topology
+ The IMT topology object
+ random_number_gen : np.random.RandomState
+ Random number generator
+ min_dist_to_bs : _type_, optional
+ Minimum distance to the BS, by default 0.
+ central_cell : bool, optional
+ Whether the central cell in the cluster is used, by default False
+ deterministic_cell : bool, optional
+ Fix the cell to be used as anchor point, by default False
+
+ Returns
+ -------
+ tuple
+ x, y, z, azimuth and elevation angles.
+ """
+ hexagon_radius = topology.intersite_distance * 2 / 3
- min_dist_ok = False
+ x = np.array([])
+ y = np.array([])
+ z = np.array([])
+ bs_x = -hexagon_radius
+ bs_y = 0
- while not min_dist_ok:
+ while len(x) < num_stas:
+ num_stas_temp = num_stas - len(x)
# generate UE uniformly in a triangle
- x = random_number_gen.uniform(0, hexagon_radius * np.cos(np.pi / 6), num_stas)
- y = random_number_gen.uniform(0, hexagon_radius / 2, num_stas)
+ x_temp = random_number_gen.uniform(0, hexagon_radius * np.cos(np.pi / 6), num_stas_temp)
+ y_temp = random_number_gen.uniform(0, hexagon_radius / 2, num_stas_temp)
- invert_index = np.arctan(y / x) > np.pi / 6
- y[invert_index] = -(hexagon_radius / 2 - y[invert_index])
- x[invert_index] = (hexagon_radius * np.cos(np.pi / 6) - x[invert_index])
+ invert_index = np.arctan(y_temp / x_temp) > np.pi / 6
+ y_temp[invert_index] = -(hexagon_radius / 2 - y_temp[invert_index])
+ x_temp[invert_index] = (hexagon_radius * np.cos(np.pi / 6) - x_temp[invert_index])
- if any (np.sqrt(x**2 + y**2) < min_dist_to_bs):
- min_dist_ok = False
- else:
- min_dist_ok = True
+ # randomly choose a hextant
+ hextant = random_number_gen.random_integers(0, 5, num_stas_temp)
+ hextant_angle = np.pi / 6 + np.pi / 3 * hextant
- # randomly choose an hextant
- hextant = random_number_gen.random_integers(0, 5, num_stas)
- hextant_angle = np.pi / 6 + np.pi / 3 * hextant
+ old_x = x_temp
+ x_temp = x_temp * np.cos(hextant_angle) - y_temp * np.sin(hextant_angle)
+ y_temp = old_x * np.sin(hextant_angle) + y_temp * np.cos(hextant_angle)
- old_x = x
- x = x * np.cos(hextant_angle) - y * np.sin(hextant_angle)
- y = old_x * np.sin(hextant_angle) + y * np.cos(hextant_angle)
+ dist = np.sqrt((x_temp - bs_x) ** 2 + (y_temp - bs_y) ** 2)
+ indices = dist > min_dist_to_bs
+
+ x_temp = x_temp[indices]
+ y_temp = y_temp[indices]
+
+ x = np.append(x, x_temp)
+ y = np.append(y, y_temp)
- # randomly choose a cell
+ x = x - bs_x
+ y = y - bs_y
+
+ # choose cells
if central_cell:
central_cell_indices = np.where((topology.x == 0) & (topology.y == 0))
+
+ if not len(central_cell_indices[0]):
+ sys.stderr.write("ERROR\nTopology does not have a central cell")
+ sys.exit(1)
+
cell = central_cell_indices[0][random_number_gen.random_integers(0, len(central_cell_indices[0]) - 1,
num_stas)]
elif deterministic_cell:
num_bs = topology.num_base_stations
stas_per_cell = num_stas / num_bs
cell = np.repeat(np.arange(num_bs, dtype=int), stas_per_cell)
- else:
+
+ else: # random cells
num_bs = topology.num_base_stations
cell = random_number_gen.random_integers(0, num_bs - 1, num_stas)
cell_x = topology.x[cell]
cell_y = topology.y[cell]
+ cell_z = topology.z[cell]
- x = x + cell_x + hexagon_radius * np.cos(topology.azimuth[cell] * np.pi / 180)
- y = y + cell_y + hexagon_radius * np.sin(topology.azimuth[cell] * np.pi / 180)
+ # x = x + cell_x + hexagon_radius * np.cos(topology.azimuth[cell] * np.pi / 180)
+ # y = y + cell_y + hexagon_radius * np.sin(topology.azimuth[cell] * np.pi / 180)
+ old_x = x
+ x = x * np.cos(np.radians(topology.azimuth[cell])) - y * np.sin(np.radians(topology.azimuth[cell]))
+ y = old_x * np.sin(np.radians(topology.azimuth[cell])) + y * np.cos(np.radians(topology.azimuth[cell]))
+ x = x + cell_x
+ y = y + cell_y
+ z = cell_z
x = list(x)
y = list(y)
+ z = list(z)
# calculate UE azimuth wrt serving BS
- theta = np.arctan2(y - cell_y, x - cell_x)
+ if topology.is_space_station is False:
+ theta = np.arctan2(y - cell_y, x - cell_x)
- # calculate elevation angle
- # psi is the vertical angle of the UE wrt the serving BS
- distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2)
+ # calculate elevation angle
+ # psi is the vertical angle of the UE wrt the serving BS
+ distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2)
+ else:
+ theta = np.arctan2(y - topology.space_station_y[cell], x - topology.space_station_x[cell])
+ distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2 + (topology.bs.height)**2)
- return x, y, theta, distance
+ return x, y, z, theta, distance
if __name__ == '__main__':
- from matplotlib import pyplot as plt
-
- # plot uniform distribution in macrocell scenario
-
- factory = StationFactory()
- topology = TopologyMacrocell(1000, 1)
- topology.calculate_coordinates()
-
- class ParamsAux(object):
- def __init__(self):
- self.spectral_mask = 'IMT-2020'
- self.frequency = 10000
- self.topology = 'MACROCELL'
- self.ue_distribution_type = "UNIFORM"
- self.bs_height = 30
- self.ue_height = 3
- self.ue_indoor_percent = 0
- self.ue_k = 3
- self.ue_k_m = 1
- self.bandwidth = np.random.rand()
- self.ue_noise_figure = np.random.rand()
- self.minimum_separation_distance_bs_ue = 10
- self.spurious_emissions = -30
- self.intersite_distance = 1000
-
- params = ParamsAux()
-
- ant_param = ParametersAntennaImt()
-
- ant_param.adjacent_antenna_model = "SINGLE_ELEMENT"
- ant_param.bs_element_pattern = "F1336"
- ant_param.bs_element_max_g = 5
- ant_param.bs_element_phi_3db = 65
- ant_param.bs_element_theta_3db = 65
- ant_param.bs_element_am = 30
- ant_param.bs_element_sla_v = 30
- ant_param.bs_n_rows = 8
- ant_param.bs_n_columns = 8
- ant_param.bs_element_horiz_spacing = 0.5
- ant_param.bs_element_vert_spacing = 0.5
- ant_param.bs_downtilt_deg = 10
- ant_param.bs_multiplication_factor = 12
- ant_param.bs_minimum_array_gain = -200
-
- ant_param.ue_element_pattern = "FIXED"
- ant_param.ue_element_max_g = 5
- ant_param.ue_element_phi_3db = 90
- ant_param.ue_element_theta_3db = 90
- ant_param.ue_element_am = 25
- ant_param.ue_element_sla_v = 25
- ant_param.ue_n_rows = 4
- ant_param.ue_n_columns = 4
- ant_param.ue_element_horiz_spacing = 0.5
- ant_param.ue_element_vert_spacing = 0.5
- ant_param.ue_multiplication_factor = 12
- ant_param.ue_minimum_array_gain = -200
-
- ant_param.ue_normalization = False
- ant_param.bs_normalization = False
-
- rnd = np.random.RandomState(1)
-
- imt_ue = factory.generate_imt_ue(params, ant_param, topology, rnd)
-
- fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k') # create a figure object
- ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
-
- topology.plot(ax)
-
- plt.axis('image')
- plt.title("Macro cell topology")
- plt.xlabel("x-coordinate [m]")
- plt.ylabel("y-coordinate [m]")
-
- plt.plot(imt_ue.x, imt_ue.y, "r.")
-
- plt.tight_layout()
- plt.show()
+ rand_gen = np.random.RandomState(101)
+ geometry_converter = GeometryConverter()
+
+ # somente vou utilizar a translaรงรฃo que o satรฉlite teoricamente sofreu:
+ ref_lat = -14.1
+ ref_long = -45.1
+ ref_alt = 1200
+ geometry_converter.set_reference(ref_lat, ref_long, ref_alt)
+ from sharc.parameters.parameters_orbit import ParametersOrbit
+
+ orbit = ParametersOrbit(
+ n_planes=20,
+ sats_per_plane=32,
+ phasing_deg=3.9,
+ long_asc_deg=18.0,
+ inclination_deg=54.5,
+ perigee_alt_km=525,
+ apogee_alt_km=525
+ )
+ from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc
+ params = ParametersImtMssDc(
+ beam_radius=36516.0,
+ num_beams=7,
+ orbits=[orbit]
+ )
+ params.sat_is_active_if.conditions = ["MINIMUM_ELEVATION_FROM_ES"]
+ params.sat_is_active_if.minimum_elevation_from_es = 5.0
+
+ topology = TopologyImtMssDc(params, geometry_converter)
+
+ topology.calculate_coordinates(rand_gen)
+
+ topology.calculate_coordinates(rand_gen)
+
+ parameters = ParametersImt()
+ parameters.ue.k = 1
+ parameters.ue.k_m = 1
+ parameters.ue.azimuth_range = (-180, 180)
+ parameters.ue.distribution_distance = "UNIFORM"
+ parameters.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ parameters.ue.distribution_azimuth = "NORMAL"
+ parameters.ue.height = 1.5
+ parameters.ue.indoor_percent = 0
+ parameters.bandwidth = 10
+ parameters.frequency = 10
+ parameters.ue.noise_figure = 0
+
+ imt_ue = StationFactory.generate_imt_ue_outdoor(
+ parameters,
+ parameters.ue.antenna.array,
+ rand_gen,
+ topology
+ )
+
+ from sharc.satellite.scripts.plot_3d_param_file import plot_globe_with_borders
+ fig = plot_globe_with_borders(True, geometry_converter)
+
+ import plotly.graph_objects as go
+
+ # fig.add_trace(go.Scatter3d(
+ # x=topology.x,
+ # y=topology.y,
+ # z=topology.z,
+ # mode='markers',
+ # marker=dict(size=3, color='green', opacity=0.8),
+ # showlegend=False
+ # ))
+ fig.add_trace(go.Scatter3d(
+ x=topology.space_station_x,
+ y=topology.space_station_y,
+ z=topology.space_station_z,
+ mode='markers',
+ marker=dict(size=3, color='green', opacity=0.8),
+ showlegend=False
+ ))
+
+ fig.add_trace(
+ go.Scatter3d(
+ x=imt_ue.x,
+ y=imt_ue.y,
+ z=imt_ue.z,
+ mode='markers',
+ marker=dict(size=1, color='red', opacity=1),
+ showlegend=False
+ )
+ )
+
+ # TODO: replace this with generate imt mss dc station
+ st = StationManager(topology.num_base_stations)
+ st.x = topology.space_station_x
+ st.y = topology.space_station_y
+ st.z = topology.space_station_z
+
+ fig.add_trace(
+ go.Scatter3d(
+ x=[0],
+ y=[0],
+ z=[0],
+ mode='markers',
+ marker=dict(size=3, color='black', opacity=1),
+ showlegend=False
+ )
+ )
+
+ from sharc.support.sharc_geom import polar_to_cartesian
+ # Plot beam boresight vectors
+ boresight_length = 100 * 1e3 # Length of the boresight vectors for visualization
+ boresight_x, boresight_y, boresight_z = polar_to_cartesian(
+ boresight_length,
+ imt_ue.azimuth,
+ imt_ue.elevation
+ )
+ # Add arrow heads to the end of the boresight vectors
+ for x, y, z, bx, by, bz in zip(imt_ue.x,
+ imt_ue.y,
+ imt_ue.z,
+ boresight_x,
+ boresight_y,
+ boresight_z):
+ fig.add_trace(go.Cone(
+ x=[x + bx],
+ y=[y + by],
+ z=[z + bz],
+ u=[bx],
+ v=[by],
+ w=[bz],
+ colorscale=[[0, 'orange'], [1, 'orange']],
+ sizemode='absolute',
+ sizeref=2 * boresight_length / 5,
+ showscale=False
+ ))
+ for x, y, z, bx, by, bz in zip(imt_ue.x,
+ imt_ue.y,
+ imt_ue.z,
+ boresight_x,
+ boresight_y,
+ boresight_z):
+ fig.add_trace(go.Scatter3d(
+ x=[x, x + bx],
+ y=[y, y + by],
+ z=[z, z + bz],
+ mode='lines',
+ line=dict(color='orange', width=2),
+ name='Boresight'
+ ))
+ # Suppress the legend for the boresight plot
+ fig.update_traces(showlegend=False, selector=dict(name='Boresight'))
+
+ # Maintain axis proportions
+ fig.update_layout(scene_aspectmode='data')
+
+ ref_x = imt_ue.x[11]
+ ref_y = imt_ue.y[11]
+ ref_z = imt_ue.z[11]
+ range_scale = 1000
+
+ range_scale = 5000
+ ref_x = 0
+ ref_y = 0
+ ref_z = 0
+ fig.update_layout(
+ scene=dict(
+ zaxis=dict(
+ range=(-1e3 * range_scale + ref_z, 1e3 * range_scale + ref_z)
+ ),
+ yaxis=dict(
+ range=(-1e3 * range_scale + ref_y, 1e3 * range_scale + ref_y)
+ ),
+ xaxis=dict(
+ range=(-1e3 * range_scale + ref_x, 1e3 * range_scale + ref_x)
+ ),
+ )
+ )
+
+ # fig.tight_layout()
+ fig.show()
diff --git a/sharc/station_manager.py b/sharc/station_manager.py
index d83fecdb8..c9b38c5b8 100644
--- a/sharc/station_manager.py
+++ b/sharc/station_manager.py
@@ -6,13 +6,12 @@
"""
import numpy as np
-import math
from sharc.support.enumerations import StationType
from sharc.station import Station
from sharc.antenna.antenna import Antenna
-from sharc.mask.spectral_mask_imt import SpectralMaskImt
-from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp
+from sharc.mask.spectral_mask import SpectralMask
+
class StationManager(object):
"""
@@ -23,36 +22,40 @@ class StationManager(object):
def __init__(self, n):
self.num_stations = n
- self.x = np.empty(n)
- self.y = np.empty(n)
+ self.x = np.empty(n) # x coordinate
+ self.y = np.empty(n) # y coordinate
+ self.z = np.empty(n) # z coordinate (includes height above ground)
self.azimuth = np.empty(n)
self.elevation = np.empty(n)
- self.height = np.empty(n)
+ self.height = np.empty(n) # station height above ground
+ self.idx_orbit = np.empty(n)
self.indoor = np.zeros(n, dtype=bool)
self.active = np.ones(n, dtype=bool)
self.tx_power = np.empty(n)
self.rx_power = np.empty(n)
- self.rx_interference = np.empty(n)
- self.ext_interference = np.empty(n)
+ self.rx_interference = np.empty(n) # Rx interferece in dBW
+ self.ext_interference = np.empty(n) # External interferece in dBW
self.antenna = np.empty(n, dtype=Antenna)
- self.bandwidth = np.empty(n)
+ self.bandwidth = np.empty(n) # Bandwidth in MHz
self.noise_figure = np.empty(n)
self.noise_temperature = np.empty(n)
self.thermal_noise = np.empty(n)
self.total_interference = np.empty(n)
+ self.pfd_external = np.empty(n) # External PFD in dBW/mยฒ/MHz
+ self.pfd_external_aggregated = np.empty(n) # Aggregated External PFD in dBW/mยฒ/MHz
self.snr = np.empty(n)
self.sinr = np.empty(n)
self.sinr_ext = np.empty(n)
- self.inr = np.empty(n)
- self.pfd = np.empty(n)
- self.spectral_mask = np.empty(n, dtype=SpectralMask3Gpp)
+ self.inr = np.empty(n) # INR in dBm/MHz
+ self.pfd = np.empty(n) # Powerflux density in dBm/m^2
+ self.spectral_mask = np.empty(n, dtype=SpectralMask)
self.center_freq = np.empty(n)
- self.spectral_mask = None
self.station_type = StationType.NONE
+ self.is_space_station = False
self.intersite_dist = 0.0
def get_station_list(self, id=None) -> list:
- if(id is None):
+ if (id is None):
id = range(self.num_stations)
station_list = list()
for i in id:
@@ -64,6 +67,7 @@ def get_station(self, id) -> Station:
station.id = id
station.x = self.x[id]
station.y = self.y[id]
+ station.z = self.z[id]
station.azimuth = self.azimuth[id]
station.elevation = self.elevation[id]
station.height = self.height[id]
@@ -87,20 +91,48 @@ def get_station(self, id) -> Station:
return station
def get_distance_to(self, station) -> np.array:
+ """Calculates the 2D distance between stations
+
+ Parameters
+ ----------
+ station : StationManger
+ Station to which calculate the distance
+
+ Returns
+ -------
+ np.array
+ Distance between stations
+ """
distance = np.empty([self.num_stations, station.num_stations])
for i in range(self.num_stations):
- distance[i] = np.sqrt(np.power(self.x[i] - station.x, 2) +
- np.power(self.y[i] - station.y, 2))
+ distance[i] = np.sqrt(
+ np.power(self.x[i] - station.x, 2) +
+ np.power(self.y[i] - station.y, 2),
+ )
return distance
def get_3d_distance_to(self, station) -> np.array:
+ """Calculates the 3D distance between stations
+
+ Parameters
+ ----------
+ station : StationManager
+ Station to which calculate the distance
+
+ Returns
+ -------
+ np.array
+ 3D Distance between stations
+ """
distance = np.empty([self.num_stations, station.num_stations])
for i in range(self.num_stations):
- distance[i] = np.sqrt(np.power(self.x[i] - station.x, 2) +
- np.power(self.y[i] - station.y, 2) +
- np.power(self.height[i] - station.height, 2))
+ distance[i] = np.sqrt(
+ np.power(self.x[i] - station.x, 2) +
+ np.power(self.y[i] - station.y, 2) +
+ np.power(self.z[i] - station.z, 2)
+ )
return distance
-
+
def get_dist_angles_wrap_around(self, station) -> np.array:
"""
Calcualtes distances and angles using the wrap around technique
@@ -115,112 +147,122 @@ def get_dist_angles_wrap_around(self, station) -> np.array:
"""
# Initialize variables
distance_3D = np.empty([self.num_stations, station.num_stations])
- distance_2D = np.inf*np.ones_like(distance_3D)
+ distance_2D = np.inf * np.ones_like(distance_3D)
cluster_num = np.zeros_like(distance_3D, dtype=int)
-
+
# Cluster coordinates
- cluster_x = np.array([station.x,
- station.x + 3.5*self.intersite_dist,
- station.x - 0.5*self.intersite_dist,
- station.x - 4.0*self.intersite_dist,
- station.x - 3.5*self.intersite_dist,
- station.x + 0.5*self.intersite_dist,
- station.x + 4.0*self.intersite_dist])
-
- cluster_y = np.array([station.y,
- station.y + 1.5*np.sqrt(3.0)*self.intersite_dist,
- station.y + 2.5*np.sqrt(3.0)*self.intersite_dist,
- station.y + 1.0*np.sqrt(3.0)*self.intersite_dist,
- station.y - 1.5*np.sqrt(3.0)*self.intersite_dist,
- station.y - 2.5*np.sqrt(3.0)*self.intersite_dist,
- station.y - 1.0*np.sqrt(3.0)*self.intersite_dist])
-
+ cluster_x = np.array([
+ station.x,
+ station.x + 3.5 * self.intersite_dist,
+ station.x - 0.5 * self.intersite_dist,
+ station.x - 4.0 * self.intersite_dist,
+ station.x - 3.5 * self.intersite_dist,
+ station.x + 0.5 * self.intersite_dist,
+ station.x + 4.0 * self.intersite_dist,
+ ])
+
+ cluster_y = np.array([
+ station.y,
+ station.y + 1.5 *
+ np.sqrt(3.0) * self.intersite_dist,
+ station.y + 2.5 *
+ np.sqrt(3.0) * self.intersite_dist,
+ station.y + 1.0 *
+ np.sqrt(3.0) * self.intersite_dist,
+ station.y - 1.5 *
+ np.sqrt(3.0) * self.intersite_dist,
+ station.y - 2.5 *
+ np.sqrt(3.0) * self.intersite_dist,
+ station.y - 1.0 * np.sqrt(3.0) * self.intersite_dist,
+ ])
+
# Calculate 2D distance
temp_distance = np.zeros_like(distance_2D)
- for k,(x,y) in enumerate(zip(cluster_x,cluster_y)):
- temp_distance = np.sqrt(np.power(x - self.x[:,np.newaxis], 2) +
- np.power(y - self.y[:,np.newaxis], 2))
+ for k, (x, y) in enumerate(zip(cluster_x, cluster_y)):
+ temp_distance = np.sqrt(
+ np.power(x - self.x[:, np.newaxis], 2) +
+ np.power(y - self.y[:, np.newaxis], 2),
+ )
is_shorter = temp_distance < distance_2D
distance_2D[is_shorter] = temp_distance[is_shorter]
cluster_num[is_shorter] = k
-
+
# Calculate 3D distance
- distance_3D = np.sqrt(np.power(distance_2D, 2) +
- np.power(station.height - self.height[:,np.newaxis], 2))
-
+ distance_3D = np.sqrt(
+ np.power(distance_2D, 2) +
+ np.power(station.height - self.height[:, np.newaxis], 2),
+ )
+
# Calcualte pointing vector
- point_vec_x = cluster_x[cluster_num,np.arange(station.num_stations)] \
- - self.x[:,np.newaxis]
- point_vec_y = cluster_y[cluster_num,np.arange(station.num_stations)] \
- - self.y[:,np.newaxis]
- point_vec_z = station.height - self.height[:,np.newaxis]
-
- phi = np.array(np.rad2deg(np.arctan2(point_vec_y,point_vec_x)),ndmin=2)
- theta = np.rad2deg(np.arccos(point_vec_z/distance_3D))
-
+ point_vec_x = cluster_x[cluster_num, np.arange(station.num_stations)] \
+ - self.x[:, np.newaxis]
+ point_vec_y = cluster_y[cluster_num, np.arange(station.num_stations)] \
+ - self.y[:, np.newaxis]
+ point_vec_z = station.height - self.height[:, np.newaxis]
+
+ phi = np.array(
+ np.rad2deg(
+ np.arctan2(
+ point_vec_y, point_vec_x,
+ ),
+ ), ndmin=2,
+ )
+ theta = np.rad2deg(np.arccos(point_vec_z / distance_3D))
+
return distance_2D, distance_3D, phi, theta
def get_elevation(self, station) -> np.array:
"""
Calculates the elevation angle between stations. Can be used for
IMT stations.
-
- TODO: this implementation is essentialy the same as the one from
+
+ TODO: this implementation is essentialy the same as the one from
get_elevation_angle (free-space elevation angle), despite the
- different matrix dimentions. So, the methods should be merged
+ different matrix dimentions. So, the methods should be merged
in order to reuse the source code
"""
elevation = np.empty([self.num_stations, station.num_stations])
for i in range(self.num_stations):
- distance = np.sqrt(np.power(self.x[i] - station.x, 2) +
- np.power(self.y[i] - station.y, 2))
- rel_z = station.height - self.height[i]
+ distance = np.sqrt(
+ np.power(self.x[i] - station.x, 2) +
+ np.power(self.y[i] - station.y, 2),
+ )
+ rel_z = station.z - self.z[i]
elevation[i] = np.degrees(np.arctan2(rel_z, distance))
-
- return elevation
-
-
- def get_elevation_angle(self, station, sat_params) -> dict:
- free_space_angle = np.empty(self.num_stations)
- angle = np.empty(self.num_stations)
- for i in range(self.num_stations):
- # calculate free-space elevation angle according to Attachment A
- rel_x = station.x - self.x[i]
- rel_y = station.y - self.y[i]
- rel_z = station.height - self.height[i]
-
- gts = np.sqrt(rel_x**2 + rel_y**2)
- theta_0 = np.arctan2(rel_z, gts) # free-space elevation angle
- free_space_angle[i] = np.degrees(theta_0)
-
- ##
- # calculate apparent elevation angle according to ITU-R P619, Attachment B
- tau_fs1 = 1.728 + 0.5411 * theta_0 + 0.03723 * theta_0**2
- tau_fs2 = 0.1815 + 0.06272 * theta_0 + 0.01380 * theta_0**2
- tau_fs3 = 0.01727 + 0.008288 * theta_0
-
- # change in elevation angle due to refraction
- tau_fs_deg = 1/(tau_fs1 + sat_params.altitude*tau_fs2 +
- sat_params.altitude**2*tau_fs3)
- tau_fs = tau_fs_deg / 180. * np.pi
+ return elevation
- angle[i] = np.degrees(theta_0 + tau_fs)
+ def get_pointing_vector_to(self, station) -> tuple:
+ """calculate the pointing vector (angles) w.r.t. the other station
- return{'free_space': free_space_angle, 'apparent': angle}
+ Parameters
+ ----------
+ station : StationManager
+ The other station to calculate the pointing vector
- def get_pointing_vector_to(self, station) -> tuple:
+ Returns
+ -------
+ tuple
+ phi, theta (phi is calculated with respect to x counter-clock-wise and
+ theta is calculated with respect to z counter-clock-wise)
+ """
- point_vec_x = station.x- self.x[:,np.newaxis]
- point_vec_y = station.y - self.y[:,np.newaxis]
- point_vec_z = station.height - self.height[:,np.newaxis]
+ point_vec_x = station.x - self.x[:, np.newaxis]
+ point_vec_y = station.y - self.y[:, np.newaxis]
+ point_vec_z = station.z - self.z[:, np.newaxis]
dist = self.get_3d_distance_to(station)
- phi = np.array(np.rad2deg(np.arctan2(point_vec_y,point_vec_x)),ndmin=2)
- theta = np.rad2deg(np.arccos(point_vec_z/dist))
+ phi = np.array(
+ np.rad2deg(
+ np.arctan2(
+ point_vec_y, point_vec_x,
+ ),
+ ), ndmin=2,
+ )
+ theta = np.rad2deg(np.arccos(point_vec_z / dist))
return phi, theta
@@ -231,11 +273,79 @@ def get_off_axis_angle(self, station) -> np.array:
Az, b = self.get_pointing_vector_to(station)
Az0 = self.azimuth
- a = 90 - self.elevation
- C = Az0 - Az
+ a = 90 - self.elevation[:, np.newaxis]
+ C = Az0[:, np.newaxis] - Az
- phi = 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)))
+ cos_phi = 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))
+ phi = np.arccos(
+ # imprecision may accumulate enough for numbers to be slightly out of arccos range
+ np.clip(cos_phi, -1., 1.)
+ )
phi_deg = np.degrees(phi)
return phi_deg
+
+ def is_imt_station(self) -> bool:
+ """Whether this station is IMT or not
+
+ Parameters
+ ----------
+ sta : StationManager
+ The station that we're testing.
+
+ Returns
+ -------
+ bool
+ Whether this station is IMT or not
+ """
+ if self.station_type is StationType.IMT_BS or self.station_type is StationType.IMT_UE:
+ return True
+ else:
+ return False
+
+
+def copy_active_stations(stations: StationManager) -> StationManager:
+ """Copy only the active stations from a StationManager object
+
+ Parameters
+ ----------
+ stations : StationManager
+ StationManager object to copy
+
+ Returns
+ -------
+ StationManager
+ A new StationManager object with only the active stations
+ """
+ act_sta = StationManager(np.sum(stations.active))
+ for idx, active_idx in enumerate(np.where(stations.active)[0]):
+ act_sta.x[idx] = stations.x[active_idx]
+ act_sta.y[idx] = stations.y[active_idx]
+ act_sta.z[idx] = stations.z[active_idx]
+ act_sta.azimuth[idx] = stations.azimuth[active_idx]
+ act_sta.elevation[idx] = stations.elevation[active_idx]
+ act_sta.height[idx] = stations.height[active_idx]
+ act_sta.indoor[idx] = stations.indoor[active_idx]
+ act_sta.active[idx] = stations.active[active_idx]
+ act_sta.tx_power[idx] = stations.tx_power[active_idx]
+ act_sta.rx_power[idx] = stations.rx_power[active_idx]
+ act_sta.rx_interference[idx] = stations.rx_interference[active_idx]
+ act_sta.ext_interference[idx] = stations.ext_interference[active_idx]
+ act_sta.antenna[idx] = stations.antenna[active_idx]
+ act_sta.bandwidth[idx] = stations.bandwidth[active_idx]
+ act_sta.noise_figure[idx] = stations.noise_figure[active_idx]
+ act_sta.noise_temperature[idx] = stations.noise_temperature[active_idx]
+ act_sta.thermal_noise[idx] = stations.thermal_noise[active_idx]
+ act_sta.total_interference[idx] = stations.total_interference[active_idx]
+ act_sta.snr[idx] = stations.snr[active_idx]
+ act_sta.sinr[idx] = stations.sinr[active_idx]
+ act_sta.sinr_ext[idx] = stations.sinr_ext[active_idx]
+ act_sta.inr[idx] = stations.inr[active_idx]
+ act_sta.pfd[idx] = stations.pfd[active_idx]
+ act_sta.spectral_mask = stations.spectral_mask
+ act_sta.center_freq[idx] = stations.center_freq[active_idx]
+ act_sta.station_type = stations.station_type
+ act_sta.is_space_station = stations.is_space_station
+ act_sta.intersite_dist = stations.intersite_dist
+ return act_sta
diff --git a/sharc/support/__init__.py b/sharc/support/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/support/__init__.py
+++ b/sharc/support/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/support/enumerations.py b/sharc/support/enumerations.py
index fe43c3c7a..7b8a89dd3 100644
--- a/sharc/support/enumerations.py
+++ b/sharc/support/enumerations.py
@@ -7,35 +7,43 @@
from enum import Enum
-class Action( Enum ):
+
+class Action(Enum):
"""
The action that is sent to controller in order to control the simulation
"""
START_SIMULATION = 1
START_SIMULATION_SINGLE_THREAD = 2
- STOP_SIMULATION = 3
-
-class State( Enum ):
+ STOP_SIMULATION = 3
+
+
+class State(Enum):
"""
This is the graphical user interface state
"""
- INITIAL = 1
- RUNNING = 2
+ INITIAL = 1
+ RUNNING = 2
FINISHED = 3
- STOPPED = 4
+ STOPPED = 4
STOPPING = 5
-
+
+
class StationType(Enum):
"""
Station types supported by simulator.
"""
- NONE = 0 # Dummy enum, for initialization purposes only
+ NONE = 0 # Dummy enum, for initialization purposes only
IMT_BS = 1 # IMT Base Station
IMT_UE = 2 # IMT User Equipment
FSS_SS = 3 # FSS Space Station
FSS_ES = 4 # FSS Earth Station
- FS = 5 # Fixed Service
- HAPS = 6 # HAPS (airbone) station
- RNS = 7 # Radionavigation service
- RAS = 8 # Radio Astronomy Service
- EESS_PASSIVE = 9 # EESS passive sensor
+ FS = 5 # Fixed Service
+ HAPS = 6 # HAPS (airbone) station
+ RNS = 7 # Radionavigation service
+ RAS = 8 # Radio Astronomy Service
+ EESS_SS = 9 # EESS Space Station
+ METSAT_SS = 10 # MetSat Space Station
+ SINGLE_EARTH_STATION = 11 # Generic Earth Station implementation
+ SINGLE_SPACE_STATION = 12
+ MSS_D2D = 13 # Mobile Satellite Service - Direct to Device system
+ MSS_SS = 14 # Mobile Satellite Service - Space Station
diff --git a/sharc/support/footprint.py b/sharc/support/footprint.py
index 4d97ffe8a..89a3f2516 100644
--- a/sharc/support/footprint.py
+++ b/sharc/support/footprint.py
@@ -8,32 +8,43 @@
from area import area as earthArea
from numpy import cos, sin, tan, arctan, deg2rad, rad2deg, arccos, pi, linspace, arcsin, vstack, arctan2, where, zeros_like
import matplotlib.pyplot as plt
+from sharc.parameters.constants import EARTH_RADIUS
+
class Footprint(object):
"""
Defines a satellite footprint region and calculates its area.
- Method for generating footprints (Siocos,1973) is found in the book
+ Method for generating footprints (Siocos,1973) is found in the book
"Satellite Communication Systems" by M. Richharia ISBN 0-07-134208-7
-
+
Construction:
FootprintArea(bore_lat_deg, bore_subsat_long_deg, beam)
beam_deg (float): half of beam width in degrees
- elevation_deg (float): optional. Satellite elevation at
- boresight bore_lat_deg (float): optional, default = 0.
- Latitude of boresight point. If elevation is given this
+ elevation_deg (float): optional. Satellite elevation at
+ boresight bore_lat_deg (float): optional, default = 0.
+ Latitude of boresight point. If elevation is given this
parameter is not used. Default = 0
bore_subsat_long_deg (float): longitude of boresight with respect
to sub-satellite point, taken positive when to the west of the
- sub-satellite point. If elevation is given this
+ sub-satellite point. If elevation is given this
parameter is not used. Default = 0
+ sat_height (int): optional, Default = 3578600.
+ Height of satellite in meters. If none are given, it is assumed that it is a geostationary satellite.
"""
- def __init__(self,beam_deg:float,**kwargs):
+
+ def __init__(self, beam_deg: float, **kwargs):
# Initialize attributes
+ self.sat_height = 35786000
+ if 'sat_height' in kwargs.keys():
+ self.sat_height = kwargs['sat_height']
+
if 'elevation_deg' in kwargs.keys():
self.elevation_deg = kwargs['elevation_deg']
self.bore_lat_deg = 0.0
+ self.sigma = EARTH_RADIUS / (EARTH_RADIUS + self.sat_height)
self.bore_subsat_long_deg = self.calc_beta(self.elevation_deg)
else:
+ self.sigma = EARTH_RADIUS / (EARTH_RADIUS + self.sat_height)
self.bore_lat_deg = 0.0
self.bore_subsat_long_deg = 0.0
if 'bore_lat_deg' in kwargs.keys():
@@ -41,194 +52,338 @@ def __init__(self,beam_deg:float,**kwargs):
if 'bore_subsat_long_deg' in kwargs.keys():
self.bore_subsat_long_deg = kwargs['bore_subsat_long_deg']
self.elevation_deg = \
- self.calc_elevation(self.bore_lat_deg,self.bore_subsat_long_deg)
-
+ self.calc_elevation(
+ self.bore_lat_deg,
+ self.bore_subsat_long_deg,
+ )
+
self.beam_width_deg = beam_deg
-
+
+ # sigma is the relation bewtween earth radius and satellite height
+ # print(self.sigma)
+
# Convert to radians
self.elevation_rad = deg2rad(self.elevation_deg)
self.bore_lat_rad = deg2rad(self.bore_lat_deg)
self.bore_subsat_long_rad = deg2rad(self.bore_subsat_long_deg)
self.beam_width_rad = deg2rad(self.beam_width_deg)
-
+
# Calculate tilt
- self.beta = arccos(cos(self.bore_lat_rad)*\
- cos(self.bore_subsat_long_rad))
- self.bore_tilt = arctan2(sin(self.beta),(6.6235 - cos(self.beta)))
-
+ self.beta = arccos(
+ cos(self.bore_lat_rad) *
+ cos(self.bore_subsat_long_rad),
+ )
+ self.bore_tilt = arctan2(
+ sin(self.beta), ((1 / self.sigma) - cos(self.beta)),
+ )
+
# Maximum tilt and latitute coverage
- self.max_gamma_rad = deg2rad(8.6833)
- self.max_beta_rad = deg2rad(81.3164)
-
- def calc_beta(self,elev_deg: float):
+ self.max_beta_rad = arccos(self.sigma)
+ self.max_gamma_rad = pi / 2 - self.max_beta_rad
+
+ def calc_beta(self, elev_deg: float):
"""
- Calculates elevation angle based on given elevation. Beta is the
+ Calculates elevation angle based on given elevation. Beta is the
subpoint to earth station great-circle distance
-
+
Input:
elev_deg (float): elevation in degrees
-
+
Output:
- beta (float): beta angle in degrees
+ beta (float): beta angle in degrees
"""
elev_rad = deg2rad(elev_deg)
- beta = 90 - elev_deg - rad2deg(arcsin(cos(elev_rad)/6.6235))
+ beta = 90 - elev_deg - rad2deg(arcsin(cos(elev_rad) * self.sigma))
return beta
-
- def calc_elevation(self,lat_deg: float, long_deg: float):
+
+ def calc_elevation(self, lat_deg: float, long_deg: float):
"""
- Calculates elevation for given latitude of boresight point and
+ Calculates elevation for given latitude of boresight point and
longitude of boresight with respect to sub-satellite point.
-
+
Inputs:
lat_deg (float): latitude of boresight point in degrees
long_deg (float): longitude of boresight with respect
to sub-satellite point, taken positive when to the west of the
sub-satellite point, in degrees
-
+
Output:
elev (float): elevation in degrees
"""
lat_rad = deg2rad(lat_deg)
long_rad = deg2rad(long_deg)
- beta = arccos(cos(lat_rad)*cos(long_rad))
- elev = arctan2((cos(beta) - 0.1510),sin(beta))
-
+ beta = arccos(cos(lat_rad) * cos(long_rad))
+ elev = arctan2((cos(beta) - self.sigma), sin(beta))
+
return rad2deg(elev)
-
- def set_elevation(self,elev: float):
+
+ def set_elevation(self, elev: float):
"""
Resets elevation angle to given value
"""
self.elevation_deg = elev
self.bore_lat_deg = 0.0
self.bore_subsat_long_deg = self.calc_beta(self.elevation_deg)
-
+
# Convert to radians
self.elevation_rad = deg2rad(self.elevation_deg)
self.bore_lat_rad = deg2rad(self.bore_lat_deg)
self.bore_subsat_long_rad = deg2rad(self.bore_subsat_long_deg)
-
+
# Calculate tilt
- self.beta = arccos(cos(self.bore_lat_rad)*\
- cos(self.bore_subsat_long_rad))
- self.bore_tilt = arctan2(sin(self.beta),(6.6235 - cos(self.beta)))
-
+ self.beta = arccos(
+ cos(self.bore_lat_rad) *
+ cos(self.bore_subsat_long_rad),
+ )
+ self.bore_tilt = arctan2(
+ sin(self.beta), (1 / self.sigma - cos(self.beta)),
+ )
+
def calc_footprint(self, n: int):
"""
Defines footprint polygonal approximation
-
+
Input:
n (int): number of vertices on polygonal
-
+
Outputs:
pt_long (np.array): longitude of vertices in deg
pt_lat (np.array): latiture of vertices in deg
"""
# Projection angles
- phi = linspace(0,2*pi,num = n)
-
- cos_gamma_n = cos(self.bore_tilt)*cos(self.beam_width_rad) + \
- sin(self.bore_tilt)*sin(self.beam_width_rad)*\
- cos(phi)
-
- gamma_n = arccos(cos_gamma_n)
- phi_n = arctan2(sin(phi),(sin(self.bore_tilt)*self.cot(self.beam_width_rad) - \
- cos(self.bore_tilt)*cos(phi)))
-
- eps_n = arctan2(sin(self.bore_subsat_long_rad),tan(self.bore_lat_rad)) + \
- phi_n
-
- beta_n = arcsin(6.6235*sin(gamma_n)) - gamma_n
- beta_n[where(gamma_n > self.max_gamma_rad)] = self.max_beta_rad
-
- pt_lat = arcsin(sin(beta_n)*cos(eps_n))
- pt_long = arctan(tan(beta_n)*sin(eps_n))
-
+ phi = linspace(0, 2 * pi, num=n)
+
+ cos_gamma_n = cos(self.bore_tilt) * cos(self.beam_width_rad) + \
+ sin(self.bore_tilt) * sin(self.beam_width_rad) *\
+ cos(phi)
+
+ gamma_n = arccos(cos_gamma_n)
+ phi_n = arctan2(
+ sin(phi), (
+ sin(self.bore_tilt) * self.cot(self.beam_width_rad) -
+ cos(self.bore_tilt) * cos(phi)
+ ),
+ )
+
+ eps_n = arctan2(sin(self.bore_subsat_long_rad), tan(self.bore_lat_rad)) + \
+ phi_n
+
+ beta_n = arcsin((1 / self.sigma) * sin(gamma_n)) - gamma_n
+ beta_n[where(gamma_n > self.max_gamma_rad)] = self.max_beta_rad
+
+ pt_lat = arcsin(sin(beta_n) * cos(eps_n))
+ pt_long = arctan(tan(beta_n) * sin(eps_n))
+
return rad2deg(pt_long), rad2deg(pt_lat)
-
- def calc_area(self, n:int):
+
+ def calc_area(self, n: int):
"""
Returns footprint area in km^2
-
+
Input:
n (int): number of vertices on polygonal approximation
Output:
a (float): footprint area in km^2
"""
+
long, lat = self.calc_footprint(n)
-
+
long_lat = vstack((long, lat)).T
-
- obj = {'type':'Polygon',
- 'coordinates':[long_lat.tolist()]}
-
- return earthArea(obj)*1e-6
-
- def cot(self,angle):
- return tan(pi/2 - angle)
-
- def arccot(self,x):
- return pi/2 - arctan(x)
-
+
+ obj = {
+ 'type': 'Polygon',
+ 'coordinates': [long_lat.tolist()],
+ }
+
+ return earthArea(obj) * 1e-6
+
+ def cot(self, angle):
+ return tan(pi / 2 - angle)
+
+ def arccot(self, x):
+ return pi / 2 - arctan(x)
+
+
if __name__ == '__main__':
- # Earth [km]
- R = 6371
-
- # Create object
- fprint90 = Footprint(0.325,elevation_deg=90)
- fprint45 = Footprint(0.325,elevation_deg=45)
- fprint30 = Footprint(0.325,elevation_deg=30)
- fprint20 = Footprint(0.325,elevation_deg=20)
- fprint05 = Footprint(0.325,elevation_deg=5)
-
+
+ # Create 20km footprints
+ footprint_20km_10deg = Footprint(5, elevation_deg=10, sat_height=20000)
+ footprint_20km_20deg = Footprint(5, elevation_deg=20, sat_height=20000)
+ footprint_20km_30deg = Footprint(5, elevation_deg=30, sat_height=20000)
+ footprint_20km_45deg = Footprint(5, elevation_deg=45, sat_height=20000)
+ footprint_20km_90deg = Footprint(5, elevation_deg=90, sat_height=20000)
+
+ plt.figure(figsize=(15, 2))
+ n = 100
+ lng, lat = footprint_20km_90deg.calc_footprint(n)
+ plt.plot(lng, lat, 'k', label='$90^o$')
+ lng, lat = footprint_20km_45deg.calc_footprint(n)
+ plt.plot(lng, lat, 'b', label='$45^o$')
+ lng, lat = footprint_20km_30deg.calc_footprint(n)
+ plt.plot(lng, lat, 'r', label='$30^o$')
+ lng, lat = footprint_20km_20deg.calc_footprint(n)
+ plt.plot(lng, lat, 'g', label='$20^o$')
+ lng, lat = footprint_20km_10deg.calc_footprint(n)
+ plt.plot(lng, lat, 'y', label='$10^o$')
+
+ plt.title("Footprints at 20km")
+ plt.legend(loc='upper right')
+ plt.xlabel('Longitude [deg]')
+ plt.ylabel('Latitude [deg]')
+ # plt.xlim([-5, 6])
+ plt.grid()
+ plt.show()
+
+ # Create 500km footprints
+ footprint_500km_10deg = Footprint(5, elevation_deg=10, sat_height=500000)
+ footprint_500km_20deg = Footprint(5, elevation_deg=20, sat_height=500000)
+ footprint_500km_30deg = Footprint(5, elevation_deg=30, sat_height=500000)
+ footprint_500km_45deg = Footprint(5, elevation_deg=45, sat_height=500000)
+ footprint_500km_90deg = Footprint(5, elevation_deg=90, sat_height=500000)
+ print(
+ "Sat at 500km elevation 90 deg: area = {}".format(
+ footprint_500km_90deg.calc_area(n),),
+ )
+
+ plt.figure(figsize=(15, 2))
+ n = 100
+ lng, lat = footprint_500km_90deg.calc_footprint(n)
+ plt.plot(lng, lat, 'k', label='$90^o$')
+ lng, lat = footprint_500km_45deg.calc_footprint(n)
+ plt.plot(lng, lat, 'b', label='$45^o$')
+ lng, lat = footprint_500km_30deg.calc_footprint(n)
+ plt.plot(lng, lat, 'r', label='$30^o$')
+ lng, lat = footprint_500km_20deg.calc_footprint(n)
+ plt.plot(lng, lat, 'g', label='$20^o$')
+ lng, lat = footprint_500km_10deg.calc_footprint(n)
+ plt.plot(lng, lat, 'y', label='$10^o$')
+
+ plt.title("Footprints at 500km")
+ plt.legend(loc='upper right')
+ plt.xlabel('Longitude [deg]')
+ plt.ylabel('Latitude [deg]')
+ # plt.xlim([-5, 20])
+ plt.grid()
+ plt.show()
+
+ # Create GEO footprints
+ fprint90 = Footprint(0.325, elevation_deg=90)
+ fprint45 = Footprint(0.325, elevation_deg=45)
+ fprint30 = Footprint(0.325, elevation_deg=30)
+ fprint20 = Footprint(0.325, elevation_deg=20)
+ fprint05 = Footprint(0.325, elevation_deg=5)
+
# Plot coordinates
n = 100
- plt.figure(figsize=(15,2))
+ plt.figure(figsize=(15, 2))
long, lat = fprint90.calc_footprint(n)
- plt.plot(long,lat,'k',label='$90^o$')
+ plt.plot(long, lat, 'k', label='$90^o$')
long, lat = fprint45.calc_footprint(n)
- plt.plot(long,lat,'b',label='$45^o$')
+ plt.plot(long, lat, 'b', label='$45^o$')
long, lat = fprint30.calc_footprint(n)
- plt.plot(long,lat,'r',label='$30^o$')
+ plt.plot(long, lat, 'r', label='$30^o$')
long, lat = fprint20.calc_footprint(n)
- plt.plot(long,lat,'g',label='$20^o$')
+ plt.plot(long, lat, 'g', label='$20^o$')
long, lat = fprint05.calc_footprint(n)
- plt.plot(long,lat,'y',label='$5^o$')
+ plt.plot(long, lat, 'y', label='$5^o$')
+
+ plt.title("Footprints at 35786km (GEO)")
plt.legend(loc='upper right')
plt.xlabel('Longitude [deg]')
plt.ylabel('Latitude [deg]')
- plt.xlim([-5, 90])
+ # plt.xlim([-5, 90])
plt.grid()
plt.show()
-
+
# Print areas
n = 1000
- print("Sat elevation 90 deg: area = {}".format(fprint90.calc_area(n)))
- print("Sat elevation 45 deg: area = {}".format(fprint45.calc_area(n)))
- print("Sat elevation 30 deg: area = {}".format(fprint30.calc_area(n)))
- print("Sat elevation 20 deg: area = {}".format(fprint20.calc_area(n)))
- print("Sat elevation 05 deg: area = {}".format(fprint05.calc_area(n)))
-
+
+ # area 20km
+ print(
+ "Sat at 20km elevation 90 deg: area = {}".format(
+ footprint_20km_90deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 20km elevation 45 deg: area = {}".format(
+ footprint_20km_45deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 20km elevation 30 deg: area = {}".format(
+ footprint_20km_30deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 20km elevation 20 deg: area = {}".format(
+ footprint_20km_20deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 20km elevation 10 deg: area = {}".format(
+ footprint_20km_10deg.calc_area(n),
+ ),
+ )
+
+ # area 500km
+ print(
+ "Sat at 500km elevation 90 deg: area = {}".format(
+ footprint_500km_90deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 500km elevation 45 deg: area = {}".format(
+ footprint_500km_45deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 500km elevation 30 deg: area = {}".format(
+ footprint_500km_30deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 500km elevation 20 deg: area = {}".format(
+ footprint_500km_20deg.calc_area(n),
+ ),
+ )
+ print(
+ "Sat at 500km elevation 10 deg: area = {}".format(
+ footprint_500km_10deg.calc_area(n),
+ ),
+ )
+
# Plot area vs elevation
n_el = 100
n_poly = 1000
- elevation = linspace(0,90,num=n_el)
- area = zeros_like(elevation)
-
- fprint = Footprint(0.320,elevation_deg=0)
-
+ elevation = linspace(0, 90, num=n_el)
+ area_20km = zeros_like(elevation)
+ area_500km = zeros_like(elevation)
+ area_35786km = zeros_like(elevation)
+
+ fprint_20km = Footprint(5, elevation_deg=0, sat_height=20000)
+ fprint_500km = Footprint(5, elevation_deg=0, sat_height=500000)
+ fprint_35786km = Footprint(0.325, elevation_deg=0, sat_height=35786000)
+
for k in range(len(elevation)):
- fprint.set_elevation(elevation[k])
- area[k] = fprint.calc_area(n_poly)
-
- plt.plot(elevation,area)
+ fprint_20km.set_elevation(elevation[k])
+ area_20km[k] = fprint_20km.calc_area(n_poly)
+ fprint_500km.set_elevation(elevation[k])
+ area_500km[k] = fprint_500km.calc_area(n_poly)
+ fprint_35786km.set_elevation(elevation[k])
+ area_35786km[k] = fprint_35786km.calc_area(n_poly)
+
+ plt.plot(elevation, area_20km, color='r', label='20km')
+ plt.plot(elevation, area_500km, color='g', label='500km')
+ plt.plot(elevation, area_35786km, color='b', label='35786km')
plt.xlabel('Elevation [deg]')
plt.ylabel('Footprint area [$km^2$]')
+ plt.legend(loc='upper right')
plt.xlim([0, 90])
plt.grid()
plt.show()
-
-
-
-
\ No newline at end of file
+
+ # Plot area vs elevation
+ n_el = 100
+ n_poly = 1000
+ elevation = linspace(0, 90, num=n_el)
+ area = zeros_like(elevation)
diff --git a/sharc/support/logging.py b/sharc/support/logging.py
index e1e929b31..464a3482f 100644
--- a/sharc/support/logging.py
+++ b/sharc/support/logging.py
@@ -10,12 +10,14 @@
import yaml
+
class Logging():
-
@staticmethod
- def setup_logging(default_path='support/logging.yaml',
- default_level=logging.INFO, env_key='LOG_CFG'):
+ def setup_logging(
+ default_path='support/logging.yaml',
+ default_level=logging.INFO, env_key='LOG_CFG',
+ ):
"""
Setup logging configuration
"""
diff --git a/sharc/support/named_tuples.py b/sharc/support/named_tuples.py
index 4cbe8e97a..75945ad5c 100644
--- a/sharc/support/named_tuples.py
+++ b/sharc/support/named_tuples.py
@@ -7,5 +7,10 @@
from collections import namedtuple
-AntennaPar = namedtuple("AntennaPar",
- "adjacent_antenna_model normalization normalization_data element_pattern element_max_g element_phi_3db element_theta_3db element_am element_sla_v n_rows n_columns element_horiz_spacing element_vert_spacing multiplication_factor minimum_array_gain downtilt")
+AntennaPar = namedtuple(
+ "AntennaPar",
+ "adjacent_antenna_model normalization normalization_data element_pattern \
+ element_max_g element_phi_3db element_theta_3db element_am element_sla_v n_rows \
+ n_columns element_horiz_spacing element_vert_spacing multiplication_factor \
+ minimum_array_gain downtilt",
+)
diff --git a/sharc/support/observable.py b/sharc/support/observable.py
index c9730e84f..5ce4f6c9f 100644
--- a/sharc/support/observable.py
+++ b/sharc/support/observable.py
@@ -5,41 +5,42 @@
@author: edgar
"""
+
class Observable(object):
"""
- This class represents an observable object, or "data" in the model-view
- paradigm. It can be subclassed to represent an object that the application
- wants to have observed. An observable object can have one or more
- observers. An observer may be any object that implements interface
- Observer. After an observable instance changes, an application calling the
- Observable's notify_observers method causes all of its observers to be
+ This class represents an observable object, or "data" in the model-view
+ paradigm. It can be subclassed to represent an object that the application
+ wants to have observed. An observable object can have one or more
+ observers. An observer may be any object that implements interface
+ Observer. After an observable instance changes, an application calling the
+ Observable's notify_observers method causes all of its observers to be
notified of the change by a call to their update method.
-
+
Attributes
----------
observers : Observer
The list of observers
"""
-
+
def __init__(self):
self.observers = list()
-
+
def add_observer(self, observer):
"""
Adds a new observer to the current list of observers.
-
+
Parameters
----------
observer : Observer
The observer to be added
"""
- if not observer in self.observers:
+ if observer not in self.observers:
self.observers.append(observer)
-
+
def delete_observer(self, observer):
"""
Deletes an observer from the current list of observers.
-
+
Parameters
----------
observer : Observer
@@ -47,15 +48,15 @@ def delete_observer(self, observer):
"""
if observer in self.observers:
self.observers.remove(observer)
-
+
def delete_observers(self):
"""
- Clears the observer list so that this object no longer has any
+ Clears the observer list so that this object no longer has any
observers.
"""
if self.observers:
del self.observers[:]
-
+
def notify_observers(self, *args, **kwargs):
"""
Notifies all observers that this object has changed
diff --git a/sharc/support/observer.py b/sharc/support/observer.py
index 3058281bb..fc40b9a26 100644
--- a/sharc/support/observer.py
+++ b/sharc/support/observer.py
@@ -6,21 +6,21 @@
"""
from abc import ABCMeta, abstractmethod
-
+
+
class Observer(object):
"""
- This is the abstract base class of the Observer pattern. A concrete
- class can extend this class when it wants to be informed of changes in
+ This is the abstract base class of the Observer pattern. A concrete
+ class can extend this class when it wants to be informed of changes in
observable objects.
"""
-
+
__metaclass__ = ABCMeta
-
+
@abstractmethod
def notify_observer(self, *args, **kwargs):
"""
- This method is called whenever the observed object is changed. An
- application calls an Observable object's notify_observers method to
+ This method is called whenever the observed object is changed. An
+ application calls an Observable object's notify_observers method to
have all the object's observers notified of the change.
"""
- pass
diff --git a/sharc/support/sharc_geom.py b/sharc/support/sharc_geom.py
new file mode 100644
index 000000000..0a37d9d41
--- /dev/null
+++ b/sharc/support/sharc_geom.py
@@ -0,0 +1,523 @@
+import numpy as np
+import shapely as shp
+import typing
+import pyproj
+
+from sharc.satellite.utils.sat_utils import lla2ecef, ecef2lla
+from sharc.station_manager import StationManager
+
+
+def cartesian_to_polar(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple:
+ """
+ Converts cartesian coordinates to polar coordinates.
+
+ Parameters
+ ----------
+ x : np.ndarray
+ x coordinate in meters
+ y : np.ndarray
+ y coordinate in meters
+ z : np.ndarray
+ z coordinate in meters
+
+ Returns
+ -------
+ tuple
+ range, azimuth and elevation in meters, degrees and degrees
+ """
+ # range calculation
+ r = np.sqrt(x**2 + y**2 + z**2)
+
+ # azimuth calculation
+ azimuth = np.arctan2(y, x)
+
+ # elevation calculation
+ elevation = np.arcsin(z / r)
+
+ return r, np.degrees(azimuth), np.degrees(elevation)
+
+
+def polar_to_cartesian(r: np.ndarray, azimuth: np.ndarray, elevation: np.ndarray) -> tuple:
+ """
+ Converts polar coordinates to cartesian coordinates.
+
+ Parameters
+ ----------
+ r : np.ndarray
+ range in meters
+ azimuth : np.ndarray
+ azimuth in degrees
+ elevation : np.ndarray
+ elevation in degrees
+
+ Returns
+ -------
+ tuple
+ x, y and z coordinates in meters
+ """
+ azimuth = np.radians(azimuth)
+ elevation = np.radians(elevation)
+ x = r * np.cos(elevation) * np.cos(azimuth)
+ y = r * np.cos(elevation) * np.sin(azimuth)
+ z = r * np.sin(elevation)
+
+ return x, y, z
+
+
+def get_rotation_matrix(around_z, around_y):
+ """
+ Rotates with the right hand rule around the z axis (similar to simulator azimuth)
+ and with the right hand rule around the y axis (similar to simulator elevation)
+ """
+ alpha = np.deg2rad(around_z)
+ beta = np.deg2rad(around_y)
+
+ ry = np.matrix([
+ [np.cos(beta), 0.0, np.sin(beta)],
+ [0.0, 1.0, 0.0],
+ [-np.sin(beta), 0.0, np.cos(beta)],
+ ])
+ rz = np.matrix([
+ [np.cos(alpha), -np.sin(alpha), 0.0],
+ [np.sin(alpha), np.cos(alpha), 0.0],
+ [0.0, 0.0, 1.0],
+ ])
+
+ return rz * ry
+
+
+def rotate_angles_based_on_new_nadir(elev, azim, nadir_elev, nadir_azim):
+ """
+ Receives elevation and azimuth 2d, rotates around base
+ so that base_elev <> 0deg and base_azim <> 0deg
+ elevation being 0 at horizon (xy plane) and azimuth 0 at x axis
+
+ Returns
+ ------
+ elevation, azimuth
+ (xy plane elevation)
+ """
+ # translating to normal polar coordinate system, with theta being angle from z axis
+ # and phi being angle from x axis in the xy plane
+ nadir_theta = 90 - nadir_elev
+ nadir_phi = nadir_azim
+
+ nadir_point = np.matrix([
+ np.sin(np.deg2rad(nadir_theta)) * np.cos(np.deg2rad(nadir_phi)),
+ np.sin(np.deg2rad(nadir_theta)) * np.sin(np.deg2rad(nadir_phi)),
+ np.cos(np.deg2rad(nadir_theta)),
+ ])
+ # first rotate around y axis nadir_theta-180 to reach new theta
+ # since nadir_theta in (0,180), rotation will end up to azimuth=0
+ # so we rotate it around z axis nadir_phi
+ rotation_matrix = get_rotation_matrix(nadir_phi, nadir_theta - 180)
+
+ theta = 90 - elev
+ phi = azim
+
+ phi_rad = np.ravel(np.array([np.deg2rad(phi)]))
+ theta_rad = np.ravel(np.array([np.deg2rad(theta)]))
+
+ points = np.matrix([
+ np.sin(theta_rad) * np.cos(phi_rad),
+ np.sin(theta_rad) * np.sin(phi_rad),
+ np.cos(theta_rad),
+ ])
+
+ rotated_points = rotation_matrix @ points
+
+ rotated_phi = np.ravel(
+ np.asarray(
+ np.rad2deg(
+ np.arctan2(rotated_points[1], rotated_points[0]),
+ ),
+ ),
+ )
+ rotated_theta = np.ravel(
+ np.asarray(
+ np.rad2deg(np.arccos(rotated_points[2])),
+ ),
+ )
+
+ # back to elevation = 0 deg at xy plane, 90 at zenith and -90 at nadir
+ res_elev = 90 - rotated_theta
+
+ return res_elev, rotated_phi
+
+
+class GeometryConverter():
+ """
+ This is a Singleton. set_reference should be called once per simulation/snapshot.
+
+ Alert:
+ Every conversion between polar and geodesic should be intermediated by cartesian.
+ Every transformation should be done either at cartesian or polar.
+ Ignore at your own risk (and sadness)
+ """
+ def __init__(self):
+ # geodesical
+ self.ref_lat = None
+ self.ref_long = None
+ self.ref_alt = None
+
+ # cartesian
+ self.ref_x = None
+ self.ref_y = None
+ self.ref_z = None
+
+ # polar
+ self.ref_r = None
+ self.ref_azim = None
+ self.ref_elev = None
+
+ def get_translation(self):
+ return self.ref_r
+
+ def validate(self):
+ if None in [self.ref_elev, self.ref_azim, self.ref_r]:
+ raise ValueError("You need to set a reference for coordinate transformation before using it")
+
+ def set_reference(self, ref_lat: float, ref_long: float, ref_alt: float):
+ self.ref_lat = ref_lat
+ self.ref_long = ref_long
+ self.ref_alt = ref_alt
+ ref_x, ref_y, ref_z = lla2ecef(self.ref_lat, self.ref_long, self.ref_alt)
+ self.ref_x = ref_x
+ self.ref_y = ref_y
+ self.ref_z = ref_z
+
+ # polar coordinates
+ # geodesic doesn't necessarily translate one to one here
+ # so we use cartesian as intermediary
+ self.ref_r, self.ref_azim, self.ref_elev = cartesian_to_polar(ref_x, ref_y, ref_z)
+
+ def convert_cartesian_to_transformed_cartesian(
+ self, x, y, z, *, translate=None
+ ):
+ """
+ Transforms points by the same transformation required to bring reference to (0,0,0)
+ You can only rotate by specifying translate=0
+ """
+ ref_elev = self.ref_elev
+ ref_azim = self.ref_azim
+ ref_r = self.ref_r
+
+ self.validate()
+
+ # calculate distances to the centre of the Earth
+ dist_sat_centre_earth_km, azim, elev = cartesian_to_polar(x, y, z)
+
+ dist_imt_centre_earth = translate
+ if translate is None:
+ dist_imt_centre_earth = ref_r
+
+ # calculate Cartesian coordinates of , with origin at centre of the Earth,
+ # but considering the x axis at same longitude as the ref_long
+ # so that we can rotate around y to bring reference to top
+ sat_lat_rad = np.deg2rad(elev)
+ # consider coordinates rotating ref_long clockwise around z axis
+ imt_long_diff_rad = np.deg2rad(azim - ref_azim)
+ x1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad)
+ y1 = dist_sat_centre_earth_km * \
+ np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad)
+
+ # didn't transform, shoud eq height
+ z1 = dist_sat_centre_earth_km * np.sin(sat_lat_rad)
+
+ # rotate axis and calculate coordinates with origin at IMT system
+ imt_lat_rad = np.deg2rad(ref_elev)
+ x2 = (
+ x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)
+ )
+
+ y2 = y1
+
+ z2 = (
+ z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad)
+ ) - dist_imt_centre_earth
+
+ return (x2, y2, z2)
+
+ def revert_transformed_cartesian_to_cartesian(
+ self, x2, y2, z2, *, translate=None
+ ):
+ """
+ Reverses transformed points by the same transformation required to bring reference to (0,0,0)
+ You can only rotate by specifying translate=0. You need to use the same 'translate' value used
+ in transformation if you wish to reverse the transformation correctly
+ """
+ ref_elev = self.ref_elev
+ ref_azim = self.ref_azim
+ ref_r = self.ref_r
+
+ self.validate()
+
+ # rotate axis and calculate coordinates with origin at IMT system
+ imt_lat_rad = np.deg2rad(ref_elev)
+
+ dist_imt_centre_earth = translate
+ if translate is None:
+ dist_imt_centre_earth = ref_r
+
+ # transposed transformation matrix
+ z2 = z2 + dist_imt_centre_earth
+ y1 = y2
+ x1 = x2 * np.sin(imt_lat_rad) + z2 * np.cos(imt_lat_rad)
+ z1 = z2 * np.sin(imt_lat_rad) - x2 * np.cos(imt_lat_rad)
+
+ dist_sat_centre_earth_km = np.sqrt(x1 * x1 + z1 * z1 + y1 * y1)
+ sat_lat_rad = np.arcsin(z1 / dist_sat_centre_earth_km)
+
+ imt_long_diff_rad = np.arctan2(
+ y1, x1
+ )
+
+ # calculate distances to the centre of the Earth
+ x, y, z = polar_to_cartesian(
+ dist_sat_centre_earth_km,
+ np.rad2deg(imt_long_diff_rad) + ref_azim,
+ np.rad2deg(sat_lat_rad)
+ )
+
+ return (x, y, z)
+
+ def convert_lla_to_transformed_cartesian(
+ self, lat: np.array, long: np.array, alt: np.array
+ ):
+ """
+ You cannot transform this back to lla and expect something useful...
+ This rotates and translates every point considering the reference that was set
+ and a geodesical coordinate system
+ """
+ # get cartesian position by geodesical
+ x, y, z = lla2ecef(lat, long, alt)
+
+ return self.convert_cartesian_to_transformed_cartesian(x, y, z)
+
+ def convert_station_3d_to_2d(
+ self, station: StationManager, idx=None
+ ) -> None:
+ """
+ In place rotate and translate all coordinates so that reference parameters end up in (0,0,0)
+ and stations end up in same relative position according to each other,
+ adapting their angles to the rotation.
+
+ if idx is specified, only stations[idx] will be converted
+ """
+ # transform positions
+ # print("(station.x, station.y, station.z)", (station.x[idx], station.y[idx], station.z[idx]))
+ if idx is None:
+ nx, ny, nz = self.convert_cartesian_to_transformed_cartesian(station.x, station.y, station.z)
+ else:
+ nx, ny, nz = self.convert_cartesian_to_transformed_cartesian(station.x[idx], station.y[idx], station.z[idx])
+
+ if idx is None:
+ azim = station.azimuth
+ elev = station.elevation
+ else:
+ azim = station.azimuth[idx]
+ elev = station.elevation[idx]
+
+ r = 1
+ # then get pointing vec
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = polar_to_cartesian(r, azim, elev)
+
+ # transform pointing vectors, without considering geodesical earth coord system
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = self.convert_cartesian_to_transformed_cartesian(
+ pointing_vec_x, pointing_vec_y, pointing_vec_z, translate=0
+ )
+
+ if idx is None:
+ station.x = nx
+ station.y = ny
+ station.z = nz
+
+ _, station.azimuth, station.elevation = cartesian_to_polar(pointing_vec_x, pointing_vec_y, pointing_vec_z)
+ else:
+ station.x[idx] = nx
+ station.y[idx] = ny
+ station.z[idx] = nz
+
+ _, azimuth, elevation = cartesian_to_polar(pointing_vec_x, pointing_vec_y, pointing_vec_z)
+
+ station.azimuth[idx] = azimuth
+ station.elevation[idx] = elevation
+
+ def revert_station_2d_to_3d(
+ self, station: StationManager, idx=None
+ ) -> None:
+ """
+ In place rotate and translate all coordinates so that reference parameters end up in (0,0,0)
+ and stations end up in same relative position according to each other,
+ adapting their angles to the rotation.
+
+ if idx is specified, only stations[idx] will be converted
+ """
+ # transform positions
+ # print("(station.x, station.y, station.z)", (station.x[idx], station.y[idx], station.z[idx]))
+ if idx is None:
+ nx, ny, nz = self.revert_transformed_cartesian_to_cartesian(station.x, station.y, station.z)
+ else:
+ nx, ny, nz = self.revert_transformed_cartesian_to_cartesian(station.x[idx], station.y[idx], station.z[idx])
+
+ if idx is None:
+ azim = station.azimuth
+ elev = station.elevation
+ else:
+ azim = station.azimuth[idx]
+ elev = station.elevation[idx]
+
+ r = 1
+ # then get pointing vec
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = polar_to_cartesian(r, azim, elev)
+
+ # transform pointing vectors, without considering geodesical earth coord system
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = self.revert_transformed_cartesian_to_cartesian(
+ pointing_vec_x, pointing_vec_y, pointing_vec_z, translate=0
+ )
+
+ if idx is None:
+ station.x = nx
+ station.y = ny
+ station.z = nz
+
+ _, station.azimuth, station.elevation = cartesian_to_polar(pointing_vec_x, pointing_vec_y, pointing_vec_z)
+ else:
+ station.x[idx] = nx
+ station.y[idx] = ny
+ station.z[idx] = nz
+
+ _, azimuth, elevation = cartesian_to_polar(pointing_vec_x, pointing_vec_y, pointing_vec_z)
+
+ station.azimuth[idx] = azimuth
+ station.elevation[idx] = elevation
+
+
+def get_lambert_equal_area_crs(polygon: shp.geometry.Polygon):
+ centroid = polygon.centroid
+ return pyproj.CRS.from_user_input(
+ f"+proj=laea +lat_0={centroid.y} +lon_0={centroid.x} +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
+ )
+
+
+def shrink_country_polygon_by_km(
+ polygon: shp.geometry.Polygon, km: float
+) -> shp.geometry.Polygon:
+ """
+ Projects a Polygon in "EPSG:4326" to Lambert Azimuthal Equal Area projection,
+ shrinks the polygon by x km,
+ projects the polygon back to EPSG:4326.
+
+ Hint:
+ Check for polygon validity after transformation:
+ if poly.is_valid: raise Exception("bad polygon")
+ if not poly.is_empty and poly.area > 0: continue # ignore
+ ...
+ """
+ # Lambert is more precise, but could prob. get UTM projection
+ # Didn't see any practical difference for current use cases
+ proj_crs = get_lambert_equal_area_crs(polygon)
+
+ # Create transformer objects
+ # NOTE: important always_xy=True to not mix lat lon up order
+ to_proj = pyproj.Transformer.from_crs("EPSG:4326", proj_crs, always_xy=True).transform
+ from_proj = pyproj.Transformer.from_crs(proj_crs, "EPSG:4326", always_xy=True).transform
+
+ # Transform to projection where unit is meters
+ polygon_proj = shp.ops.transform(to_proj, polygon)
+
+ # Shrink (negative buffer in meters)
+ polygon_proj_shrunk = polygon_proj.buffer(-km * 1000)
+
+ # Return to EPSG:4326
+ return shp.ops.transform(from_proj, polygon_proj_shrunk)
+
+
+def shrink_countries_by_km(
+ countries: list[shp.geometry.MultiPolygon],
+ km: float
+) -> list[shp.geometry.MultiPolygon]:
+ """
+ Receives a MultiPolygon containing multiple countries
+ and diminishes
+ """
+ polys = []
+
+ for ext_poly in countries:
+ if ext_poly.geom_type == 'Polygon':
+ polys.append(shrink_country_polygon_by_km(ext_poly, km))
+ elif ext_poly.geom_type == 'MultiPolygon':
+ polys.append(shp.ops.unary_union([
+ shrink_country_polygon_by_km(poly, km) for poly in ext_poly.geoms
+ ]))
+
+ for poly in polys:
+ if not poly.is_valid:
+ # may be ignorable..?
+ # TODO: check if this error can be safely removed
+ # If you need to look into this, plot the erroring scenario
+ # drops and check to see if unexpected behavior occurs
+ raise ValueError("SOME BAD POLYGON")
+
+ return [
+ poly for poly in polys if poly.is_valid and not poly.is_empty and poly.area > 0
+ ]
+
+
+if __name__ == "__main__":
+ # baixo, direita, frente, esquerda, atrรกs, cima, cima
+ # elev = np.array([-90., 0., 0., 0., 0., 90., 90.])
+ # azim = np.array([0., 0., 90., 180., -90., 0., 90.])
+ elev = np.array([-89.93761622])
+ azim = np.array([180.])
+ n = len(azim)
+
+ # print("pointing nadir to the right (rotated around earth to the left)")
+ # print(np.concatenate((elev, azim)).reshape((2, n)).transpose())
+ # res_elev, res_az = rotate_angles_based_on_new_nadir(elev, azim, 0, 0)
+ # res = np.concatenate((res_elev, res_az)).reshape((2, n)).transpose()
+ # print(res)
+
+ # print("pointing nadir to the left (rotated around earth to the right)")
+ # print(np.concatenate((elev, azim)).reshape((2, n)).transpose())
+ # res_elev, res_az = rotate_angles_based_on_new_nadir(elev, azim, 0, 180)
+ # res = np.concatenate((res_elev, res_az)).reshape((2, n)).transpose()
+ # print(res)
+
+ # print(get_rotation_matrix_between_vecs(np.array([0,1,0]), np.array([0,0,1])))
+
+ geoconv = GeometryConverter()
+
+ sys_lat = 89
+ sys_long = 0
+ sys_alt = 1200
+
+ # geoconv.set_reference(
+ # sys_lat, sys_long, sys_alt
+ # )
+ # stat = StationManager(1)
+ # # stat.x = np.array([-2000.])
+ # # stat.y = np.array([0.])
+ # stat.x = np.array([0.])
+ # stat.y = np.array([-2000.])
+ # stat.z = np.array([0.])
+ # stat.elevation = 0
+ # stat.azimuth = -90
+
+ # print("stat.x", stat.x)
+ # print("stat.y", stat.y)
+ # print("stat.z", stat.z)
+
+ # print("stat.azimuth", stat.azimuth)
+ # print("stat.elevation", stat.elevation)
+ # print("#########")
+ # geoconv.convert_station_3d_to_2d(stat)
+ # print("#########")
+
+ # print("stat.x", stat.x)
+ # print("stat.y", stat.y)
+ # print("stat.z", stat.z)
+
+ # print("stat.azimuth", stat.azimuth)
+ # print("stat.elevation", stat.elevation)
diff --git a/sharc/support/sharc_utils.py b/sharc/support/sharc_utils.py
new file mode 100644
index 000000000..9bc643236
--- /dev/null
+++ b/sharc/support/sharc_utils.py
@@ -0,0 +1,18 @@
+def is_float(s: str) -> bool:
+ """Check if string represents a float value
+
+ Parameters
+ ----------
+ s : str
+ input string
+
+ Returns
+ -------
+ bool
+ whether the string is a float value or not
+ """
+ try:
+ float(s)
+ return True
+ except ValueError:
+ return False
diff --git a/sharc/thread_simulation.py b/sharc/thread_simulation.py
index 0c2358695..192570daa 100644
--- a/sharc/thread_simulation.py
+++ b/sharc/thread_simulation.py
@@ -10,46 +10,47 @@
import time
from threading import Thread, Event
+
class ThreadSimulation(Thread):
"""
This class extends the Thread class and controls the simulation (start/stop)
-
+
Attributes
----------
_stop (Event) : This flag is used to control when simulation is stopped
by user
model (Model) : Reference to the Model implementation of MVC
"""
-
+
def __init__(self, model: Model):
Thread.__init__(self)
self.model = model
self.stop_flag = Event()
-
+
def stop(self):
"""
- This is called by the controller when it receives the stop command by
- view. This method sets the stop flag that is checked during the
+ This is called by the controller when it receives the stop command by
+ view. This method sets the stop flag that is checked during the
simulation. Simulation stops when stop flag is set.
"""
self.stop_flag.set()
-
+
def is_stopped(self) -> bool:
"""
Checks if stop flag is set.
-
+
Returns
-------
True if simulation is stopped
"""
return self.stop_flag.isSet()
-
+
def run(self):
"""
This is overriden from base class and represents the thread's activity.
"""
start = time.perf_counter()
-
+
self.model.initialize()
while not self.model.is_finished() and not self.is_stopped():
self.model.snapshot()
@@ -57,7 +58,8 @@ def run(self):
# calculates simulation time when it finishes and sets the elapsed time
end = time.perf_counter()
elapsed_time = time.gmtime(end - start)
- self.model.set_elapsed_time(time.strftime("%H h %M min %S seg", elapsed_time))
-
-
-
\ No newline at end of file
+ self.model.set_elapsed_time(
+ time.strftime(
+ "%H h %M min %S seg", elapsed_time,
+ ),
+ )
diff --git a/sharc/topology/__init__.py b/sharc/topology/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/topology/__init__.py
+++ b/sharc/topology/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/sharc/topology/topology.py b/sharc/topology/topology.py
index 32499b1b2..2d72e1410 100644
--- a/sharc/topology/topology.py
+++ b/sharc/topology/topology.py
@@ -9,13 +9,16 @@
import numpy as np
import matplotlib.axes
+
class Topology(object):
__metaclass__ = ABCMeta
- def __init__(self,
- intersite_distance: float,
- cell_radius: float):
+ def __init__(
+ self,
+ intersite_distance: float,
+ cell_radius: float,
+ ):
self.intersite_distance = intersite_distance
self.cell_radius = cell_radius
@@ -23,24 +26,30 @@ def __init__(self,
# is equivalent to a sector (hexagon) in the macrocell topology
self.x = np.empty(0)
self.y = np.empty(0)
+ self.z = np.empty(0)
self.azimuth = np.empty(0)
self.indoor = np.empty(0)
+ self.is_space_station = False
self.num_base_stations = -1
self.static_base_stations = False
-
@abstractmethod
def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
"""
Calculates the coordinates of the stations according to the class
atributes.
"""
- pass
+ # by default, a sharc topology will translate the UE distribution by the BS position
+ def transform_ue_xyz(self, bs_i: int, x: np.array, y: np.array, z: np.array):
+ return (
+ x + self.x[bs_i],
+ y + self.y[bs_i],
+ z + self.z[bs_i],
+ )
@abstractmethod
def plot(self, ax: matplotlib.axes.Axes):
"""
Plots the topology on the given axis.
"""
- pass
diff --git a/sharc/topology/topology_factory.py b/sharc/topology/topology_factory.py
index 1d4f5503e..85750c03d 100644
--- a/sharc/topology/topology_factory.py
+++ b/sharc/topology/topology_factory.py
@@ -8,23 +8,54 @@
from sharc.topology.topology import Topology
from sharc.topology.topology_macrocell import TopologyMacrocell
+from sharc.topology.topology_imt_mss_dc import TopologyImtMssDc
from sharc.topology.topology_hotspot import TopologyHotspot
from sharc.topology.topology_indoor import TopologyIndoor
+from sharc.topology.topology_ntn import TopologyNTN
from sharc.topology.topology_single_base_station import TopologySingleBaseStation
from sharc.parameters.parameters import Parameters
+from sharc.support.sharc_geom import GeometryConverter
+
class TopologyFactory(object):
-
+
@staticmethod
- def createTopology(parameters: Parameters) -> Topology:
- if parameters.imt.topology == "SINGLE_BS":
- return TopologySingleBaseStation(parameters.imt.intersite_distance*2/3, parameters.imt.num_clusters)
- elif parameters.imt.topology == "MACROCELL":
- return TopologyMacrocell(parameters.imt.intersite_distance, parameters.imt.num_clusters)
- elif parameters.imt.topology == "HOTSPOT":
- return TopologyHotspot(parameters.hotspot, parameters.imt.intersite_distance, parameters.imt.num_clusters)
- elif parameters.imt.topology == "INDOOR":
- return TopologyIndoor(parameters.indoor)
+ def createTopology(parameters: Parameters, geometry_converter: GeometryConverter) -> Topology:
+ if parameters.imt.topology.type == "SINGLE_BS":
+ return TopologySingleBaseStation(
+ parameters.imt.topology.single_bs.cell_radius,
+ parameters.imt.topology.single_bs.num_clusters
+ )
+ elif parameters.imt.topology.type == "MACROCELL":
+ return TopologyMacrocell(
+ parameters.imt.topology.macrocell.intersite_distance,
+ parameters.imt.topology.macrocell.num_clusters
+ )
+ elif parameters.imt.topology.type == "HOTSPOT":
+ return TopologyHotspot(
+ parameters.imt.topology.hotspot,
+ parameters.imt.topology.hotspot.intersite_distance,
+ parameters.imt.topology.hotspot.num_clusters
+ )
+ elif parameters.imt.topology.type == "INDOOR":
+ return TopologyIndoor(parameters.imt.topology.indoor)
+ elif parameters.imt.topology.type == "NTN":
+ return TopologyNTN(
+ parameters.imt.topology.ntn.intersite_distance,
+ parameters.imt.topology.ntn.cell_radius,
+ parameters.imt.topology.ntn.bs_height,
+ parameters.imt.topology.ntn.bs_azimuth,
+ parameters.imt.topology.ntn.bs_elevation,
+ parameters.imt.topology.ntn.num_sectors,
+ )
+ elif parameters.imt.topology.type == "MSS_DC":
+ return TopologyImtMssDc(
+ parameters.imt.topology.mss_dc,
+ geometry_converter
+ )
else:
- sys.stderr.write("ERROR\nInvalid topology: " + parameters.imt.topology)
- sys.exit(1)
+ sys.stderr.write(
+ "ERROR\nInvalid topology: " +
+ parameters.imt.topology,
+ )
+ sys.exit(1)
diff --git a/sharc/topology/topology_hotspot.py b/sharc/topology/topology_hotspot.py
index da68d7d59..4e15fa10f 100644
--- a/sharc/topology/topology_hotspot.py
+++ b/sharc/topology/topology_hotspot.py
@@ -16,7 +16,7 @@
from sharc.topology.topology import Topology
from sharc.topology.topology_macrocell import TopologyMacrocell
-from sharc.parameters.parameters_hotspot import ParametersHotspot
+from sharc.parameters.imt.parameters_hotspot import ParametersHotspot
class TopologyHotspot(Topology):
@@ -56,28 +56,35 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
y = np.empty(0)
azimuth = np.empty(0)
for cell_x, cell_y, cell_azimuth in zip(self.macrocell.x, self.macrocell.y, self.macrocell.azimuth):
- #print("base station #{}".format(i))
+ # print("base station #{}".format(i))
i += 1
# find the center coordinates of the sector (hexagon)
- macro_cell_x = cell_x + self.macrocell.intersite_distance/3*math.cos(math.radians(cell_azimuth))
- macro_cell_y = cell_y + self.macrocell.intersite_distance/3*math.sin(math.radians(cell_azimuth))
+ macro_cell_x = cell_x + self.macrocell.intersite_distance / \
+ 3 * math.cos(math.radians(cell_azimuth))
+ macro_cell_y = cell_y + self.macrocell.intersite_distance / \
+ 3 * math.sin(math.radians(cell_azimuth))
# Hotspots are generated inside an inscribed circle of a regular hexagon (sector).
# The backoff factor (1.0) controls the overlapping rate between hotspots
# coverage areas (overlapping of hotspots in different macro cells)
- r = np.maximum(0, (self.macrocell.intersite_distance/3)*np.sqrt(3)/2 - self.param.max_dist_hotspot_ue/1.0)
+ r = np.maximum(
+ 0, (self.macrocell.intersite_distance / 3) *
+ np.sqrt(3) / 2 - self.param.max_dist_hotspot_ue / 1.0,
+ )
hotspot_x = np.array(0)
hotspot_y = np.array(0)
hotspot_azimuth = np.array(0)
-
+
for hs in range(self.param.num_hotspots_per_cell):
num_attempts = 0
- while(True):
+ while (True):
# create one hotspot
- hotspot_radius = r*random_number_gen.rand(1)
- hotspot_angle = 2*np.pi*random_number_gen.rand(1)
- candidate_x = hotspot_radius*np.cos(hotspot_angle) + macro_cell_x
- candidate_y = hotspot_radius*np.sin(hotspot_angle) + macro_cell_y
- candidate_azimuth = 360*random_number_gen.rand(1)
+ hotspot_radius = r * random_number_gen.rand(1)
+ hotspot_angle = 2 * np.pi * random_number_gen.rand(1)
+ candidate_x = hotspot_radius * \
+ np.cos(hotspot_angle) + macro_cell_x
+ candidate_y = hotspot_radius * \
+ np.sin(hotspot_angle) + macro_cell_y
+ candidate_azimuth = 360 * random_number_gen.rand(1)
if hs == 0:
# the candidate is valid if it is the first to be created
hotspot_x = candidate_x
@@ -85,51 +92,68 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
hotspot_azimuth = candidate_azimuth
break
else:
- overlapping_hotspots = self.overlapping_hotspots(candidate_x,
- candidate_y,
- candidate_azimuth,
- hotspot_x,
- hotspot_y,
- hotspot_azimuth,
- self.cell_radius)
- min_dist_validated = self.validade_min_dist_bs_hotspot(candidate_x,
- candidate_y,
- self.macrocell.x,
- self.macrocell.y,
- self.param.min_dist_bs_hotspot)
- candidate_valid = (not overlapping_hotspots) and min_dist_validated
+ overlapping_hotspots = self.overlapping_hotspots(
+ candidate_x,
+ candidate_y,
+ candidate_azimuth,
+ hotspot_x,
+ hotspot_y,
+ hotspot_azimuth,
+ self.cell_radius,
+ )
+ min_dist_validated = self.validade_min_dist_bs_hotspot(
+ candidate_x,
+ candidate_y,
+ self.macrocell.x,
+ self.macrocell.y,
+ self.param.min_dist_bs_hotspot,
+ )
+ candidate_valid = (
+ not overlapping_hotspots
+ ) and min_dist_validated
if candidate_valid:
- hotspot_x = np.concatenate((hotspot_x, candidate_x))
- hotspot_y = np.concatenate((hotspot_y, candidate_y))
- hotspot_azimuth = np.concatenate((hotspot_azimuth, candidate_azimuth))
+ hotspot_x = np.concatenate(
+ (hotspot_x, candidate_x),
+ )
+ hotspot_y = np.concatenate(
+ (hotspot_y, candidate_y),
+ )
+ hotspot_azimuth = np.concatenate(
+ (hotspot_azimuth, candidate_azimuth),
+ )
break
else:
num_attempts = num_attempts + 1
-
+
if num_attempts > TopologyHotspot.MAX_NUM_LOOPS:
- sys.stderr.write("ERROR\nInfinite loop while creating hotspots.\nTry less hotspots per cell or greater macro cell intersite distance.\n")
+ sys.stderr.write(
+ "ERROR\nInfinite loop while creating hotspots.\n \
+ Try less hotspots per cell or greater macro cell intersite distance.\n",
+ )
sys.exit(1)
- #if num_attempts > 1: print("number of attempts: {}".format(num_attempts))
+ # if num_attempts > 1: print("number of attempts: {}".format(num_attempts))
x = np.concatenate([x, hotspot_x])
y = np.concatenate([y, hotspot_y])
- azimuth = np.concatenate([azimuth, hotspot_azimuth])
-
+ azimuth = np.concatenate([azimuth, hotspot_azimuth])
+
self.x = x
self.y = y
self.azimuth = azimuth
# In the end, we have to update the number of base stations
self.num_base_stations = len(self.x)
- self.indoor = np.zeros(self.num_base_stations, dtype = bool)
-
-
- def overlapping_hotspots(self,
- candidate_x: np.array,
- candidate_y: np.array,
- candidate_azimuth: np.array,
- set_x: np.array,
- set_y: np.array,
- set_azimuth: np.array,
- radius: float) -> bool:
+ self.z = np.zeros(self.num_base_stations)
+ self.indoor = np.zeros(self.num_base_stations, dtype=bool)
+
+ def overlapping_hotspots(
+ self,
+ candidate_x: np.array,
+ candidate_y: np.array,
+ candidate_azimuth: np.array,
+ set_x: np.array,
+ set_y: np.array,
+ set_azimuth: np.array,
+ radius: float,
+ ) -> bool:
"""
Evaluates the spatial relationships among hotspots and checks whether
the hotspot defined by (x, y, azimuth) intersects with any hotspot of
@@ -142,7 +166,7 @@ def overlapping_hotspots(self,
candidate_azimuth: horizontal angle of the candidate hotspot (orientation)
set_x: x-coordinates of the set of hotspots
set_y: y-coordinates of the set of hotspots
- set_azimuth: horizontal angle of the set of hotspots (orientation)
+ set_azimuth: horizontal angle of the set of hotspots (orientation)
radius: radius of all hotspots
Returns
@@ -157,20 +181,24 @@ def overlapping_hotspots(self,
set_points = list()
set_points.append((x, y))
for a in range(len(azimuth_values)):
- set_points.append((x + radius*math.cos(np.radians(azimuth + azimuth_values[a])),
- y + radius*math.sin(np.radians(azimuth + azimuth_values[a]))))
+ set_points.append((
+ x + radius * math.cos(np.radians(azimuth + azimuth_values[a])),
+ y + radius * math.sin(np.radians(azimuth + azimuth_values[a])),
+ ))
set_polygons.append(Polygon(set_points))
# Creating the candidate polygon
points = list()
points.append((candidate_x, candidate_y))
for a in range(len(azimuth_values)):
- points.append((candidate_x + radius*math.cos(np.radians(candidate_azimuth + azimuth_values[a])),
- candidate_y + radius*math.sin(np.radians(candidate_azimuth + azimuth_values[a]))))
+ points.append((
+ candidate_x + radius * math.cos(np.radians(candidate_azimuth + azimuth_values[a])),
+ candidate_y + radius * math.sin(np.radians(candidate_azimuth + azimuth_values[a])),
+ ))
polygon = Polygon(points)
-
- # Check if there is overlapping between the candidate hotspot and
- # any of the hotspots of the set. In other words, check if any polygons
+
+ # Check if there is overlapping between the candidate hotspot and
+ # any of the hotspots of the set. In other words, check if any polygons
# intersect
for p in range(len(set_polygons)):
overlapping = polygon.intersects(set_polygons[p])
@@ -182,13 +210,14 @@ def overlapping_hotspots(self,
# If this point is reached, then there is no intersection between polygons
return False
-
- def validade_min_dist_bs_hotspot(self,
- hotspot_x: np.array,
- hotspot_y: np.array,
- macrocell_x: np.array,
- macrocell_y: np.array,
- min_dist_bs_hotspot: float) -> bool:
+ def validade_min_dist_bs_hotspot(
+ self,
+ hotspot_x: np.array,
+ hotspot_y: np.array,
+ macrocell_x: np.array,
+ macrocell_y: np.array,
+ min_dist_bs_hotspot: float,
+ ) -> bool:
"""
Checks minimum 2D distance between macro cell base stations and
hotspots.
@@ -202,30 +231,35 @@ def validade_min_dist_bs_hotspot(self,
# Here we have a 2D matrix whose values indicates the distance between
# base station and hotspots. In this matrix, each line corresponds to
# a macro cell base station and each column corresponds to a hotspot
- distance = np.sqrt((hotspot_x - macrocell_x.reshape((-1, 1)))**2 +
- (hotspot_y - macrocell_y.reshape((-1, 1)))**2)
+ distance = np.sqrt(
+ (hotspot_x - macrocell_x.reshape((-1, 1)))**2 +
+ (hotspot_y - macrocell_y.reshape((-1, 1)))**2,
+ )
# count the number of values that are less than the minimum distance and
# return true if any value is equal os less than minimum 2D distance
# between macro cell base stations and hotspot centers
occ = np.where(distance < min_dist_bs_hotspot)[0]
return len(occ) == 0
-
def plot(self, ax: matplotlib.axes.Axes):
# plot macrocells
self.macrocell.plot(ax)
# plot hotspots
- plt.scatter(self.x, self.y, color='g', edgecolor="w", linewidth=0.5, label="Hotspot")
+ plt.scatter(
+ self.x, self.y, color='g', edgecolor="w",
+ linewidth=0.5, label="Hotspot",
+ )
# plot hotspots coverage area
for x, y, a in zip(self.x, self.y, self.azimuth):
- pa = patches.Wedge( (x, y), self.cell_radius, a-60, a+60, fill=False,
- edgecolor="green", linestyle='solid' )
+ pa = patches.Wedge(
+ (x, y), self.cell_radius, a - 60, a + 60, fill=False,
+ edgecolor="green", linestyle='solid',
+ )
ax.add_patch(pa)
-
if __name__ == '__main__':
param = ParametersHotspot()
param.num_hotspots_per_cell = 2
@@ -239,7 +273,10 @@ def plot(self, ax: matplotlib.axes.Axes):
topology = TopologyHotspot(param, intersite_distance, num_clusters)
topology.calculate_coordinates()
- fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 8), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
topology.plot(ax)
diff --git a/sharc/topology/topology_imt_mss_dc.py b/sharc/topology/topology_imt_mss_dc.py
new file mode 100644
index 000000000..197f90711
--- /dev/null
+++ b/sharc/topology/topology_imt_mss_dc.py
@@ -0,0 +1,764 @@
+
+"""
+This module implements an IMT Mobile Satellite Service (MSS) for Direct Connectivity (D2D) topology.
+
+It consists of a group of NGSO satellites that provide direct connectivity to user equipment (UE) on the ground.
+The Space Stations positions are generated from the Keplerian elements of the orbits in the OrbitModel class.
+Only a subset of Space Stations are used, which are the ones that are visible to the UE.
+After satellite visibility is calculated, the ECEF coordinates are transformed to a new cartesian coordinate system
+centered at the reference point defined in the GeometryConverter object.
+The azimuth and elevation angles are also rotated to the new coordinate system.
+The visible Space Stations are then used to generate the IMT Base Stations.
+"""
+
+import numpy as np
+import geopandas as gpd
+import shapely as shp
+import pyproj
+
+from sharc.topology.topology import Topology
+from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc
+from sharc.parameters.parameters_orbit import ParametersOrbit
+from sharc.satellite.ngso.orbit_model import OrbitModel
+from sharc.support.sharc_geom import GeometryConverter, rotate_angles_based_on_new_nadir
+from sharc.topology.topology_ntn import TopologyNTN
+from sharc.satellite.utils.sat_utils import calc_elevation
+from sharc.support.sharc_geom import lla2ecef, cartesian_to_polar, polar_to_cartesian
+
+
+class TopologyImtMssDc(Topology):
+ def __init__(self, params: ParametersImtMssDc, geometry_converter: GeometryConverter):
+ """Implements a IMT Mobile Satellite Service (MSS) for Direct Connectivity (D2D) topology.
+
+ Parameters
+ ----------
+ params : ParametersImtMssDc
+ Input parameters for the IMT MSS-DC topology
+ geometry_converter : GeometryConverter
+ GeometryConverter object that converts the ECEF coordintate system to one
+ centered at GeometryConverter.reference.
+ """
+ # That means the we need to pass the groud reference points to the base stations generator
+ self.is_space_station = True
+ self.num_sectors = params.num_beams
+
+ # Specific attributes
+ self.geometry_converter = geometry_converter
+ self.orbit_params = params
+ self.space_station_x = None
+ self.space_station_y = None
+ self.space_station_z = None
+
+ self.cell_radius = params.beam_radius
+ # TODO: check this:
+ self.intersite_distance = self.cell_radius * np.sqrt(3)
+
+ self.lat = None
+ self.lon = None
+ self.orbits = []
+
+ # Iterate through each orbit defined in the parameters
+ for param in self.orbit_params.orbits:
+ # Instantiate an OrbitModel for the current orbit
+ orbit = OrbitModel(
+ Nsp=param.sats_per_plane, # Satellites per plane
+ Np=param.n_planes, # Number of orbital planes
+ phasing=param.phasing_deg, # Phasing angle in degrees
+ long_asc=param.long_asc_deg, # Longitude of ascending node in degrees
+ omega=param.omega_deg, # Argument of perigee in degrees
+ delta=param.inclination_deg, # Orbital inclination in degrees
+ hp=param.perigee_alt_km, # Perigee altitude in kilometers
+ ha=param.apogee_alt_km, # Apogee altitude in kilometers
+ Mo=param.initial_mean_anomaly # Initial mean anomaly in degrees
+ )
+ self.orbits.append(orbit)
+
+ @staticmethod
+ def get_coordinates(
+ geometry_converter: GeometryConverter,
+ orbit_params: ParametersImtMssDc,
+ random_number_gen=np.random.RandomState(),
+ only_active=True,
+ ):
+ """
+ Computes the coordintates of the visible space stations
+ """
+ orbit_params.sat_is_active_if.validate("orbit_params.sat_is_active_if")
+ # Calculate the total number of satellites across all orbits
+ total_satellites = sum(orbit.n_planes * orbit.sats_per_plane for orbit in orbit_params.orbits)
+ if any([
+ not hasattr(orbit_params, attr)
+ for attr in ["sat_is_active_if", "orbits", "beam_radius", "num_beams", "center_beam_positioning"]
+ ]):
+ raise ValueError(
+ "Parameter passed to TopologyImtMssDc needs to contain all of the attributes:\n"
+ '["sat_is_active_if", "orbits", "beam_radius", "num_beams", "center_beam_positioning"]'
+ )
+
+ idx_orbit = np.zeros(total_satellites, dtype=int) # Add orbit index array
+
+ # List to store indices of active satellites
+ active_satellite_idxs = []
+
+ MAX_ITER = 10000 # Maximum iterations to find at least one visible satellite
+ i = 0 # Iteration counter for ensuring satellite visibility
+ while len(active_satellite_idxs) == 0:
+ # Initialize arrays to store satellite positions, angles and distance from center of earth
+ all_positions = {"R": [], "lat": [], "lon": [], "sx": [], "sy": [], "sz": []}
+ all_elevations = [] # Store satellite elevations
+ all_azimuths = [] # Store satellite azimuths
+
+ current_sat_idx = 0 # Index tracker for satellites across all orbits
+
+ # Iterate through each orbit defined in the parameters
+ for orbit_idx, param in enumerate(orbit_params.orbits):
+ orbit = OrbitModel(
+ Nsp=param.sats_per_plane, # Satellites per plane
+ Np=param.n_planes, # Number of orbital planes
+ phasing=param.phasing_deg, # Phasing angle in degrees
+ long_asc=param.long_asc_deg, # Longitude of ascending node in degrees
+ omega=param.omega_deg, # Argument of perigee in degrees
+ delta=param.inclination_deg, # Orbital inclination in degrees
+ hp=param.perigee_alt_km, # Perigee altitude in kilometers
+ ha=param.apogee_alt_km, # Apogee altitude in kilometers
+ Mo=param.initial_mean_anomaly # Initial mean anomaly in degrees
+ )
+ # Generate random positions for satellites in this orbit
+ pos_vec = orbit.get_orbit_positions_random(rng=random_number_gen)
+
+ # Determine the number of satellites in this orbit
+ num_satellites = len(pos_vec["sx"])
+
+ # Assign orbit index to satellites
+ idx_orbit[current_sat_idx:current_sat_idx + num_satellites] = orbit_idx
+
+ # Extract satellite positions and calculate distances
+ sx, sy, sz = pos_vec['sx'], pos_vec['sy'], pos_vec['sz']
+ r = np.sqrt(sx**2 + sy**2 + sz**2) # Distance from Earth's center
+
+ # When getting azimuth and elevation, we need to consider sx, sy and sz points
+ # from the center of earth to the satellite, and we need to point the satellite
+ # towards the center of earth
+ elevations = np.degrees(np.arcsin(-sz / r)) # Calculate elevation angles
+ azimuths = np.degrees(np.arctan2(-sy, -sx)) # Calculate azimuth angles
+
+ # Append satellite positions and angles to global lists
+ all_positions['lat'].extend(pos_vec['lat']) # Latitudes
+ all_positions['lon'].extend(pos_vec['lon']) # Longitudes
+ all_positions['sx'].extend(sx) # X-coordinates
+ all_positions['sy'].extend(sy) # Y-coordinates
+ all_positions['sz'].extend(sz) # Z-coordinates
+ all_positions["R"].extend(r)
+ all_elevations.extend(elevations) # Elevation angles
+ all_azimuths.extend(azimuths) # Azimuth angles
+
+ active_sats_mask = np.ones(len(pos_vec['lat']), dtype=bool)
+
+ if "MINIMUM_ELEVATION_FROM_ES" in orbit_params.sat_is_active_if.conditions:
+ # Calculate satellite visibility from base stations
+ elev_from_bs = calc_elevation(
+ geometry_converter.ref_lat, # Latitude of base station
+ pos_vec['lat'], # Latitude of satellites
+ geometry_converter.ref_long, # Longitude of base station
+ pos_vec['lon'], # Longitude of satellites
+ orbit.perigee_alt_km # Perigee altitude in kilometers
+ )
+
+ # Determine visible satellites based on minimum elevation angle
+ active_sats_mask = active_sats_mask & (
+ elev_from_bs.flatten() >= orbit_params.sat_is_active_if.minimum_elevation_from_es
+ )
+
+ if "MAXIMUM_ELEVATION_FROM_ES" in orbit_params.sat_is_active_if.conditions:
+ # no need to recalculate if already calculated above
+ if not "MINIMUM_ELEVATION_FROM_ES" in orbit_params.sat_is_active_if.conditions:
+ # Calculate satellite visibility from base stations
+ elev_from_bs = calc_elevation(
+ geometry_converter.ref_lat, # Latitude of base station
+ pos_vec['lat'], # Latitude of satellites
+ geometry_converter.ref_long, # Longitude of base station
+ pos_vec['lon'], # Longitude of satellites
+ orbit.perigee_alt_km # Perigee altitude in kilometers
+ )
+
+ # Determine visible satellites based on minimum elevation angle
+ active_sats_mask = active_sats_mask & (
+ elev_from_bs.flatten() <= orbit_params.sat_is_active_if.maximum_elevation_from_es
+ )
+
+ if "LAT_LONG_INSIDE_COUNTRY" in orbit_params.sat_is_active_if.conditions:
+ flat_active_lon = pos_vec["lon"].flatten()[active_sats_mask]
+ flat_active_lat = pos_vec["lat"].flatten()[active_sats_mask]
+
+ # create points(lon, lat) to compare to country
+ sats_points = gpd.points_from_xy(flat_active_lon, flat_active_lat, crs="EPSG:4326")
+
+ # Check if the satellite is inside the country polygon
+ polygon_mask = np.zeros_like(active_sats_mask)
+ polygon_mask[active_sats_mask] = sats_points.within(
+ orbit_params.sat_is_active_if.lat_long_inside_country.filter_polygon
+ )
+
+ active_sats_mask = active_sats_mask & polygon_mask
+
+ visible_sat_idxs = np.arange(
+ current_sat_idx, current_sat_idx + len(pos_vec['lat']), dtype=int
+ )[active_sats_mask]
+ active_satellite_idxs.extend(visible_sat_idxs)
+
+ # Update the index tracker for the next orbit
+ current_sat_idx += len(sx)
+
+ i += 1 # Increment iteration counter
+ if i >= MAX_ITER: # Check if maximum iterations reached
+ raise RuntimeError(
+ "Maximum iterations reached, and no satellite was selected within the minimum elevation criteria."
+ )
+ # We have the list of visible satellites, now create a Topolgy of this subset and move the coordinate system
+ # reference.
+ if only_active:
+ total_active_satellites = len(active_satellite_idxs)
+ space_station_x = np.squeeze(np.array(all_positions['sx']))[active_satellite_idxs] * 1e3 # Convert X-coordinates to meters
+ space_station_y = np.squeeze(np.array(all_positions['sy']))[active_satellite_idxs] * 1e3 # Convert Y-coordinates to meters
+ space_station_z = np.squeeze(np.array(all_positions['sz']))[active_satellite_idxs] * 1e3 # Convert Z-coordinates to meters
+ elevation = np.squeeze(np.array(all_elevations))[active_satellite_idxs] # Elevation angles
+ azimuth = np.squeeze(np.array(all_azimuths))[active_satellite_idxs] # Azimuth angles
+ # Store the latitude and longitude of the visible satellites for later use
+ lat = np.squeeze(np.array(all_positions['lat']))[active_satellite_idxs]
+ lon = np.squeeze(np.array(all_positions['lon']))[active_satellite_idxs]
+ else:
+ total_active_satellites = total_satellites
+ space_station_x = np.squeeze(np.array(all_positions['sx'])) * 1e3 # Convert X-coordinates to meters
+ space_station_y = np.squeeze(np.array(all_positions['sy'])) * 1e3 # Convert Y-coordinates to meters
+ space_station_z = np.squeeze(np.array(all_positions['sz'])) * 1e3 # Convert Z-coordinates to meters
+ elevation = np.squeeze(np.array(all_elevations)) # Elevation angles
+ azimuth = np.squeeze(np.array(all_azimuths)) # Azimuth angles
+ # Store the latitude and longitude of the visible satellites for later use
+ lat = np.squeeze(np.array(all_positions['lat']))
+ lon = np.squeeze(np.array(all_positions['lon']))
+
+ rx, ry, rz = lla2ecef(
+ np.squeeze(lat),
+ np.squeeze(lon),
+ 0
+ )
+ earth_radius = np.sqrt(rx * rx + ry * ry + rz * rz)
+ all_r = np.squeeze(np.array(all_positions['R'])) * 1e3
+ if only_active:
+ all_r = all_r[active_satellite_idxs]
+ sat_altitude = np.array(all_r - earth_radius)
+
+ # Convert the ECEF coordinates to the transformed cartesian coordinates and set the Space Station positions
+ # used to generetate the IMT Base Stations
+ space_station_x, space_station_y, space_station_z = \
+ geometry_converter.convert_cartesian_to_transformed_cartesian(space_station_x, space_station_y, space_station_z)
+
+ # Rotate the azimuth and elevation angles off the center beam the new transformed cartesian coordinates
+ r = 1
+ # transform pointing vectors, without considering geodesical earth coord system
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = polar_to_cartesian(r, azimuth, elevation)
+ pointing_vec_x, pointing_vec_y, pointing_vec_z = \
+ geometry_converter.convert_cartesian_to_transformed_cartesian(
+ pointing_vec_x, pointing_vec_y, pointing_vec_z, translate=0)
+ _, azimuth, elevation = cartesian_to_polar(pointing_vec_x, pointing_vec_y, pointing_vec_z)
+
+ # We borrow the TopologyNTN method to calculate the sectors azimuth and elevation angles from their
+ # respective x and y boresight coordinates
+ sx, sy = TopologyNTN.get_sectors_xy(
+ intersite_distance=orbit_params.beam_radius * np.sqrt(3),
+ num_sectors=orbit_params.num_beams
+ )
+
+ assert (len(sx) == orbit_params.num_beams)
+ assert (len(sy) == orbit_params.num_beams)
+
+ # we give num_beams sectors to each satellite
+ sx = np.resize(sx, orbit_params.num_beams * total_active_satellites)
+ sy = np.resize(sy, orbit_params.num_beams * total_active_satellites)
+
+ if orbit_params.center_beam_positioning.type == "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE":
+ match orbit_params.center_beam_positioning.angle_from_subsatellite_phi.type:
+ case "FIXED":
+ azim_add = np.repeat(orbit_params.center_beam_positioning.angle_from_subsatellite_phi.fixed, total_active_satellites)
+ case "~U(MIN,MAX)":
+ azim_add = random_number_gen.uniform(
+ orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.min,
+ orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.max,
+ total_active_satellites
+ )
+ case "~SQRT(U(0,1))*MAX":
+ azim_add = random_number_gen.uniform(
+ 0, 1,
+ total_active_satellites
+ ) * orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.max
+ case _:
+ raise ValueError(
+ f"mss_d2d_params.center_beam_positioning.angle_from_subsatellite_phi.type = \n"
+ f"'{orbit_params.center_beam_positioning.angle_from_subsatellite_phi.type}' is not recognized!"
+ )
+
+ match orbit_params.center_beam_positioning.distance_from_subsatellite.type:
+ case "FIXED":
+ subsatellite_distance_add = np.repeat(orbit_params.center_beam_positioning.distance_from_subsatellite.fixed, total_active_satellites)
+ case "~U(MIN,MAX)":
+ subsatellite_distance_add = random_number_gen.uniform(
+ orbit_params.center_beam_positioning.distance_from_subsatellite.distribution.min,
+ orbit_params.center_beam_positioning.distance_from_subsatellite.distribution.max,
+ total_active_satellites
+ )
+ case "~SQRT(U(0,1))*MAX":
+ subsatellite_distance_add = random_number_gen.uniform(
+ 0, 1,
+ total_active_satellites
+ ) * orbit_params.center_beam_positioning.distance_from_subsatellite.distribution.max
+ case _:
+ raise ValueError(
+ f"mss_d2d_params.center_beam_positioning.distance_from_subsatellite.type = \n"
+ f"'{orbit_params.center_beam_positioning.angle_from_subsatellite_theta.type}' is not recognized!"
+ )
+
+ elif orbit_params.center_beam_positioning.type == "ANGLE_FROM_SUBSATELLITE":
+ match orbit_params.center_beam_positioning.angle_from_subsatellite_theta.type:
+ case "FIXED":
+ off_nadir_add = np.repeat(orbit_params.center_beam_positioning.angle_from_subsatellite_theta.fixed, total_active_satellites)
+ case "~U(MIN,MAX)":
+ off_nadir_add = random_number_gen.uniform(
+ orbit_params.center_beam_positioning.angle_from_subsatellite_theta.distribution.min,
+ orbit_params.center_beam_positioning.angle_from_subsatellite_theta.distribution.max,
+ total_active_satellites
+ )
+ case "~SQRT(U(0,1))*MAX":
+ off_nadir_add = random_number_gen.uniform(
+ 0, 1,
+ total_active_satellites
+ ) * orbit_params.center_beam_positioning.angle_from_subsatellite_theta.distribution.max
+ case _:
+ raise ValueError(
+ f"mss_d2d_params.center_beam_positioning.angle_from_subsatellite_theta.type = \n"
+ f"'{orbit_params.center_beam_positioning.angle_from_subsatellite_theta.type}' is not recognized!"
+ )
+ subsatellite_distance_add = sat_altitude * np.tan(off_nadir_add)
+
+ match orbit_params.center_beam_positioning.angle_from_subsatellite_phi.type:
+ case "FIXED":
+ azim_add = np.repeat(orbit_params.center_beam_positioning.angle_from_subsatellite_phi.fixed, total_active_satellites)
+ case "~U(MIN,MAX)":
+ azim_add = random_number_gen.uniform(
+ orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.min,
+ orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.max,
+ total_active_satellites
+ )
+ case "~SQRT(U(0,1))*MAX":
+ azim_add = random_number_gen.uniform(
+ 0, 1,
+ total_active_satellites
+ ) * orbit_params.center_beam_positioning.angle_from_subsatellite_phi.distribution.max
+ case _:
+ raise ValueError(
+ f"mss_d2d_params.center_beam_positioning.angle_from_subsatellite_phi.type = \n"
+ f"'{orbit_params.center_beam_positioning.angle_from_subsatellite_phi.type}' is not recognized!"
+ )
+
+ subsatellite_distance_add = np.repeat(subsatellite_distance_add, orbit_params.num_beams)
+ azim_add = np.repeat(azim_add, orbit_params.num_beams)
+
+ sx += subsatellite_distance_add
+
+ # Calculate the azimuth and elevation angles for each beam
+ # as though their nadir is at (0,0)
+ # before rotating them
+ beams_azim = np.rad2deg(np.arctan2(sy, sx)) + azim_add
+ beams_elev = np.rad2deg(np.arctan2(np.sqrt(sy * sy + sx * sx),
+ np.repeat(sat_altitude, orbit_params.num_beams))
+ ) - 90
+
+ beams_azim = beams_azim.reshape(
+ (total_active_satellites, orbit_params.num_beams)
+ )
+
+ beams_elev = beams_elev.reshape(
+ (total_active_satellites, orbit_params.num_beams)
+ )
+
+ # Rotate and set the each beam azimuth and elevation angles - only for the visible satellites
+ for i in range(total_active_satellites):
+ # Rotate the azimuth and elevation angles based on the new nadir point
+ beams_elev[i], beams_azim[i] = rotate_angles_based_on_new_nadir(
+ beams_elev[i],
+ beams_azim[i],
+ elevation[i],
+ azimuth[i]
+ )
+
+ # In SHARC each sector is treated as a separate base station, so we need to repeat the satellite positions
+ # for each sector.
+ space_station_x = np.repeat(space_station_x, orbit_params.num_beams)
+ space_station_y = np.repeat(space_station_y, orbit_params.num_beams)
+ space_station_z = np.repeat(space_station_z, orbit_params.num_beams)
+
+ num_base_stations = orbit_params.num_beams * total_active_satellites
+ elevation = beams_elev.flatten()
+ azimuth = beams_azim.flatten()
+ lat = np.repeat(lat, orbit_params.num_beams)
+ lon = np.repeat(lon, orbit_params.num_beams)
+
+ altitudes = np.repeat(sat_altitude, orbit_params.num_beams)
+
+ assert (space_station_x.shape == (num_base_stations,))
+ assert (space_station_y.shape == (num_base_stations,))
+ assert (space_station_z.shape == (num_base_stations,))
+ assert (lat.shape == (num_base_stations,))
+ assert (lon.shape == (num_base_stations,))
+ assert (altitudes.shape == (num_base_stations,))
+ assert (elevation.shape == (num_base_stations,))
+ assert (azimuth.shape == (num_base_stations,))
+ assert (sx.shape == (num_base_stations,))
+ assert (sy.shape == (num_base_stations,))
+
+ # update indices (multiply by num_beams)
+ # and make all num_beams of satellite active
+ active_satellite_idxs = np.ravel(np.array(active_satellite_idxs)[:, np.newaxis] * orbit_params.num_beams +
+ np.arange(orbit_params.num_beams))
+
+ return {
+ "num_satellites": num_base_stations,
+ "num_active_satellites": len(active_satellite_idxs),
+ "active_satellites_idxs": active_satellite_idxs,
+ "sat_x": space_station_x,
+ "sat_y": space_station_y,
+ "sat_z": space_station_z,
+ "sat_lat": lat,
+ "sat_lon": lon,
+ "sat_alt": altitudes,
+ "sat_antenna_elev": elevation,
+ "sat_antenna_azim": azimuth,
+ "sectors_x": sx,
+ "sectors_y": sy,
+ "sectors_z": np.zeros_like(sx)
+ }
+
+ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
+ """
+ Computes the coordintates of the visible space stations
+ """
+ self.geometry_converter.validate()
+
+ sat_values = self.get_coordinates(self.geometry_converter, self.orbit_params, random_number_gen)
+
+ self.num_base_stations = sat_values["num_satellites"]
+
+ self.space_station_x = sat_values["sat_x"]
+ self.space_station_y = sat_values["sat_y"]
+ self.space_station_z = sat_values["sat_z"]
+ self.height = sat_values["sat_alt"]
+ self.lat = sat_values["sat_lat"]
+ self.lon = sat_values["sat_lon"]
+
+ self.elevation = sat_values["sat_antenna_elev"]
+ self.azimuth = sat_values["sat_antenna_azim"]
+
+ self.x = sat_values["sectors_x"]
+ self.y = sat_values["sectors_y"]
+ self.z = sat_values["sectors_z"]
+
+ self.indoor = np.zeros(self.num_base_stations, dtype=bool) # ofcourse, all are outdoor
+
+ return
+
+ # We can factor this out if another topology also ends up needing this
+ def transform_ue_xyz(self, bs_i, x, y, z):
+ x, y, z = super().transform_ue_xyz(bs_i, x, y, z)
+
+ # translate by earth radius on the lat long passed
+ # this way we mantain the center of topology on surface of earth
+ # considering a geodesic earth
+ # since we expect the area to be small, we can just consider
+ # the center of the topology for this translation
+ rx, ry, rz = lla2ecef(self.lat[bs_i], self.lon[bs_i], 0)
+ earth_radius_at_sat_nadir = np.sqrt(rx * rx + ry * ry + rz * rz)
+ z += earth_radius_at_sat_nadir
+
+ # get angle around y axis
+ around_y = np.arctan2(
+ np.sqrt(
+ self.space_station_x[bs_i] ** 2 +
+ self.space_station_y[bs_i] ** 2
+ ),
+ self.space_station_z[bs_i] + self.geometry_converter.get_translation()
+ )
+
+ # get around z axis
+ around_z = np.arctan2(self.space_station_y[bs_i], self.space_station_x[bs_i])
+
+ # rotating around y
+ nx = x * np.cos(around_y) + z * np.sin(around_y)
+ nz = x * -np.sin(around_y) + z * np.cos(around_y)
+ x = nx
+ z = nz
+
+ # now we have (x,y) = (A, 0)
+ # rotating around z
+ nx = x * np.cos(around_z) + y * -np.sin(around_z)
+ ny = x * np.sin(around_z) + y * np.cos(around_z)
+ x = nx
+ y = ny
+
+ # translate ue back so other system is in (0,0,0)
+ z -= self.geometry_converter.get_translation()
+
+ return (x, y, z)
+
+
+# Example usage
+if __name__ == '__main__':
+ from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc
+ from sharc.support.sharc_geom import GeometryConverter
+
+ # Define the parameters for the IMT MSS-DC topology
+ # SystemA Orbit parameters
+ orbit = ParametersOrbit(
+ n_planes=28,
+ sats_per_plane=120,
+ phasing_deg=1.5,
+ long_asc_deg=0.0,
+ inclination_deg=53.0,
+ perigee_alt_km=525,
+ apogee_alt_km=525
+ )
+ params = ParametersImtMssDc(
+ beam_radius=39745.0,
+ num_beams=19,
+ orbits=[orbit]
+ )
+ params.sat_is_active_if.conditions = [
+ "LAT_LONG_INSIDE_COUNTRY",
+ "MINIMUM_ELEVATION_FROM_ES",
+ ]
+ params.sat_is_active_if.minimum_elevation_from_es = 5
+ params.sat_is_active_if.lat_long_inside_country.country_names = ["Brazil"]
+
+ # Define the geometry converter
+ geometry_converter = GeometryConverter()
+ geometry_converter.set_reference(-15.0, -42.0, 1200)
+
+ # Instantiate the IMT MSS-DC topology
+ imt_mss_dc_topology = TopologyImtMssDc(params, geometry_converter)
+
+ # Calculate the coordinates of the space stations
+ rng = np.random.RandomState(101)
+ imt_mss_dc_topology.calculate_coordinates(random_number_gen=rng)
+
+ # Plot the IMT MSS-DC space stations after selecting the visible ones and transforming the coordinate system
+ import plotly.graph_objects as go
+
+ # Create a 3D scatter plot using Plotly
+ fig = go.Figure(data=[go.Scatter3d(
+ x=imt_mss_dc_topology.space_station_x / 1e3,
+ y=imt_mss_dc_topology.space_station_y / 1e3,
+ z=imt_mss_dc_topology.space_station_z / 1e3,
+ mode='markers',
+ marker=dict(
+ size=4,
+ color='red',
+ opacity=0.8
+ )
+ )])
+
+ # Set plot title and axis labels
+ fig.update_layout(
+ title='IMT MSS-DC Topology',
+ scene=dict(
+ xaxis_title='X [m]',
+ yaxis_title='Y [m]',
+ zaxis_title='Z [m]'
+ )
+ )
+
+ # Add a point at the origin
+ fig.add_trace(go.Scatter3d(
+ x=[0],
+ y=[0],
+ z=[0],
+ mode='markers',
+ marker=dict(
+ size=6,
+ color='blue',
+ opacity=1.0
+ ),
+ name='Reference'
+ ))
+ # Calculate the elevation with respect to the x-y plane
+ elevation_xy_plane = np.degrees(
+ np.arctan2(
+ imt_mss_dc_topology.space_station_z,
+ np.sqrt(imt_mss_dc_topology.space_station_x**2 + imt_mss_dc_topology.space_station_y**2)
+ )
+ )
+
+ # Add the elevation with respect to the x-y plane to the plot
+ fig.add_trace(go.Scatter3d(
+ x=imt_mss_dc_topology.space_station_x / 1e3,
+ y=imt_mss_dc_topology.space_station_y / 1e3,
+ z=imt_mss_dc_topology.space_station_z / 1e3,
+ mode='markers',
+ marker=dict(
+ size=4,
+ color=elevation_xy_plane,
+ colorscale='Viridis',
+ colorbar=dict(title='Elevation (degrees)', x=-0.1),
+ opacity=0.8
+ ),
+ name='Space Stations Positions'
+ ))
+
+ # Add lines between the origin and the IMT space stations
+ for x, y, z in zip(imt_mss_dc_topology.space_station_x / 1e3, imt_mss_dc_topology.space_station_y / 1e3,
+ imt_mss_dc_topology.space_station_z / 1e3):
+ fig.add_trace(go.Scatter3d(
+ x=[0, x],
+ y=[0, y],
+ z=[0, z],
+ mode='lines',
+ line=dict(color='green', width=2, dash='dash'),
+ name='Elevation Line'
+ ))
+ # Suppress the legend for the elevation plot
+ fig.update_traces(showlegend=False, selector=dict(name='Elevation Line'))
+
+ # Plot beam boresight vectors
+ boresight_length = 100 # Length of the boresight vectors for visualization
+ boresight_x, boresight_y, boresight_z = polar_to_cartesian(
+ boresight_length,
+ imt_mss_dc_topology.azimuth,
+ imt_mss_dc_topology.elevation
+ )
+ # Add arrow heads to the end of the boresight vectors
+ for x, y, z, bx, by, bz in zip(imt_mss_dc_topology.space_station_x / 1e3,
+ imt_mss_dc_topology.space_station_y / 1e3,
+ imt_mss_dc_topology.space_station_z / 1e3,
+ boresight_x,
+ boresight_y,
+ boresight_z):
+ fig.add_trace(go.Cone(
+ x=[x + bx],
+ y=[y + by],
+ z=[z + bz],
+ u=[bx],
+ v=[by],
+ w=[bz],
+ colorscale=[[0, 'orange'], [1, 'orange']],
+ sizemode='absolute',
+ sizeref=40,
+ showscale=False
+ ))
+ for x, y, z, bx, by, bz in zip(imt_mss_dc_topology.space_station_x / 1e3,
+ imt_mss_dc_topology.space_station_y / 1e3,
+ imt_mss_dc_topology.space_station_z / 1e3,
+ boresight_x,
+ boresight_y,
+ boresight_z):
+ fig.add_trace(go.Scatter3d(
+ x=[x, x + bx],
+ y=[y, y + by],
+ z=[z, z + bz],
+ mode='lines',
+ line=dict(color='orange', width=2),
+ name='Boresight'
+ ))
+ # Suppress the legend for the boresight plot
+ fig.update_traces(showlegend=False, selector=dict(name='Boresight'))
+
+ # Maintain axis proportions
+ fig.update_layout(scene_aspectmode='data')
+
+ fig.show()
+
+ # Plot the interception of the boresight vectors with the x-y plane
+ fig_intercept = go.Figure()
+
+ # Calculate the interception points of the boresight vectors with the x-y plane
+ t_intercept = -imt_mss_dc_topology.space_station_z / boresight_z
+ intercept_x = imt_mss_dc_topology.space_station_x + t_intercept * boresight_x
+ intercept_y = imt_mss_dc_topology.space_station_y + t_intercept * boresight_y
+
+ # Add the interception points to the plot
+ fig_intercept.add_trace(go.Scatter(
+ x=intercept_x / 1e3,
+ y=intercept_y / 1e3,
+ mode='markers',
+ marker=dict(
+ size=6,
+ color='purple',
+ opacity=0.8
+ ),
+ name='Interception Points'
+ ))
+
+ # Add the space station positions for reference
+ fig_intercept.add_trace(go.Scatter(
+ x=imt_mss_dc_topology.space_station_x / 1e3,
+ y=imt_mss_dc_topology.space_station_y / 1e3,
+ mode='markers',
+ marker=dict(
+ size=4,
+ color='red',
+ opacity=0.8
+ ),
+ name='Space Stations'
+ ))
+
+ # Set plot title and axis labels
+ fig_intercept.update_layout(
+ title='Interception of Boresight Vectors with x-y Plane',
+ xaxis_title='X [km]',
+ yaxis_title='Y [km]',
+ showlegend=True
+ )
+
+ # Maintain axis proportions
+ fig_intercept.update_yaxes(scaleanchor="x", scaleratio=1)
+
+ # Show the plot
+ fig_intercept.show()
+
+ # Plot the IMT MSS-DC space stations in a 2D plane
+ fig_2d = go.Figure()
+
+ # Add circles centered at the (x, y) coordinates of the space stations
+ for x, y in zip(imt_mss_dc_topology.x, imt_mss_dc_topology.y):
+ circle = go.Scatter(
+ x=[x / 1e3 + imt_mss_dc_topology.orbit_params.beam_radius /
+ 1e3 * np.cos(theta) for theta in np.linspace(0, 2 * np.pi, 100)],
+ y=[y / 1e3 + imt_mss_dc_topology.orbit_params.beam_radius /
+ 1e3 * np.sin(theta) for theta in np.linspace(0, 2 * np.pi, 100)],
+ mode='lines',
+ line=dict(color='blue')
+ )
+ fig_2d.add_trace(circle)
+
+ # Set plot title and axis labels
+ fig_2d.update_layout(
+ title='IMT MSS-DC Topology in x-y Plane',
+ xaxis_title='X [m]',
+ yaxis_title='Y [m]',
+ showlegend=False
+ )
+
+ # Maintain axis proportions
+ fig_2d.update_yaxes(scaleanchor="x", scaleratio=1)
+
+ # Show the plot
+ fig_2d.show()
+
+ # Print the elevation angles
+ print('Elevation angles:', imt_mss_dc_topology.elevation)
+ # Print the azimuth angles
+ print('Azimuth angles:', imt_mss_dc_topology.azimuth)
+ # Print the elevation w.r.t. the x-y plane
+ print('Elevation w.r.t. XY plane:', elevation_xy_plane)
+ # Print the slant range
+ idxs = np.arange(imt_mss_dc_topology.num_base_stations // imt_mss_dc_topology.num_sectors) * \
+ imt_mss_dc_topology.num_sectors
+ print('Slant range:', np.sqrt(imt_mss_dc_topology.space_station_x[idxs]**2 +
+ imt_mss_dc_topology.space_station_y[idxs]**2 +
+ imt_mss_dc_topology.space_station_z[idxs]**2) / 1e3)
diff --git a/sharc/topology/topology_indoor.py b/sharc/topology/topology_indoor.py
index 5b804f441..11d5ba611 100644
--- a/sharc/topology/topology_indoor.py
+++ b/sharc/topology/topology_indoor.py
@@ -6,24 +6,24 @@
"""
from sharc.topology.topology import Topology
-from sharc.parameters.parameters_indoor import ParametersIndoor
+from sharc.parameters.imt.parameters_indoor import ParametersIndoor
import matplotlib.pyplot as plt
import matplotlib.axes
import numpy as np
from itertools import product
+
class TopologyIndoor(Topology):
"""
Generates the coordinates of the sites based on the indoor network
- topology.
+ topology.
"""
-
def __init__(self, param: ParametersIndoor):
"""
Constructor method that sets the parameters.
-
+
Parameters
----------
param : parameters of the indoor topology
@@ -34,10 +34,10 @@ def __init__(self, param: ParametersIndoor):
self.b_w = 120
self.b_d = 50
self.b_h = 3
-
- cell_radius = param.intersite_distance/2
+
+ cell_radius = param.intersite_distance / 2
super().__init__(param.intersite_distance, cell_radius)
-
+
self.n_rows = param.n_rows
self.n_colums = param.n_colums
self.street_width = param.street_width
@@ -47,55 +47,65 @@ def __init__(self, param: ParametersIndoor):
self.num_floors = param.num_floors
if param.num_imt_buildings == 'ALL':
self.all_buildings = True
- self.num_imt_buildings = self.n_rows*self.n_colums
+ self.num_imt_buildings = self.n_rows * self.n_colums
else:
self.all_buildings = False
self.num_imt_buildings = int(param.num_imt_buildings)
self.imt_buildings = list()
- self.total_bs_level = self.num_imt_buildings*self.num_cells
-
+ self.total_bs_level = self.num_imt_buildings * self.num_cells
+
self.height = np.empty(0)
-
-
+
def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
"""
Calculates the coordinates of the stations according to the inter-site
- distance parameter. This method is invoked in all snapshots but it can
- be called only once for the indoor topology. So we set
+ distance parameter. This method is invoked in all snapshots but it can
+ be called only once for the indoor topology. So we set
static_base_stations to True to avoid unnecessary calculations.
"""
if not self.static_base_stations:
self.reset()
self.static_base_stations = self.all_buildings
-
- x_base = np.array([ (2*k + 1)*self.cell_radius for k in range(self.num_cells)])
- y_base = self.b_d/2*np.ones(self.num_cells)
-
+
+ x_base = np.array(
+ [(2 * k + 1) * self.cell_radius for k in range(self.num_cells)],
+ )
+ y_base = self.b_d / 2 * np.ones(self.num_cells)
+
# Choose random buildings
- all_buildings = list(product(range(self.n_rows),range(self.n_colums)))
+ all_buildings = list(
+ product(range(self.n_rows), range(self.n_colums)),
+ )
random_number_gen.shuffle(all_buildings)
self.imt_buildings = all_buildings[:self.num_imt_buildings]
-
+
floor_x = np.empty(0)
floor_y = np.empty(0)
for build in self.imt_buildings:
r = build[0]
c = build[1]
- floor_x = np.concatenate((floor_x, x_base + c*(self.b_w + self.street_width)))
- floor_y = np.concatenate((floor_y, y_base + r*(self.b_d + self.street_width)))
+ floor_x = np.concatenate(
+ (floor_x, x_base + c * (self.b_w + self.street_width)),
+ )
+ floor_y = np.concatenate(
+ (floor_y, y_base + r * (self.b_d + self.street_width)),
+ )
for f in range(self.num_floors):
self.x = np.concatenate((self.x, floor_x))
self.y = np.concatenate((self.y, floor_y))
- self.height = np.concatenate((self.height,
- (f+1)*self.b_h*np.ones_like(floor_x)))
+ self.height = np.concatenate((
+ self.height,
+ (f + 1) * self.b_h * np.ones_like(floor_x),
+ ))
# In the end, we have to update the number of base stations
- self.num_base_stations = len(self.x)
+ self.num_base_stations = len(self.x)
+ self.z = np.zeros_like(self.height) # height will be added to z in station_factory
self.azimuth = np.zeros(self.num_base_stations)
- self.indoor = np.ones(self.num_base_stations, dtype = bool)
-
+ self.indoor = np.ones(self.num_base_stations, dtype=bool)
+
def reset(self):
self.x = np.empty(0)
self.y = np.empty(0)
@@ -104,67 +114,84 @@ def reset(self):
self.indoor = np.empty(0)
self.num_base_stations = -1
self.static_base_stations = False
-
- def plot(self, ax: matplotlib.axes.Axes, top_view = True):
+
+ def plot(self, ax: matplotlib.axes.Axes, top_view=True):
if top_view:
self.plot_top_view(ax)
else:
self.plot_side_view(ax)
-
+
def plot_top_view(self, ax: matplotlib.axes.Axes):
# create the building
- for b in range(int(self.num_base_stations/self.num_cells)):
- x_b = self.x[self.num_cells*b] - self.cell_radius
- y_b = self.y[self.num_cells*b] - self.b_d/2
- points = list([[x_b, y_b],
- [x_b + self.b_w, y_b],
- [x_b + self.b_w, y_b + self.b_d],
- [x_b, y_b + + self.b_d]])
+ for b in range(int(self.num_base_stations / self.num_cells)):
+ x_b = self.x[self.num_cells * b] - self.cell_radius
+ y_b = self.y[self.num_cells * b] - self.b_d / 2
+ points = list([
+ [x_b, y_b],
+ [x_b + self.b_w, y_b],
+ [x_b + self.b_w, y_b + self.b_d],
+ [x_b, y_b + + self.b_d],
+ ])
sector = plt.Polygon(points, fill=None, edgecolor='k')
- ax.add_patch(sector)
+ ax.add_patch(sector)
for q in range(8):
points = list()
- x_b = self.x[self.num_cells*b] - self.cell_radius + q*15
- y_b = self.y[self.num_cells*b] + 10
- points.extend([[x_b, y_b],
- [x_b + 15, y_b],
- [x_b + 15, y_b + 15],
- [x_b, y_b + 15]])
+ x_b = self.x[self.num_cells * b] - self.cell_radius + q * 15
+ y_b = self.y[self.num_cells * b] + 10
+ points.extend([
+ [x_b, y_b],
+ [x_b + 15, y_b],
+ [x_b + 15, y_b + 15],
+ [x_b, y_b + 15],
+ ])
sector = plt.Polygon(points, fill=None, edgecolor='k')
ax.add_patch(sector)
-
+
for q in range(8):
points = list()
- x_b = self.x[self.num_cells*b] - self.cell_radius + q*15
- y_b = self.y[self.num_cells*b] - self.b_d/2
- points.extend([[x_b, y_b],
- [x_b + 15, y_b],
- [x_b + 15, y_b + 15],
- [x_b, y_b + 15]])
+ x_b = self.x[self.num_cells * b] - self.cell_radius + q * 15
+ y_b = self.y[self.num_cells * b] - self.b_d / 2
+ points.extend([
+ [x_b, y_b],
+ [x_b + 15, y_b],
+ [x_b + 15, y_b + 15],
+ [x_b, y_b + 15],
+ ])
sector = plt.Polygon(points, fill=None, edgecolor='k')
- ax.add_patch(sector)
-
-
+ ax.add_patch(sector)
+
# indoor base stations
- ax.scatter(self.x, self.y, color='k', edgecolor="k", linewidth=2, label="Base station")
-
+ ax.scatter(
+ self.x, self.y, color='k', edgecolor="k",
+ linewidth=2, label="Base station",
+ )
+
def plot_side_view(self, ax: matplotlib.axes.Axes):
-
+
# Loop on each floor of each column of buildings
for f in range(int(self.num_floors)):
for build in self.imt_buildings:
c = build[1]
- x_b = self.x[f*self.total_bs_level + c*self.num_cells] - self.cell_radius
- z_b = self.height[f*self.total_bs_level + c*self.num_cells]
- points = list([[x_b, z_b],
- [x_b + self.b_w, z_b],
- [x_b + self.b_w, z_b - self.b_h],
- [x_b, z_b - self.b_h]])
+ x_b = self.x[
+ f * self.total_bs_level +
+ c * self.num_cells
+ ] - self.cell_radius
+ z_b = self.height[f * self.total_bs_level + c * self.num_cells]
+ points = list([
+ [x_b, z_b],
+ [x_b + self.b_w, z_b],
+ [x_b + self.b_w, z_b - self.b_h],
+ [x_b, z_b - self.b_h],
+ ])
sector = plt.Polygon(points, fill=None, edgecolor='k')
- ax.add_patch(sector)
-
- ax.scatter(self.x, self.height-0.05, color='k', edgecolor="k", linewidth=2, label="Base station")
+ ax.add_patch(sector)
+
+ ax.scatter(
+ self.x, self.height - 0.05, color='k',
+ edgecolor="k", linewidth=2, label="Base station",
+ )
+
if __name__ == '__main__':
param = ParametersIndoor()
@@ -180,36 +207,44 @@ def plot_side_view(self, ax: matplotlib.axes.Axes):
param.building_class = "TRADITIONAL"
topology = TopologyIndoor(param)
topology.calculate_coordinates()
-
+
# Plot top view
- fig = plt.figure(figsize=(10,8), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(10, 8), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
-
+
topology.plot(ax)
-
- plt.axis('image')
+
+ plt.axis('image')
plt.title("Indoor topology")
plt.xlabel("x-coordinate [m]")
plt.ylabel("y-coordinate [m]")
- plt.tight_layout()
-
+ plt.tight_layout()
+
axes = plt.gca()
-# axes.set_xlim([-param.street_width, param.n_colums*3*param.intersite_distance + (param.n_colums-1)*param.street_width + param.street_width])
-# axes.set_ylim([-param.street_width, param.n_rows*topology.b_d + (param.n_rows-1)*param.street_width + param.street_width])
-
- plt.show()
-
+# axes.set_xlim([-param.street_width, param.n_colums*3*param.intersite_distance + \
+# (param.n_colums-1)*param.street_width + param.street_width])
+# axes.set_ylim([-param.street_width, param.n_rows*topology.b_d + \
+# (param.n_rows-1)*param.street_width + param.street_width])
+
+ plt.show()
+
# Plot side view
- fig = plt.figure(figsize=(10,8), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(10, 8), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
-
- topology.plot(ax,top_view=False)
-
+
+ topology.plot(ax, top_view=False)
+
plt.title("Indoor topology")
plt.xlabel("x-coordinate [m]")
- plt.ylabel("z-coordinate [m]")
+ plt.ylabel("z-coordinate [m]")
plt.tight_layout()
-
+
axes = plt.gca()
- axes.set_ylim((0,3*param.num_floors + 1))
- plt.show()
\ No newline at end of file
+ axes.set_ylim((0, 3 * param.num_floors + 1))
+ plt.show()
diff --git a/sharc/topology/topology_macrocell.py b/sharc/topology/topology_macrocell.py
index 59466312c..4b59a40c8 100644
--- a/sharc/topology/topology_macrocell.py
+++ b/sharc/topology/topology_macrocell.py
@@ -12,6 +12,7 @@
import math
import numpy as np
+
class TopologyMacrocell(Topology):
"""
Generates the coordinates of the sites based on the macrocell network
@@ -34,10 +35,12 @@ def __init__(self, intersite_distance: float, num_clusters: int):
num_clusters : Number of clusters, should be 1 or 7
"""
if num_clusters not in TopologyMacrocell.ALLOWED_NUM_CLUSTERS:
- error_message = "invalid number of clusters ({})".format(num_clusters)
+ error_message = "invalid number of clusters ({})".format(
+ num_clusters,
+ )
raise ValueError(error_message)
- cell_radius = intersite_distance*2/3
+ cell_radius = intersite_distance * 2 / 3
super().__init__(intersite_distance, cell_radius)
self.num_clusters = num_clusters
@@ -55,58 +58,76 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
self.static_base_stations = True
d = self.intersite_distance
- h = (d/3)*math.sqrt(3)/2
+ h = (d / 3) * math.sqrt(3) / 2
# these are the coordinates of the central cluster
- x_central = np.array([0, d, d/2, -d/2, -d, -d/2,
- d/2, 2*d, 3*d/2, d, 0, -d,
- -3*d/2, -2*d, -3*d/2, -d, 0, d, 3*d/2])
- y_central = np.array([0, 0, 3*h, 3*h, 0, -3*h,
- -3*h, 0, 3*h, 6*h, 6*h, 6*h,
- 3*h, 0, -3*h, -6*h, -6*h, -6*h, -3*h])
+ x_central = np.array([
+ 0, d, d / 2, -d / 2, -d, -d / 2,
+ d / 2, 2 * d, 3 * d / 2, d, 0, -d,
+ -3 * d / 2, -2 * d, -3 * d / 2, -d, 0, d, 3 * d / 2,
+ ])
+ y_central = np.array([
+ 0, 0, 3 * h, 3 * h, 0, -3 * h,
+ -3 * h, 0, 3 * h, 6 * h, 6 * h, 6 * h,
+ 3 * h, 0, -3 * h, -6 * h, -6 * h, -6 * h, -3 * h,
+ ])
self.x = np.copy(x_central)
self.y = np.copy(y_central)
# other clusters are calculated by shifting the central cluster
if self.num_clusters == 7:
- x_shift = np.array([7*d/2, -d/2, -4*d, -7*d/2, d/2, 4*d])
- y_shift = np.array([9*h, 15*h, 6*h, -9*h, -15*h, -6*h])
+ x_shift = np.array(
+ [7 * d / 2, -d / 2, -4 * d, -7 * d / 2, d / 2, 4 * d],
+ )
+ y_shift = np.array(
+ [9 * h, 15 * h, 6 * h, -9 * h, -15 * h, -6 * h],
+ )
for xs, ys in zip(x_shift, y_shift):
self.x = np.concatenate((self.x, x_central + xs))
self.y = np.concatenate((self.y, y_central + ys))
self.x = np.repeat(self.x, 3)
self.y = np.repeat(self.y, 3)
- self.azimuth = np.tile(self.AZIMUTH, 19*self.num_clusters)
+ self.z = np.zeros_like(self.x)
+ self.azimuth = np.tile(self.AZIMUTH, 19 * self.num_clusters)
# In the end, we have to update the number of base stations
self.num_base_stations = len(self.x)
- self.indoor = np.zeros(self.num_base_stations, dtype = bool)
+ self.indoor = np.zeros(self.num_base_stations, dtype=bool)
def plot(self, ax: matplotlib.axes.Axes):
# create the hexagons
- r = self.intersite_distance/3
+ r = self.intersite_distance / 3
for x, y, az in zip(self.x, self.y, self.azimuth):
- se = list([[x,y]])
+ se = list([[x, y]])
angle = int(az - 60)
for a in range(6):
- se.extend([[se[-1][0] + r*math.cos(math.radians(angle)), se[-1][1] + r*math.sin(math.radians(angle))]])
+ se.extend([[
+ se[-1][0] + r * math.cos(math.radians(angle)),
+ se[-1][1] + r * math.sin(math.radians(angle)),
+ ]])
angle += 60
sector = plt.Polygon(se, fill=None, edgecolor='k')
ax.add_patch(sector)
# macro cell base stations
- ax.scatter(self.x, self.y, color='k', edgecolor="k", linewidth=4, label="Macro cell")
+ ax.scatter(
+ self.x, self.y, color='k', edgecolor="k",
+ linewidth=4, label="Macro cell",
+ )
if __name__ == '__main__':
- intersite_distance = 500
+ intersite_distance = 1500
num_clusters = 1
topology = TopologyMacrocell(intersite_distance, num_clusters)
topology.calculate_coordinates()
- fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 8), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
topology.plot(ax)
@@ -117,5 +138,3 @@ def plot(self, ax: matplotlib.axes.Axes):
plt.ylabel("y-coordinate [m]")
plt.tight_layout()
plt.show()
-
-
diff --git a/sharc/topology/topology_ntn.py b/sharc/topology/topology_ntn.py
new file mode 100644
index 000000000..90f37b1d2
--- /dev/null
+++ b/sharc/topology/topology_ntn.py
@@ -0,0 +1,303 @@
+from sharc.topology.topology import Topology
+import numpy as np
+import math
+import matplotlib.pyplot as plt
+import matplotlib.axes
+import geopandas as gpd
+from shapely.geometry import Polygon, MultiPolygon
+from pathlib import Path
+
+
+class TopologyNTN(Topology):
+ """
+ Class to generate and manage the topology of Non-Terrestrial Network (NTN) sites
+ based on a specified macrocell network topology.
+ """
+
+ ALLOWED_NUM_SECTORS = [1, 7, 19]
+
+ def __init__(
+ self, intersite_distance: float, cell_radius: int, bs_height: float, bs_azimuth: float,
+ bs_elevation: float, num_sectors=7,
+ ):
+ """
+ Initializes the NTN topology with specific network settings.
+
+ Parameters:
+ intersite_distance: Distance between adjacent sites in meters.
+ cell_radius: Radius of the coverage area for each site in meters.
+ bs_height: Height of the base station (satellite) from the x-y plane.
+ bs_azimuth: Azimuth angle of the base station in degrees.
+ bs_elevation: Elevation angle of the base station in degrees.
+ num_sectors: Number of sectors for the topology (default is 7).
+ """
+
+ if num_sectors not in self.ALLOWED_NUM_SECTORS:
+ raise ValueError(
+ f"Invalid number of sectors: {num_sectors}. Allowed values are {self.ALLOWED_NUM_SECTORS}.",
+ )
+
+ # Call to the superclass constructor to set common properties
+ super().__init__(intersite_distance, cell_radius)
+ self.is_space_station = True
+ self.space_station_x = None
+ self.space_station_y = None
+ self.space_station_z = None
+ self.bs_azimuth = np.radians(bs_azimuth)
+ self.bs_elevation = np.radians(bs_elevation)
+ self.bs_radius = bs_height / np.sin(self.bs_elevation)
+ self.num_sectors = num_sectors
+
+ # Calculate the base station coordinates
+ self.space_station_x = self.bs_radius * np.cos(self.bs_elevation) * np.cos(self.bs_azimuth)
+ self.space_station_y = self.bs_radius * np.cos(self.bs_elevation) * np.sin(self.bs_azimuth)
+ self.space_station_z = bs_height
+
+ self.calculate_coordinates()
+
+ @staticmethod
+ def get_sectors_xy(*, num_sectors, intersite_distance) -> (np.array, np.array):
+ d = intersite_distance
+ x = [0.]
+ y = [0.]
+ # First ring (6 points)
+ if num_sectors == 7 or num_sectors == 19:
+
+ for k in range(6):
+ angle = k * 60
+ x.append(d * np.cos(np.radians(angle)))
+ y.append(d * np.sin(np.radians(angle)))
+
+ if num_sectors == 19:
+ # Coordinates with 19 sectors
+ # Second ring (12 points)
+ for k in range(6):
+ angle = k * 60
+ x.append(2 * d * np.cos(np.radians(angle)))
+ y.append(2 * d * np.sin(np.radians(angle)))
+ x.append(
+ d * np.cos(np.radians(angle)) +
+ d * np.cos(np.radians(angle + 60)),
+ )
+ y.append(
+ d * np.sin(np.radians(angle)) +
+ d * np.sin(np.radians(angle + 60)),
+ )
+
+ return (np.array(x), np.array(y))
+
+ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
+ """
+ Computes the coordinates of each site. This is where the actual layout calculation would be implemented.
+ """
+
+ self.x, self.y = self.get_sectors_xy(
+ num_sectors=self.num_sectors,
+ intersite_distance=self.intersite_distance
+ )
+
+ # Assuming all points are at ground level
+ self.z = np.zeros_like(self.x)
+
+ # Rotate the anchor points by 30 degrees
+ theta = np.radians(30)
+ cos_theta = np.cos(theta)
+ sin_theta = np.sin(theta)
+ self.x_rotated = self.x * cos_theta - self.y * sin_theta
+ self.y_rotated = self.x * sin_theta + self.y * cos_theta
+
+ # Calculate azimuth and elevation for each point
+ self.azimuth = np.arctan2(
+ self.y_rotated - self.space_station_y,
+ self.x_rotated - self.space_station_x,
+ ) * 180 / np.pi
+ distance_xy = np.sqrt(
+ (self.x_rotated - self.space_station_x) **
+ 2 + (self.y_rotated - self.space_station_y)**2,
+ )
+ self.elevation = np.arctan2(
+ self.z - self.space_station_z, distance_xy,
+ ) * 180 / np.pi
+
+ # Update the number of base stations after setup
+ self.num_base_stations = len(self.x)
+ self.indoor = np.zeros(self.num_base_stations, dtype=bool)
+
+ self.x = self.x_rotated
+ self.y = self.y_rotated
+
+ def plot(self, axis: matplotlib.axes.Axes, scale=1000):
+ r = self.cell_radius / scale # Convert to kilometers
+
+ # Plot each sector
+ for x, y in zip(self.x / scale, self.y / scale): # Convert to kilometers
+ hexagon = []
+ for a in range(6):
+ angle_rad = math.radians(a * 60)
+ hexagon.append([
+ x + r * math.cos(angle_rad),
+ y + r * math.sin(angle_rad),
+ ])
+ hexagon.append(hexagon[0]) # Close the hexagon
+ hexagon = np.array(hexagon)
+
+ sector = plt.Polygon(hexagon, fill=None, edgecolor='k')
+ axis.add_patch(sector)
+
+ # Plot base stations
+ axis.scatter(
+ self.x / scale, self.y / scale, s=200, marker='v', c='k', edgecolor='k', linewidth=1, alpha=1,
+ label="Anchor Points",
+ )
+
+ # Add labels and title
+ axis.set_xlabel("x-coordinate [km]")
+ axis.set_ylabel("y-coordinate [km]")
+ axis.set_title(f"NTN Topology - {self.num_sectors} Sectors")
+ axis.legend()
+ plt.tight_layout()
+
+ def plot_3d(self, axis: matplotlib.axes.Axes, map=False):
+ r = self.cell_radius / 1000 # Convert to kilometers
+
+ if map:
+ # Load the map of Brazil using GeoPandas
+ workspace_root = Path(__file__).resolve().parent.parent.parent
+ brazil = gpd.read_file(
+ workspace_root / "sharc" / "data" / "countries" / "ne_110m_admin_0_countries.shp",
+ )
+ brazil = brazil[brazil['NAME'] == "Brazil"]
+
+ # Coordinates of the Federal District (Brasรญlia)
+ federal_district_coords = (-47.9292, -15.7801)
+
+ # Approximate conversion factors (1 degree latitude = 111 km, 1 degree longitude = 111 km)
+ lat_to_km = 111
+ lon_to_km = 111
+
+ # Convert Federal District coordinates to kilometers
+ federal_district_coords_km = (
+ federal_district_coords[0] * lon_to_km, federal_district_coords[1] * lat_to_km,
+ )
+
+ # Calculate the shift required to move the Federal District to (0, 0)
+ x_shift = federal_district_coords_km[0]
+ y_shift = federal_district_coords_km[1]
+
+ # Manually plot the map of Brazil on the xy-plane
+ for geom in brazil.geometry:
+ if isinstance(geom, Polygon):
+ lon, lat = geom.exterior.xy
+ x = np.array(lon) * lon_to_km - x_shift
+ y = np.array(lat) * lat_to_km - y_shift
+ axis.plot(x, y, zs=0, zdir='z', color='lightgray')
+ elif isinstance(geom, MultiPolygon):
+ for poly in geom:
+ lon, lat = poly.exterior.xy
+ x = np.array(lon) * lon_to_km - x_shift
+ y = np.array(lat) * lat_to_km - y_shift
+ axis.plot(x, y, zs=0, zdir='z', color='lightgray')
+
+ # Add the Federal District location to the plot
+ axis.scatter(0, 0, 0, color='red', zorder=5)
+ axis.text(0, 0, 0, 'Federal District', fontsize=12, ha='right')
+
+ # Plot each sector
+ for x, y in zip(self.x / 1000, self.y / 1000): # Convert to kilometers
+ hexagon = []
+ for a in range(6):
+ angle_rad = math.radians(a * 60)
+ hexagon.append([
+ x + r * math.cos(angle_rad),
+ y + r * math.sin(angle_rad),
+ ])
+ hexagon.append(hexagon[0]) # Close the hexagon
+ hexagon = np.array(hexagon)
+
+ # 3D hexagon
+ axis.plot(
+ hexagon[:, 0], hexagon[:, 1],
+ np.zeros_like(hexagon[:, 0]), 'k-',
+ )
+
+ # Plot base stations
+ axis.scatter(
+ self.x / 1000, self.y / 1000, np.zeros_like(self.x), s=75, marker='v', c='k', edgecolor='k',
+ linewidth=1, alpha=1,
+ label="Anchor Points",
+ )
+
+ # Plot the satellite
+ axis.scatter(
+ self.space_station_x / 1000, self.space_station_y / 1000, self.space_station_z / 1000, s=75, c='r',
+ marker='^', edgecolor='k', linewidth=1, alpha=1,
+ label=f"Satellite (ฯ={np.degrees(self.bs_azimuth):.1f}ยฐ, ฮธ={np.degrees(self.bs_elevation):.1f}ยฐ)",
+ )
+
+ # Plot the height line
+ axis.plot(
+ [self.space_station_x / 1000, self.space_station_x / 1000],
+ [self.space_station_y / 1000, self.space_station_y / 1000],
+ [0, self.space_station_z / 1000], 'b-', label=f'Height = {self.space_station_z / 1000:.1f} km',
+ )
+
+ # Plot the slant range line
+ axis.plot(
+ [0, self.space_station_x / 1000],
+ [0, self.space_station_y / 1000],
+ [0, self.space_station_z / 1000], 'g--', label=f'Slant range = {self.bs_radius / 1000:.1f} km',
+ )
+
+ # Add labels and title
+ axis.set_xlabel("x-coordinate [km]")
+ axis.set_ylabel("y-coordinate [km]")
+ axis.set_zlabel("z-coordinate [km]")
+ axis.set_title(f"NTN Topology - {self.num_sectors} Sectors")
+ axis.legend()
+ plt.tight_layout()
+
+
+# Example usage
+if __name__ == '__main__':
+
+ bs_height = 1000e3 # meters
+ bs_azimuth = 45 # degrees
+ bs_elevation = 45 # degrees
+ beamwidth = 10
+ cell_radius = bs_height * \
+ math.tan(np.radians(beamwidth)) / math.cos(np.radians(bs_elevation))
+ intersite_distance = cell_radius * np.sqrt(3) # meters
+ map = True
+
+ # Test for 1 sector
+ ntn_topology_1 = TopologyNTN(
+ intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=1,
+ )
+ ntn_topology_1.calculate_coordinates() # Calculate the site coordinates
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d')
+ ntn_topology_1.plot_3d(ax, map) # Plot the 3D topology
+ plt.show()
+
+ # Test for 7 sectors
+ ntn_topology_7 = TopologyNTN(
+ intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=7,
+ )
+ ntn_topology_7.calculate_coordinates() # Calculate the site coordinates
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d')
+ ntn_topology_7.plot_3d(ax, map) # Plot the 3D topology
+ plt.show()
+
+ # Test for 19 sectors
+ ntn_topology_19 = TopologyNTN(
+ intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=19,
+ )
+ ntn_topology_19.calculate_coordinates() # Calculate the site coordinates
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111, projection='3d')
+ ntn_topology_19.plot_3d(ax, map) # Plot the 3D topology
+ plt.show()
diff --git a/sharc/topology/topology_single_base_station.py b/sharc/topology/topology_single_base_station.py
index 8fd3b932c..5d08c8bd7 100644
--- a/sharc/topology/topology_single_base_station.py
+++ b/sharc/topology/topology_single_base_station.py
@@ -11,6 +11,7 @@
from sharc.topology.topology import Topology
+
class TopologySingleBaseStation(Topology):
"""
Generates the a single base station centered at (0,0), with azimuth = 0ยฐ
@@ -21,7 +22,6 @@ class TopologySingleBaseStation(Topology):
AZIMUTH = [0, 180]
ALLOWED_NUM_CLUSTERS = [1, 2]
-
def __init__(self, cell_radius: float, num_clusters: int):
"""
Constructor method that sets the object attributes
@@ -31,10 +31,12 @@ def __init__(self, cell_radius: float, num_clusters: int):
cell_radius : radius of the cell
"""
if num_clusters not in TopologySingleBaseStation.ALLOWED_NUM_CLUSTERS:
- error_message = "invalid number of clusters ({})".format(num_clusters)
+ error_message = "invalid number of clusters ({})".format(
+ num_clusters,
+ )
raise ValueError(error_message)
- intersite_distance = 2*cell_radius
+ intersite_distance = 2 * cell_radius
super().__init__(intersite_distance, cell_radius)
self.num_clusters = num_clusters
@@ -47,24 +49,31 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()):
if self.num_clusters == 1:
self.x = np.array([0])
self.y = np.array([0])
- self.azimuth = TopologySingleBaseStation.AZIMUTH[0]*np.ones(1)
+ self.azimuth = TopologySingleBaseStation.AZIMUTH[0] * np.ones(
+ 1,
+ )
self.num_base_stations = 1
elif self.num_clusters == 2:
self.x = np.array([0, self.intersite_distance])
self.y = np.array([0, 0])
self.azimuth = np.array(TopologySingleBaseStation.AZIMUTH)
self.num_base_stations = 2
- self.indoor = np.zeros(self.num_base_stations, dtype = bool)
-
+ self.indoor = np.zeros(self.num_base_stations, dtype=bool)
+ self.z = np.zeros_like(self.x)
def plot(self, ax: matplotlib.axes.Axes):
# plot base station
- plt.scatter(self.x, self.y, color='g', edgecolor="w", linewidth=0.5, label="Hotspot")
+ plt.scatter(
+ self.x, self.y, color='g', edgecolor="w",
+ linewidth=0.5, label="Hotspot",
+ )
# plot base station coverage area
for x, y, a in zip(self.x, self.y, self.azimuth):
- pa = patches.Wedge( (x, y), self.cell_radius, a-60, a+60, fill=False,
- edgecolor="green", linestyle='solid' )
+ pa = patches.Wedge(
+ (x, y), self.cell_radius, a - 60, a + 60, fill=False,
+ edgecolor="green", linestyle='solid',
+ )
ax.add_patch(pa)
@@ -74,7 +83,10 @@ def plot(self, ax: matplotlib.axes.Axes):
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
- fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object
+ fig = plt.figure(
+ figsize=(8, 8), facecolor='w',
+ edgecolor='k',
+ ) # create a figure object
ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure
topology.plot(ax)
@@ -85,4 +97,3 @@ def plot(self, ax: matplotlib.axes.Axes):
plt.ylabel("y-coordinate [m]")
plt.tight_layout()
plt.show()
-
diff --git a/tests/_test_beamforming_normalizer.py b/tests/_test_beamforming_normalizer.py
index dbe3db662..d388ab3f6 100644
--- a/tests/_test_beamforming_normalizer.py
+++ b/tests/_test_beamforming_normalizer.py
@@ -14,13 +14,14 @@
from sharc.support.named_tuples import AntennaPar
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
+
class BeamformingNormalizerTest(unittest.TestCase):
-
+
def setUp(self):
# Test 1
resolution = 30
tolerance = 1e-2
- self.norm_1 = BeamformingNormalizer(resolution,tolerance)
+ self.norm_1 = BeamformingNormalizer(resolution, tolerance)
adjacent_antenna_model = "SINGLE_ELEMENT"
norm = False
norm_file = None
@@ -37,27 +38,29 @@ def setUp(self):
multiplication_factor = 12
minimum_array_gain = -200
downtilt = 0
- self.par_1 = AntennaPar(adjacent_antenna_model,
- norm,
- norm_file,
- element_pattern,
- element_max_g,
- element_phi_3db,
- element_theta_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- horiz_spacing,
- vert_spacing,
- multiplication_factor,
- minimum_array_gain,
- downtilt)
-
+ self.par_1 = AntennaPar(
+ adjacent_antenna_model,
+ norm,
+ norm_file,
+ element_pattern,
+ element_max_g,
+ element_phi_3db,
+ element_theta_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ horiz_spacing,
+ vert_spacing,
+ multiplication_factor,
+ minimum_array_gain,
+ downtilt,
+ )
+
# Test 2: UE configuration
resolution = 5
tolerance = 1e-2
- self.norm_2 = BeamformingNormalizer(resolution,tolerance)
+ self.norm_2 = BeamformingNormalizer(resolution, tolerance)
adjacent_antenna_model = "SINGLE_ELEMENT"
norm = False
norm_file = None
@@ -74,27 +77,29 @@ def setUp(self):
multiplication_factor = 12
minimum_array_gain = -200
downtilt = 0
- self.par_2 = AntennaPar(adjacent_antenna_model,
- norm,
- norm_file,
- element_pattern,
- element_max_g,
- element_phi_deg_3db,
- element_theta_deg_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- horiz_spacing,
- vert_spacing,
- multiplication_factor,
- minimum_array_gain,
- downtilt)
-
+ self.par_2 = AntennaPar(
+ adjacent_antenna_model,
+ norm,
+ norm_file,
+ element_pattern,
+ element_max_g,
+ element_phi_deg_3db,
+ element_theta_deg_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ horiz_spacing,
+ vert_spacing,
+ multiplication_factor,
+ minimum_array_gain,
+ downtilt,
+ )
+
# Test 3: BS configuration
resolution = 180
tolerance = 5e-2
- self.norm_3 = BeamformingNormalizer(resolution,tolerance)
+ self.norm_3 = BeamformingNormalizer(resolution, tolerance)
adjacent_antenna_model = "SINGLE_ELEMENT"
norm = False
norm_file = None
@@ -111,94 +116,101 @@ def setUp(self):
multiplication_factor = 12
minimum_array_gain = -200
downtilt = 0
- self.par_3 = AntennaPar(adjacent_antenna_model,
- norm,
- norm_file,
- element_pattern,
- element_max_g,
- element_phi_deg_3db,
- element_theta_deg_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- horiz_spacing,
- vert_spacing,
- multiplication_factor,
- minimum_array_gain,
- downtilt)
-
+ self.par_3 = AntennaPar(
+ adjacent_antenna_model,
+ norm,
+ norm_file,
+ element_pattern,
+ element_max_g,
+ element_phi_deg_3db,
+ element_theta_deg_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ horiz_spacing,
+ vert_spacing,
+ multiplication_factor,
+ minimum_array_gain,
+ downtilt,
+ )
+
def test_construction(self):
# Test 1
- self.assertEqual(self.norm_1.resolution_deg,30)
- self.assertEqual(self.norm_1.phi_min_deg,-180)
- self.assertEqual(self.norm_1.phi_max_deg,180)
- self.assertEqual(self.norm_1.theta_min_deg,0)
- self.assertEqual(self.norm_1.theta_max_deg,180)
- self.assertEqual(self.norm_1.phi_min_rad,-np.pi)
- self.assertEqual(self.norm_1.phi_max_rad,np.pi)
- self.assertEqual(self.norm_1.theta_min_rad,0)
- self.assertEqual(self.norm_1.theta_max_rad,np.pi)
- npt.assert_equal(self.norm_1.phi_vals_deg,np.arange(-180,180,30))
- npt.assert_equal(self.norm_1.theta_vals_deg,np.arange(0,180,30))
-
+ self.assertEqual(self.norm_1.resolution_deg, 30)
+ self.assertEqual(self.norm_1.phi_min_deg, -180)
+ self.assertEqual(self.norm_1.phi_max_deg, 180)
+ self.assertEqual(self.norm_1.theta_min_deg, 0)
+ self.assertEqual(self.norm_1.theta_max_deg, 180)
+ self.assertEqual(self.norm_1.phi_min_rad, -np.pi)
+ self.assertEqual(self.norm_1.phi_max_rad, np.pi)
+ self.assertEqual(self.norm_1.theta_min_rad, 0)
+ self.assertEqual(self.norm_1.theta_max_rad, np.pi)
+ npt.assert_equal(self.norm_1.phi_vals_deg, np.arange(-180, 180, 30))
+ npt.assert_equal(self.norm_1.theta_vals_deg, np.arange(0, 180, 30))
+
def test_calculate_correction_factor(self):
# Test 1: omni pattern
azi = 0
ele = 0
- self.norm_1.antenna = AntennaBeamformingImt(self.par_1,azi,ele)
+ self.norm_1.antenna = AntennaBeamformingImt(self.par_1, azi, ele)
# Test adjacent channel case: single antenna element
c_chan = False
- c_fac, err = self.norm_1.calculate_correction_factor(0,0,c_chan)
- self.assertAlmostEqual(c_fac,0.0,delta = 1e-2)
- self.assertLess(np.max(np.abs(err)),1e-3)
-
+ c_fac, err = self.norm_1.calculate_correction_factor(0, 0, c_chan)
+ self.assertAlmostEqual(c_fac, 0.0, delta=1e-2)
+ self.assertLess(np.max(np.abs(err)), 1e-3)
+
# Test 2: UE element pattern
azi = 0
ele = 0
- self.norm_2.antenna = AntennaBeamformingImt(self.par_2,azi,ele)
+ self.norm_2.antenna = AntennaBeamformingImt(self.par_2, azi, ele)
# Test adjacent channel case: single antenna element
c_chan = False
- c_fac, err = self.norm_2.calculate_correction_factor(0,0,c_chan)
- self.assertAlmostEqual(c_fac,2.4,delta = 1e-1)
- self.assertGreater(err[0],2.35)
- self.assertLess(err[1],2.45)
-
+ c_fac, err = self.norm_2.calculate_correction_factor(0, 0, c_chan)
+ self.assertAlmostEqual(c_fac, 2.4, delta=1e-1)
+ self.assertGreater(err[0], 2.35)
+ self.assertLess(err[1], 2.45)
+
# Test 3.1: BS element pattern
azi = 0
ele = 0
- self.norm_3.antenna = AntennaBeamformingImt(self.par_3,azi,ele)
+ self.norm_3.antenna = AntennaBeamformingImt(self.par_3, azi, ele)
# Test adjacent channel case: single antenna element
c_chan = False
- c_fac, err = self.norm_3.calculate_correction_factor(0,0,c_chan)
- self.assertAlmostEqual(c_fac,4.8,delta = 1e-1)
- self.assertGreater(err[0],4.75)
- self.assertLess(err[1],4.85)
-
+ c_fac, err = self.norm_3.calculate_correction_factor(0, 0, c_chan)
+ self.assertAlmostEqual(c_fac, 4.8, delta=1e-1)
+ self.assertGreater(err[0], 4.75)
+ self.assertLess(err[1], 4.85)
+
# Test 3.2: BS array
azi = 0
ele = 0
- self.norm_3.antenna = AntennaBeamformingImt(self.par_3,azi,ele)
+ self.norm_3.antenna = AntennaBeamformingImt(self.par_3, azi, ele)
# Test adjacent channel case: single antenna element
c_chan = True
- c_fac, err = self.norm_3.calculate_correction_factor(0,0,c_chan)
- self.assertAlmostEqual(c_fac,8.0,delta = 1e-1)
- self.assertGreater(err[0],7.5)
- self.assertLess(err[1],8.5)
-
+ c_fac, err = self.norm_3.calculate_correction_factor(0, 0, c_chan)
+ self.assertAlmostEqual(c_fac, 8.0, delta=1e-1)
+ self.assertGreater(err[0], 7.5)
+ self.assertLess(err[1], 8.5)
+
def test_generate_correction_matrix(self):
# Test 3.1: BS element pattern
file_name = "test_2.npz"
self.norm_3.generate_correction_matrix(self.par_3, file_name, True)
- data = np.load(file_name)
- self.assertAlmostEqual(data['correction_factor_adj_channel'],
- 4.8,delta = 1e-1)
-
+ data = np.load(file_name)
+ self.assertAlmostEqual(
+ data['correction_factor_adj_channel'],
+ 4.8, delta=1e-1,
+ )
+
# Test 3.2: BS array
- npt.assert_allclose(data['correction_factor_co_channel'],
- np.array([[8.0],[8.0]]),atol = 1e-1)
+ npt.assert_allclose(
+ data['correction_factor_co_channel'],
+ np.array([[8.0], [8.0]]), atol=1e-1,
+ )
data.close()
os.remove(file_name)
-
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/_test_propagation_p619.py b/tests/_test_propagation_p619.py
index 5eeb66359..28cfc37f3 100644
--- a/tests/_test_propagation_p619.py
+++ b/tests/_test_propagation_p619.py
@@ -23,7 +23,7 @@ class TestPropagationP619(unittest.TestCase):
def setUp(self):
self.p619 = PropagationP619(np.random.RandomState())
- def test_atmospheric_gasses_loss (self):
+ def test_atmospheric_gasses_loss(self):
# compare with benchmark from ITU-R P-619 Fig. 3
frequency_MHz = 30000.
sat_params = DummyParams()
@@ -35,35 +35,42 @@ def test_atmospheric_gasses_loss (self):
loss_lower_vec = [10, 1, .3, .2]
loss_upper_vec = [20, 2, .4, .3]
- for vapour_density, apparent_elevation, loss_lower, loss_upper in zip(vapour_density_vec,
- apparent_elevation_vec,
- loss_lower_vec,
- loss_upper_vec):
+ for vapour_density, apparent_elevation, loss_lower, loss_upper in zip(
+ vapour_density_vec,
+ apparent_elevation_vec,
+ loss_lower_vec,
+ loss_upper_vec,
+ ):
sat_params.surf_water_vapour_density = vapour_density
- loss = self.p619._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz,
- apparent_elevation=apparent_elevation,
- surf_water_vapour_density=vapour_density,
- sat_params=sat_params)
+ loss = self.p619._get_atmospheric_gasses_loss(
+ frequency_MHz=frequency_MHz,
+ apparent_elevation=apparent_elevation,
+ surf_water_vapour_density=vapour_density,
+ sat_params=sat_params,
+ )
self.assertLessEqual(loss_lower, loss)
self.assertGreaterEqual(loss_upper, loss)
-
def test_beam_spreading_attenuation(self):
# compare with benchmark from ITU-R P-619 Fig. 7
- altitude_vec = np.array([0,1,2,3,4,6]) * 1000
- elevation_vec = [0,.5,1,2,3,5]
- att_lower_vec = [.8, .6 , .4, .2, .1, .05]
+ altitude_vec = np.array([0, 1, 2, 3, 4, 6]) * 1000
+ elevation_vec = [0, .5, 1, 2, 3, 5]
+ att_lower_vec = [.8, .6, .4, .2, .1, .05]
att_upper_vec = [.9, .7, .5, .3, .2, .1]
earth_to_space_vec = [True, False, True, False, True, False]
- for altitude, elevation, lower, upper, earth_to_space in zip(altitude_vec,
- elevation_vec,
- att_lower_vec,
- att_upper_vec,
- earth_to_space_vec):
-
- attenuation = self.p619._get_beam_spreading_att(elevation, altitude, earth_to_space)
+ for altitude, elevation, lower, upper, earth_to_space in zip(
+ altitude_vec,
+ elevation_vec,
+ att_lower_vec,
+ att_upper_vec,
+ earth_to_space_vec,
+ ):
+
+ attenuation = self.p619._get_beam_spreading_att(
+ elevation, altitude, earth_to_space,
+ )
self.assertLessEqual(lower, abs(attenuation))
self.assertGreaterEqual(upper, abs(attenuation))
diff --git a/tests/main.py b/tests/main.py
index 2f80560b4..d1f6069ac 100644
--- a/tests/main.py
+++ b/tests/main.py
@@ -13,5 +13,5 @@
testRunner = unittest.runner.TextTestRunner()
test_results = testRunner.run(tests)
-if(test_results.errors != [] or test_results.failures != []): sys.exit(1)
-
\ No newline at end of file
+if (test_results.errors != [] or test_results.failures != []):
+ sys.exit(1)
diff --git a/sharc/input/__init__.py b/tests/parameters/__init__.py
similarity index 92%
rename from sharc/input/__init__.py
rename to tests/parameters/__init__.py
index faaaf799c..40a96afc6 100644
--- a/sharc/input/__init__.py
+++ b/tests/parameters/__init__.py
@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
-
-
diff --git a/tests/parameters/parameters_for_testing.yaml b/tests/parameters/parameters_for_testing.yaml
new file mode 100644
index 000000000..eb47e6bd0
--- /dev/null
+++ b/tests/parameters/parameters_for_testing.yaml
@@ -0,0 +1,923 @@
+general:
+ ###########################################################################
+ # Number of simulation snapshots
+ num_snapshots : 100
+ ###########################################################################
+ # IMT link that will be simulated (DOWNLINK or UPLINK)
+ imt_link : DOWNLINK
+ ###########################################################################
+ # The chosen system for sharing study
+ # SINGLE_SPACE_STATION, SINGLE_EARTH_STATION
+ system : SINGLE_EARTH_STATION
+ ###########################################################################
+ # Compatibility scenario (co-channel and/or adjacent channel interference)
+ enable_cochannel : FALSE
+ enable_adjacent_channel : TRUE
+ ###########################################################################
+ # Seed for random number generator
+ seed : 101
+ ###########################################################################
+ # if FALSE, then a new output directory is created
+ overwrite_output : TRUE
+imt:
+ ###########################################################################
+ # Minimum 2D separation distance from BS to UE [m]
+ minimum_separation_distance_bs_ue : 1.3
+ ###########################################################################
+ # Defines if IMT service is the interferer or interfered-with service
+ # TRUE : IMT suffers interference
+ # FALSE : IMT generates interference
+ interfered_with : FALSE
+ ###########################################################################
+ # IMT center frequency [MHz]
+ frequency : 24360
+ ###########################################################################
+ # IMT bandwidth [MHz]
+ bandwidth : 200.5
+ ###########################################################################
+ # IMT resource block bandwidth [MHz]
+ rb_bandwidth : 0.181
+ ###########################################################################
+ # IMT spectrum emission mask. Options are:
+ # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36
+ # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in
+ # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE)
+ spectral_mask : 3GPP E-UTRA
+ ###########################################################################
+ # level of spurious emissions [dBm/MHz]
+ spurious_emissions : -13.1
+ ###########################################################################
+ # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1
+ # means that 10% of the total bandwidth will be used as guard band: 5% in
+ # the lower
+ guard_band_ratio : 0.14
+ ###########################################################################
+ # Network topology parameters.
+ topology:
+ # these lat long alt parameters are needed only if other system is deployed
+ # on 3d earth and need to be transformed so that imt is on (0,0,0)
+ ###########################################################################
+ # The latitude position
+ central_latitude: 21.12
+ ###########################################################################
+ # The longitude position
+ central_longitude: -12.134
+ ###########################################################################
+ # The altitude position
+ central_altitude: 1111
+ ###########################################################################
+ # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS"
+ # "INDOOR"
+ type: INDOOR
+ ###########################################################################
+ # Macrocell Topology parameters. Relevant when imt.topology.type == "MACROCELL"
+ macrocell:
+ ###########################################################################
+ # Inter-site distance in macrocell network topology [m]
+ intersite_distance: 543
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS"
+ single_bs:
+ ###########################################################################
+ # Inter-site distance or Cell Radius in single Base Station network topology [m]
+ # You can either provide 'cell_radius' or 'intersite_distance' for this topology
+ # The relationship used is cell_radius = intersite_distance * 2 / 3
+ cell_radius: 543
+ # intersite_distance: 1
+ ###########################################################################
+ # Number of clusters in single base station topology
+ # You can simulate 1 or 2 BS's with this topology
+ num_clusters: 2
+ ###########################################################################
+ # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT"
+ hotspot:
+ ###########################################################################
+ # Inter-site distance in hotspot network topology [m]
+ intersite_distance: 321
+ ###########################################################################
+ # Enable wrap around.
+ wrap_around: true
+ ###########################################################################
+ # Number of clusters in macro cell topology
+ num_clusters: 7
+ ###########################################################################
+ # Number of hotspots per macro cell (sector)
+ num_hotspots_per_cell : 1
+ ###########################################################################
+ # Maximum 2D distance between hotspot and UE [m]
+ # This is the hotspot radius
+ max_dist_hotspot_ue : 99.9
+ ###########################################################################
+ # Minimum 2D distance between macro cell base station and hotspot [m]
+ min_dist_bs_hotspot : 1.2
+ ###########################################################################
+ # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR"
+ indoor:
+ ###########################################################################
+ # Basic path loss model for indoor topology. Possible values:
+ # "FSPL" (free-space path loss),
+ # "INH_OFFICE" (3GPP Indoor Hotspot - Office)
+ basic_path_loss : FSPL
+ ###########################################################################
+ # Number of rows of buildings in the simulation scenario
+ n_rows : 3
+ ###########################################################################
+ # Number of colums of buildings in the simulation scenario
+ n_colums : 2
+ ###########################################################################
+ # Number of buildings containing IMT stations. Options:
+ # 'ALL': all buildings contain IMT stations.
+ # Number of buildings.
+ num_imt_buildings : 2
+ ###########################################################################
+ # Street width (building separation) [m]
+ street_width : 30.1
+ ###########################################################################
+ # Intersite distance [m]
+ intersite_distance : 40.1
+ ###########################################################################
+ # Number of cells per floor
+ num_cells : 3
+ ###########################################################################
+ # Number of floors per building
+ num_floors : 1
+ ###########################################################################
+ # Percentage of indoor UE's [0, 1]
+ ue_indoor_percent : .95
+ ###########################################################################
+ # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT"
+ building_class : THERMALLY_EFFICIENT
+ ###########################################################################
+ # NTN Topology Parameters
+ ntn:
+ ###########################################################################
+ # NTN cell radius or intersite distance in network topology [m]
+ # @important: You can set only one of cell_radius or intersite_distance
+ # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3)
+ # NOTE: note that intersite distance has a different geometric meaning in ntn
+ cell_radius : 123
+ # intersite_distance : 155884
+ ###########################################################################
+ # NTN space station azimuth [degree]
+ bs_azimuth : 45
+ ###########################################################################
+ # NTN space station elevation [degree]
+ bs_elevation : 45
+ ###########################################################################
+ # number of sectors [degree]
+ num_sectors : 19
+ ###########################################################################
+ # Defines the antenna model to be used in compatibility studies between
+ # IMT and other services in adjacent band
+ # Possible values: SINGLE_ELEMENT, BEAMFORMING
+ adjacent_antenna_model : BEAMFORMING
+ # Base station parameters
+ bs:
+ ###########################################################################
+ # The load probability (or activity factor) models the statistical
+ # variation of the network load by defining the number of fully loaded
+ # base stations that are simultaneously transmitting
+ load_probability : .2
+ ###########################################################################
+ # Conducted power per antenna element [dBm/bandwidth]
+ conducted_power : 11.1
+ ###########################################################################
+ # Base station height [m]
+ height : 6.1
+ ###########################################################################
+ # Base station noise figure [dB]
+ noise_figure : 10.1
+ ###########################################################################
+ # Base station array ohmic loss [dB]
+ ohmic_loss : 3.1
+ # Base Station Antenna parameters:
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for BS
+ normalization : FALSE
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the BS azimuth as 0deg
+ horizontal_beamsteering_range: !!python/tuple [-10.1, 11.2]
+ ###########################################################################
+ # Base station horizontal beamsteering range. [deg]
+ # The range considers the horizon as 90deg, 0 as zenith
+ vertical_beamsteering_range: !!python/tuple [0., 180.]
+ ###########################################################################
+ # File to be used in the BS beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file : antenna/beamforming_normalization/bs_norm.npz
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # mechanical downtilt [degrees]
+ # NOTE: consider defining it to 90 degrees in case of indoor simulations
+ downtilt : 6
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # default: element_max_g = 5, for M.2101
+ # = 15, for M.2292
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 65
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 65
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 8
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 8
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 30
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 30
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ ###########################################################################
+ # Subarray for IMT as defined in R23-WP5D-C-0413, Annex 4.2
+ # Single column sub array
+ subarray:
+ # NOTE: if subarray is enabled, element definition will mostly come from
+ # the above definitions
+ is_enabled: true
+ # Rows per subarray
+ n_rows: 10
+ # Sub array element spacing (d/lambda).
+ element_vert_spacing: 0.05
+ # Sub array eletrical downtilt [deg]
+ eletrical_downtilt: 9.0
+
+ ###########################################################################
+ # User Equipment parameters:
+ ue:
+ ###########################################################################
+ # Number of UEs that are allocated to each cell within handover margin.
+ # Remember that in macrocell network each base station has 3 cells (sectors)
+ k : 3
+ ###########################################################################
+ # Multiplication factor that is used to ensure that the sufficient number
+ # of UE's will distributed throughout ths system area such that the number
+ # of K users is allocated to each cell. Normally, this values varies
+ # between 2 and 10 according to the user drop method
+ k_m : 1
+ ###########################################################################
+ # Percentage of indoor UE's [%]
+ indoor_percent : 5
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter states how the UEs will be distributed
+ # Possible values: UNIFORM : UEs will be uniformly distributed within the
+ # whole simulation area. Not applicable to
+ # hotspots.
+ # ANGLE_AND_DISTANCE : UEs will be distributed following
+ # given distributions for angle and
+ # distance. In this case, these must be
+ # defined later.
+ distribution_type : ANGLE_AND_DISTANCE
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the distance between UE's and BS.
+ # Possible values: RAYLEIGH, UNIFORM
+ distribution_distance : UNIFORM
+ ###########################################################################
+ # Regarding the distribution of active UE's over the cell area, this
+ # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range).
+ # Possible values: NORMAL, UNIFORM
+ distribution_azimuth : NORMAL
+ ###########################################################################
+ # Azimuth range used when distributing the UE within the cell.
+ # Default value is (-60, 60) which representes a 120 degree sector.
+ # Change this to (-180, 180) when deploying the NTN topology
+ azimuth_range: !!python/tuple [-70, 90]
+ ###########################################################################
+ # Power control algorithm
+ # tx_power_control = "ON",power control On
+ # tx_power_control = "OFF",power control Off
+ tx_power_control : ON
+ ###########################################################################
+ # Power per RB used as target value [dBm]
+ p_o_pusch : -95
+ ###########################################################################
+ # Alfa is the balancing factor for UEs with bad channel
+ # and UEs with good channel
+ alpha : 1
+ ###########################################################################
+ # Maximum UE transmit power [dBm]
+ p_cmax : 22
+ ###########################################################################
+ # UE power dynamic range [dB]
+ # The minimum transmit power of a UE is (p_cmax - dynamic_range)
+ power_dynamic_range : 63
+ ###########################################################################
+ # UE height [m]
+ height : 1.5
+ ###########################################################################
+ # User equipment noise figure [dB]
+ noise_figure : 10
+ ###########################################################################
+ # User equipment feed loss [dB]
+ ohmic_loss : 3
+ ###########################################################################
+ # User equipment body loss [dB]
+ body_loss : 4
+ antenna:
+ array:
+ ###########################################################################
+ # If normalization of M2101 should be applied for UE
+ normalization : FALSE
+ ###########################################################################
+ # File to be used in the UE beamforming normalization
+ # Normalization files can be generated with the
+ # antenna/beamforming_normalization/normalize_script.py script
+ normalization_file : antenna/beamforming_normalization/ue_norm.npz
+ ###########################################################################
+ # Radiation pattern of each antenna element
+ # Possible values: "M2101", "F1336", "FIXED"
+ element_pattern : F1336
+ ###########################################################################
+ # Minimum array gain for the beamforming antenna [dBi]
+ minimum_array_gain : -200
+ ###########################################################################
+ # BS/UE maximum transmit/receive element gain [dBi]
+ # = 15, for M.2292
+ # default: element_max_g = 5, for M.2101
+ # = -3, for M.2292
+ element_max_g : 5
+ ###########################################################################
+ # BS/UE horizontal 3dB beamwidth of single element [degrees]
+ element_phi_3db : 90
+ ###########################################################################
+ # BS/UE vertical 3dB beamwidth of single element [degrees]
+ # For F1336: if equal to 0, then beamwidth is calculated automaticaly
+ element_theta_3db : 90
+ ###########################################################################
+ # BS/UE number of rows in antenna array
+ n_rows : 4
+ ###########################################################################
+ # BS/UE number of columns in antenna array
+ n_columns : 4
+ ###########################################################################
+ # BS/UE array horizontal element spacing (d/lambda)
+ element_horiz_spacing : 0.5
+ ###########################################################################
+ # BS/UE array vertical element spacing (d/lambda)
+ element_vert_spacing : 0.5
+ ###########################################################################
+ # BS/UE front to back ratio of single element [dB]
+ element_am : 25
+ ###########################################################################
+ # BS/UE single element vertical sidelobe attenuation [dB]
+ element_sla_v : 25
+ ###########################################################################
+ # Multiplication factor k that is used to adjust the single-element pattern.
+ # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the
+ # side lobes when beamforming is assumed in adjacent channel.
+ # Original value: 12 (Rec. ITU-R M.2101)
+ multiplication_factor : 12
+ ###########################################################################
+ # Uplink parameters. Only needed when using uplink on imt
+ uplink:
+ ###########################################################################
+ # Uplink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.4
+ ###########################################################################
+ # Uplink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Uplink maximum SINR of the code set [dB]
+ sinr_max : 22
+ ###########################################################################
+ # Downlink parameters. Only needed when using donwlink on imt
+ downlink:
+ ###########################################################################
+ # Downlink attenuation factor used in link-to-system mapping
+ attenuation_factor : 0.6
+ ###########################################################################
+ # Downlink minimum SINR of the code set [dB]
+ sinr_min : -10
+ ###########################################################################
+ # Downlink maximum SINR of the code set [dB]
+ sinr_max : 30
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "CI" (close-in FS reference distance)
+ # "UMa" (Urban Macro - 3GPP)
+ # "UMi" (Urban Micro - 3GPP)
+ # "TVRO-URBAN"
+ # "TVRO-SUBURBAN"
+ # "ABG" (Alpha-Beta-Gamma)
+ # "P619"
+ channel_model : FSPL
+ ###########################################################################
+ # Adjustment factor for LoS probability in UMi path loss model.
+ # Original value: 18 (3GPP)
+ los_adjustment_factor : 18
+ ###########################################################################
+ # If shadowing should be applied or not
+ shadowing : FALSE
+ noise_temperature : 290
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 10
+haps:
+ ###########################################################################
+ # HAPS center frequency [MHz]
+ frequency : 27251.1
+ ###########################################################################
+ # HAPS bandwidth [MHz]
+ bandwidth : 200
+ ###########################################################################
+ # HAPS altitude [m] and latitude [deg]
+ altitude : 20001.1
+ lat_deg : 0.1
+ ###########################################################################
+ # Elevation angle [deg]
+ elevation : 270
+ ###########################################################################
+ # Azimuth angle [deg]
+ azimuth : 0
+ ###########################################################################
+ # EIRP spectral density [dBW/MHz]
+ eirp_density : 4.4
+ ###########################################################################
+ # HAPS peak antenna gain [dBi]
+ antenna_gain : 28.1
+ ###########################################################################
+ # Adjacent channel selectivity [dB]
+ acs : 30
+ ###########################################################################
+ # Antenna pattern of the HAPS (airbone) station
+ # Possible values: "ITU-R F.1891", "OMNI"
+ antenna_pattern : OMNI
+ # IMT parameters relevant to the HAPS system
+ # altitude of IMT system (in meters)
+ # latitude of IMT system (in degrees)
+ # difference between longitudes of IMT and satellite system
+ # (positive if space-station is to the East of earth-station)
+ earth_station_alt_m : 0
+ earth_station_lat_deg : 0
+ earth_station_long_diff_deg : 0
+ season : SUMMER
+ ###########################################################################
+ # Channel parameters
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model : P619
+ ###########################################################################
+ # Near side-lobe level (dB) relative to the peak gain required by the system
+ # design, and has a maximum value of โ25 dB
+ antenna_l_n : -25
+single_earth_station:
+ ###########################################################################
+ # Sensor center frequency [MHz]
+ frequency: 8250
+ ###########################################################################
+ # Sensor bandwidth [MHz]
+ bandwidth: 100
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 300
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: -65
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 20.0
+ geometry:
+ ###########################################################################
+ # Antenna height [meters]
+ height: 6
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ ###########################################################################
+ # Type of azimuth. May be "UNIFORM_DIST", "FIXED"
+ type: FIXED
+ ###########################################################################
+ # Value of azimuth when type == "FIXED" [deg]
+ fixed: 0
+ ###########################################################################
+ # Limits of random uniform distribution when type == "UNIFORM_DIST"
+ uniform_dist:
+ ###########################################################################
+ # uniform distribution lower bound [deg]
+ min: -180
+ ###########################################################################
+ # uniform distribution upper bound [deg]
+ max: 180
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ # Type of elevation. May be "UNIFORM_DIST", "FIXED"
+ type: FIXED
+ # Value of elevation when type == "FIXED" [deg]
+ fixed: 60
+ # Limits of random uniform distribution when type == "UNIFORM_DIST"
+ uniform_dist:
+ # uniform distribution lower bound [deg]
+ min: 30
+ # uniform distribution upper bound [deg]
+ max: 65
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ # choose way to set location. May be "CELL", "NETWORK", "UNIFORM_DIST" or "FIXED"
+ # location.type == CELL means that the ES will be distributed randomly,
+ # but always in the central cell
+ # location.type == NETWORK means that the ES will be distributed randomly,
+ # in a random cell
+ # location.type == UNIFORM_DIST means that the ES will be distributed randomly,
+ # in a ring shaped section with delimited outer and inner radius
+ # location.type == FIXED means that the ES will be always at SAME fixed location
+ type: CELL
+ ###########################################################################
+ # Value of position (x,y) when type == "FIXED" [(m, m)]
+ fixed:
+ x: 10
+ y: 100
+ cell:
+ ###########################################################################
+ # Minimum distance to IMT BS when location.type == CELL [m]
+ min_dist_to_bs: 100
+ network:
+ ###########################################################################
+ # Minimum distance to IMT BS when location.type == NETWORK [m]
+ min_dist_to_bs: 150
+ uniform_dist:
+ ###########################################################################
+ # Ring inner radius [m]
+ min_dist_to_center: 101
+ ###########################################################################
+ # Ring outer radius [m]
+ max_dist_to_center: 102
+ antenna:
+ ###########################################################################
+ # Choose the antenna pattern. Can be one of:
+ # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855"
+ pattern: ITU-R S.465
+ ###########################################################################
+ # Earth station peak receive antenna gain [dBi]
+ gain: 28
+ itu_r_f_699:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_465:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_580:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_1855:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_465_modified:
+ ###########################################################################
+ # Antenna envelope gain [dBi]
+ envelope_gain: -4
+ itu_reg_rr_a7_3:
+ ###########################################################################
+ # Antenna diameter [m]
+ # if no diameter is passed, a diameter will be assumed according to document
+ diameter: 2.12
+ ###########################################################################
+ # Selected channel model
+ # Possible values are "P619", "P452", "TerrestrialSimple"
+ channel_model: "P619"
+
+ ###########################################################################
+ # Parameters for P619 channel model
+ param_p619:
+ ###########################################################################
+ # altitude of the space station [m]
+ space_station_alt_m: 540000
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 1200
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 13
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 10
+
+ ###########################################################################
+ # season - season of the year.
+ # Only actually useful for P619, but we can't put it inside param_p619 without changing more code
+ season: SUMMER
+
+ ###########################################################################
+ # Parameters for P452 channel model
+ param_p452:
+ ###########################################################################
+ # Total air pressure [hPa]
+ atmospheric_pressure: 1
+ ###########################################################################
+ # Temperature [K]
+ air_temperature: 2
+ ###########################################################################
+ # Sea-level surface refractivity (use the map)
+ N0: 3
+ ###########################################################################
+ # Average radio-refractive (use the map)
+ delta_N: 4
+ ###########################################################################
+ # percentage p. Float (0 to 100) or RANDOM
+ percentage_p: 5
+ ###########################################################################
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dct: 6
+ ###########################################################################
+ # Distance over land from the transmit and receive antennas to the coast (km)
+ Dcr: 7
+ ###########################################################################
+ # Effective height of interfering antenna (m)
+ Hte: 8
+ ###########################################################################
+ # Effective height of interfered-with antenna (m)
+ Hre: 9
+ ###########################################################################
+ # Latitude of transmitter
+ tx_lat: 10
+ ###########################################################################
+ # Latitude of receiver
+ rx_lat: 11
+ ###########################################################################
+ # Antenna polarization. Possible values are "horizontal", "vertical"
+ polarization: horizontal
+ ###########################################################################
+ # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE)
+ clutter_loss: TRUE
+
+ # HDFSS propagation parameters
+ param_hdfss:
+ ###########################################################################
+ # HDFSS position relative to building it is on. Possible values are
+ # ROOFTOP and BUILDINGSIDE
+ es_position: BUILDINGSIDE
+ ###########################################################################
+ # Enable shadowing loss
+ shadow_enabled: FALSE
+ ###########################################################################
+ # Enable building entry loss
+ building_loss_enabled: FALSE
+ ###########################################################################
+ # Enable interference from IMT stations at the same building as the HDFSS
+ same_building_enabled: TRUE
+ ###########################################################################
+ # Enable diffraction loss
+ diffraction_enabled: FALSE
+ ###########################################################################
+ # Building entry loss type applied between BSs and HDFSS ES. Options are:
+ # P2109_RANDOM: random probability at P.2109 model, considering elevation
+ # P2109_FIXED: fixed probability at P.2109 model, considering elevation.
+ # Probability must be specified in bs_building_entry_loss_prob.
+ # FIXED_VALUE: fixed value per BS. Value must be specified in
+ # bs_building_entry_loss_value.
+ bs_building_entry_loss_type: FIXED_VALUE
+ ###########################################################################
+ # Probability of building entry loss not exceeded if
+ # bs_building_entry_loss_type = P2109_FIXED
+ bs_building_entry_loss_prob: 0.19
+ ###########################################################################
+ # Value in dB of building entry loss if
+ # bs_building_entry_loss_type = FIXED_VALUE
+ bs_building_entry_loss_value: 47
+single_space_station:
+ ###########################################################################
+ # Sensor center frequency [MHz]
+ frequency: 1234
+ ###########################################################################
+ # Sensor bandwidth [MHz]
+ bandwidth: 456
+ ###########################################################################
+ # System receive noise temperature [K]
+ noise_temperature: 300
+ ###########################################################################
+ # Peak transmit power spectral density (clear sky) [dBW/Hz]
+ tx_power_density: -65
+ # Adjacent channel selectivity [dB]
+ adjacent_ch_selectivity: 13.1
+ geometry:
+ ###########################################################################
+ # Satellite altitude [meters]
+ altitude: 6
+ ###########################################################################
+ # earth station altitude [meters]
+ es_altitude: 33
+ ###########################################################################
+ # earth station lat long [deg]
+ es_lat_deg: 11
+ ###########################################################################
+ # earth station lat long [deg]
+ es_long_deg: 3.9
+ ###########################################################################
+ # Azimuth angle [degrees]
+ azimuth:
+ ###########################################################################
+ # Type of azimuth. May be "FIXED"
+ type: FIXED
+ ###########################################################################
+ # Value of azimuth when type == "FIXED" [deg]
+ fixed: 0
+ ###########################################################################
+ # Elevation angle [degrees]
+ elevation:
+ # Type of elevation. May be "FIXED"
+ type: FIXED
+ # Value of elevation when type == "FIXED" [deg]
+ fixed: 60
+ ###########################################################################
+ # Station 2d location [meters]:
+ location:
+ ###########################################################################
+ # location.type == FIXED means that the ES will be always at SAME fixed location
+ type: FIXED
+ ###########################################################################
+ # Value of position (x,y) when type == "FIXED" [(m, m)]
+ fixed:
+ lat_deg: 10
+ long_deg: 100
+ antenna:
+ ###########################################################################
+ # Choose the antenna pattern. Can be one of:
+ # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855"
+ pattern: ITU-R S.465
+ ###########################################################################
+ # Earth station peak receive antenna gain [dBi]
+ gain: 28
+ itu_r_f_699:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_465:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_580:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_1855:
+ ###########################################################################
+ # Antenna diameter [m]
+ diameter: 1.1
+ itu_r_s_465_modified:
+ ###########################################################################
+ # Antenna envelope gain [dBi]
+ envelope_gain: -4
+ itu_reg_rr_a7_3:
+ ###########################################################################
+ # Antenna diameter [m]
+ # if no diameter is passed, a diameter will be assumed according to document
+ diameter: 2.12
+ ###########################################################################
+ # Selected channel model
+ channel_model: "P619"
+
+ ###########################################################################
+ # season - season of the year.
+ # Only actually useful for P619, but we can't put it inside param_p619 without changing more code
+ season: SUMMER
+
+mss_d2d:
+ # MSS_D2D system name
+ name: SystemA
+ # MSS_D2D system center frequency in MHz
+ frequency: 2170.0
+ # MSS_D2d system bandwidth in MHz
+ bandwidth: 5.0
+ # MSS_D2D cell radius in network topology [m]
+ cell_radius: 19000
+ # Satellite power density in dBW/Hz
+ tx_power_density: -30
+ # Number of sectors
+ num_sectors: 19
+ sat_is_active_if:
+ # for a satellite to be active, it needs to respect ALL conditions
+ conditions:
+ - LAT_LONG_INSIDE_COUNTRY
+ - MINIMUM_ELEVATION_FROM_ES
+ - MAXIMUM_ELEVATION_FROM_ES
+ minimum_elevation_from_es: 1.112
+ maximum_elevation_from_es: 1.113
+ lat_long_inside_country:
+ # You may specify another shp file for country borders reference
+ # country_shapes_filename: sharc/topology/countries/ne_110m_admin_0_countries.shp
+ country_names:
+ - Brazil
+ - Ecuador
+ # margin from inside of border [km]
+ # if positive, makes border smaller by x km
+ # if negative, makes border bigger by x km
+ margin_from_border: 11.1241
+ # Satellite antenna pattern
+ # Antenna pattern from ITU-R S.1528
+ # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO"
+ antenna_pattern: ITU-R-S.1528-Taylor
+ antenna_s1528:
+ # Satellite antenna gain dBi
+ antenna_gain: 34.1
+ ### The following parameters are used for S.1528-Taylor antenna pattern
+ # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum
+ # gain and the gain at the peak of the first side lobe.
+ slr: 20.0
+ # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function)
+ n_side_lobes: 2
+ # Radial (l_r) and transverse (l_t) sizes of the effective radiating area of the satellite transmitt antenna (m)
+ l_r: 1.6
+ l_t: 1.6
+ # channel model, possible values are "FSPL" (free-space path loss),
+ # "SatelliteSimple" (FSPL + 4 + clutter loss)
+ # "P619"
+ channel_model: P619
+ ###########################################################################
+ # P619 parameters
+ param_p619:
+ ###########################################################################
+ # altitude of ES system [m]
+ earth_station_alt_m: 0
+ ###########################################################################
+ # latitude of ES system [m]
+ earth_station_lat_deg: 0
+ ###########################################################################
+ # difference between longitudes of IMT-NTN station and FSS-ES system [deg]
+ # (positive if space-station is to the East of earth-station)
+ earth_station_long_diff_deg: 0.75
+ ###########################################################################
+ # year season: SUMMER of WINTER
+ season: SUMMER
+ # Parameters for the orbits
+ orbits:
+ - n_planes: 20
+ inclination_deg: 54.5
+ perigee_alt_km: 525.0
+ apogee_alt_km: 525.0
+ sats_per_plane: 32
+ long_asc_deg: 18.0
+ phasing_deg: 3.9
+ - n_planes: 12
+ inclination_deg: 26.0
+ perigee_alt_km: 580.0
+ apogee_alt_km: 580.0
+ sats_per_plane: 20
+ long_asc_deg: 30.0
+ phasing_deg: 2.0
+ - n_planes: 26
+ inclination_deg: 97.77
+ perigee_alt_km: 595
+ apogee_alt_km: 595.0
+ sats_per_plane: 30
+ long_asc_deg: 14.0
+ phasing_deg: 7.8
\ No newline at end of file
diff --git a/tests/parameters/test_parameters.py b/tests/parameters/test_parameters.py
new file mode 100644
index 000000000..66d417db7
--- /dev/null
+++ b/tests/parameters/test_parameters.py
@@ -0,0 +1,606 @@
+from pathlib import Path
+import unittest
+from sharc.parameters.parameters import Parameters
+import numpy as np
+from contextlib import contextmanager
+
+
+@contextmanager
+def assertDoesNotRaise(test_case):
+ try:
+ yield
+ except Exception as e:
+ test_case.fail(f"Unexpected exception raised: {type(e).__name__}:\n{e}")
+
+
+class ParametersTest(unittest.TestCase):
+ """Run Parameter class tests.
+ """
+
+ def setUp(self):
+ self.parameters = Parameters()
+ param_file = Path(__file__).parent.resolve() / \
+ 'parameters_for_testing.yaml'
+ self.parameters.set_file_name(param_file)
+ self.parameters.read_params()
+
+ def test_parameters_imt(self):
+ """Unit test for ParametersIMT
+ """
+ self.assertEqual(self.parameters.imt.topology.type, "INDOOR")
+ self.assertEqual(
+ self.parameters.imt.minimum_separation_distance_bs_ue, 1.3,
+ )
+ self.assertEqual(self.parameters.imt.interfered_with, False)
+ self.assertEqual(self.parameters.imt.frequency, 24360)
+ self.assertEqual(self.parameters.imt.bandwidth, 200.5)
+ self.assertEqual(self.parameters.imt.rb_bandwidth, 0.181)
+ self.assertEqual(self.parameters.imt.spectral_mask, "3GPP E-UTRA")
+ self.assertEqual(self.parameters.imt.spurious_emissions, -13.1)
+ self.assertEqual(self.parameters.imt.guard_band_ratio, 0.14)
+
+ self.assertEqual(self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range, (-10.1, 11.2))
+ self.assertEqual(self.parameters.imt.bs.antenna.array.vertical_beamsteering_range, (0., 180.))
+ self.assertEqual(self.parameters.imt.bs.load_probability, 0.2)
+ self.assertEqual(self.parameters.imt.bs.conducted_power, 11.1)
+ self.assertEqual(self.parameters.imt.bs.height, 6.1)
+ self.assertEqual(self.parameters.imt.bs.noise_figure, 10.1)
+ self.assertEqual(self.parameters.imt.bs.ohmic_loss, 3.1)
+ self.assertEqual(self.parameters.imt.uplink.attenuation_factor, 0.4)
+ self.assertEqual(self.parameters.imt.uplink.sinr_min, -10.0)
+ self.assertEqual(self.parameters.imt.uplink.sinr_max, 22.0)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.horizontal_beamsteering_range, (-180., 180.))
+ self.assertEqual(self.parameters.imt.ue.antenna.array.vertical_beamsteering_range, (0., 180.))
+ self.assertEqual(self.parameters.imt.ue.k, 3)
+ self.assertEqual(self.parameters.imt.ue.k_m, 1)
+ self.assertEqual(self.parameters.imt.ue.indoor_percent, 5.0)
+ self.assertEqual(
+ self.parameters.imt.ue.distribution_type,
+ "ANGLE_AND_DISTANCE",
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.distribution_distance,
+ "UNIFORM",
+ )
+ self.assertEqual(self.parameters.imt.ue.azimuth_range, (-70, 90))
+ self.assertEqual(self.parameters.imt.ue.tx_power_control, True)
+ self.assertEqual(self.parameters.imt.ue.p_o_pusch, -95.0)
+ self.assertEqual(self.parameters.imt.ue.alpha, 1.0)
+ self.assertEqual(self.parameters.imt.ue.p_cmax, 22.0)
+ self.assertEqual(self.parameters.imt.ue.power_dynamic_range, 63.0)
+ self.assertEqual(self.parameters.imt.ue.height, 1.5)
+ self.assertEqual(self.parameters.imt.ue.noise_figure, 10.0)
+ self.assertEqual(self.parameters.imt.ue.ohmic_loss, 3.0)
+ self.assertEqual(self.parameters.imt.ue.body_loss, 4.0)
+ self.assertEqual(self.parameters.imt.downlink.attenuation_factor, 0.6)
+ self.assertEqual(self.parameters.imt.downlink.sinr_min, -10.0)
+ self.assertEqual(self.parameters.imt.downlink.sinr_max, 30.0)
+ self.assertEqual(self.parameters.imt.channel_model, "FSPL")
+ self.assertEqual(self.parameters.imt.los_adjustment_factor, 18.0)
+ self.assertEqual(self.parameters.imt.shadowing, False)
+
+ """Test ParametersImtAntenna parameters
+ """
+ self.assertEqual(
+ self.parameters.imt.adjacent_antenna_model,
+ "BEAMFORMING",
+ )
+ self.assertEqual(self.parameters.imt.bs.antenna.array.normalization, False)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.normalization, False)
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.normalization_file,
+ "antenna/beamforming_normalization/bs_norm.npz",
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.normalization_file,
+ "antenna/beamforming_normalization/ue_norm.npz",
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.element_pattern,
+ "F1336",
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.element_pattern,
+ "F1336",
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.minimum_array_gain, -200,
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.minimum_array_gain, -200,
+ )
+ self.assertEqual(self.parameters.imt.bs.antenna.array.downtilt, 6)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.element_max_g, 5)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.element_max_g, 5)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.element_phi_3db, 65)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.element_phi_3db, 90)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.element_theta_3db, 65)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.element_theta_3db, 90)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.n_rows, 8)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.n_rows, 4)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.n_columns, 8)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.n_columns, 4)
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.element_horiz_spacing, 0.5,
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.element_horiz_spacing, 0.5,
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.element_vert_spacing, 0.5,
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.element_vert_spacing, 0.5,
+ )
+ self.assertEqual(self.parameters.imt.bs.antenna.array.element_am, 30)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.element_am, 25)
+ self.assertEqual(self.parameters.imt.bs.antenna.array.element_sla_v, 30)
+ self.assertEqual(self.parameters.imt.ue.antenna.array.element_sla_v, 25)
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.multiplication_factor, 12,
+ )
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.multiplication_factor, 12,
+ )
+
+ self.assertEqual(self.parameters.imt.topology.central_altitude, 1111)
+ self.assertEqual(self.parameters.imt.topology.central_latitude, 21.12)
+ self.assertEqual(self.parameters.imt.topology.central_longitude, -12.134)
+
+ """Test ParametersSubarrayImt
+ """
+ # testing default value not enabled
+ self.assertEqual(
+ self.parameters.imt.ue.antenna.array.subarray.is_enabled, False,
+ )
+ # testing a fictitious configuration
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.subarray.is_enabled, True,
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.subarray.eletrical_downtilt, 9,
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.subarray.n_rows, 10,
+ )
+ self.assertEqual(
+ self.parameters.imt.bs.antenna.array.subarray.element_vert_spacing, 0.05,
+ )
+ """Test ParametersHotspot
+ """
+ self.assertEqual(self.parameters.imt.topology.hotspot.num_hotspots_per_cell, 1)
+ self.assertEqual(self.parameters.imt.topology.hotspot.max_dist_hotspot_ue, 99.9)
+ self.assertEqual(self.parameters.imt.topology.hotspot.min_dist_bs_hotspot, 1.2)
+ self.assertEqual(self.parameters.imt.topology.hotspot.intersite_distance, 321)
+ self.assertEqual(self.parameters.imt.topology.hotspot.num_clusters, 7)
+ self.assertEqual(self.parameters.imt.topology.hotspot.wrap_around, True)
+
+ """Test ParametersMacrocell
+ """
+ self.assertEqual(self.parameters.imt.topology.macrocell.intersite_distance, 543)
+ self.assertEqual(self.parameters.imt.topology.macrocell.num_clusters, 7)
+ self.assertEqual(self.parameters.imt.topology.macrocell.wrap_around, True)
+
+ """Test ParametersSingleBaseStation
+ """
+ self.assertEqual(self.parameters.imt.topology.single_bs.cell_radius, 543)
+ self.assertEqual(
+ self.parameters.imt.topology.single_bs.intersite_distance,
+ self.parameters.imt.topology.single_bs.cell_radius * 3 / 2,
+ )
+ self.assertEqual(self.parameters.imt.topology.single_bs.num_clusters, 2)
+
+ """Test ParametersIndoor
+ """
+ self.assertEqual(self.parameters.imt.topology.indoor.basic_path_loss, "FSPL")
+ self.assertEqual(self.parameters.imt.topology.indoor.n_rows, 3)
+ self.assertEqual(self.parameters.imt.topology.indoor.n_colums, 2)
+ self.assertEqual(self.parameters.imt.topology.indoor.num_imt_buildings, 2)
+ self.assertEqual(self.parameters.imt.topology.indoor.street_width, 30.1)
+ self.assertEqual(self.parameters.imt.topology.indoor.intersite_distance, 40.1)
+ self.assertEqual(self.parameters.imt.topology.indoor.num_cells, 3)
+ self.assertEqual(self.parameters.imt.topology.indoor.num_floors, 1)
+ self.assertEqual(self.parameters.imt.topology.indoor.ue_indoor_percent, .95)
+ self.assertEqual(
+ self.parameters.imt.topology.indoor.building_class,
+ "THERMALLY_EFFICIENT",
+ )
+
+ self.assertEqual(self.parameters.imt.topology.ntn.bs_height, self.parameters.imt.bs.height)
+ self.assertEqual(self.parameters.imt.topology.ntn.cell_radius, 123)
+ self.assertEqual(
+ self.parameters.imt.topology.ntn.intersite_distance,
+ self.parameters.imt.topology.ntn.cell_radius * np.sqrt(3),
+ )
+ self.assertEqual(self.parameters.imt.topology.ntn.bs_azimuth, 45)
+ self.assertEqual(self.parameters.imt.topology.ntn.bs_elevation, 45)
+ self.assertEqual(self.parameters.imt.topology.ntn.num_sectors, 19)
+
+ def test_parameters_haps(self):
+ """Test ParametersHaps
+ """
+ self.assertEqual(self.parameters.haps.frequency, 27251.1)
+ self.assertEqual(self.parameters.haps.bandwidth, 200.0)
+ self.assertEqual(self.parameters.haps.antenna_gain, 28.1)
+ self.assertEqual(self.parameters.haps.eirp_density, 4.4)
+ self.assertEqual(
+ self.parameters.haps.tx_power_density,
+ self.parameters.haps.eirp_density - self.parameters.haps.antenna_gain - 60,
+ )
+ self.assertEqual(self.parameters.haps.altitude, 20001.1)
+ self.assertEqual(self.parameters.haps.lat_deg, 0.1)
+ self.assertEqual(self.parameters.haps.elevation, 270.0)
+ self.assertEqual(self.parameters.haps.azimuth, 0)
+ self.assertEqual(self.parameters.haps.antenna_pattern, "OMNI")
+ self.assertEqual(self.parameters.haps.earth_station_alt_m, 0.0)
+ self.assertEqual(self.parameters.haps.earth_station_lat_deg, 0.0)
+ self.assertEqual(self.parameters.haps.earth_station_long_diff_deg, 0.0)
+ self.assertEqual(self.parameters.haps.season, "SUMMER")
+ self.assertEqual(self.parameters.haps.acs, 30.0)
+ self.assertEqual(self.parameters.haps.channel_model, "P619")
+ self.assertEqual(self.parameters.haps.antenna_l_n, -25)
+
+ def test_parameters_single_earth_station(self):
+ """Test ParametersSingleEarthStation
+ """
+ self.assertEqual(self.parameters.single_earth_station.frequency, 8250)
+ self.assertEqual(self.parameters.single_earth_station.bandwidth, 100)
+ self.assertEqual(
+ self.parameters.single_earth_station.adjacent_ch_selectivity, 20.0,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.tx_power_density, -65.0,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.noise_temperature, 300,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.height, 6,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.azimuth.type,
+ "FIXED",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.azimuth.fixed, 0,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.azimuth.uniform_dist.min, -180,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.azimuth.uniform_dist.max, 180,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.elevation.type,
+ "FIXED",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.elevation.fixed, 60,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.elevation.uniform_dist.min, 30,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.elevation.uniform_dist.max, 65,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.type,
+ "CELL",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.fixed.x, 10,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.fixed.y, 100,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.uniform_dist.min_dist_to_center,
+ 101,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.uniform_dist.max_dist_to_center,
+ 102,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.cell.min_dist_to_bs,
+ 100,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.geometry.location.network.min_dist_to_bs,
+ 150,
+ )
+ self.assertEqual(self.parameters.single_earth_station.antenna.gain, 28)
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_r_f_699.diameter, 1.1,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_r_f_699.frequency,
+ self.parameters.single_earth_station.frequency,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_r_f_699.antenna_gain,
+ self.parameters.single_earth_station.antenna.gain,
+ )
+
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_reg_rr_a7_3.diameter,
+ 2.12,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_reg_rr_a7_3.frequency,
+ self.parameters.single_earth_station.frequency,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.antenna.itu_reg_rr_a7_3.antenna_gain,
+ self.parameters.single_earth_station.antenna.gain,
+ )
+
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p619.earth_station_alt_m, 1200,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p619.space_station_alt_m,
+ 540000,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p619.earth_station_lat_deg, 13,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p619.earth_station_long_diff_deg,
+ 10,
+ )
+
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.atmospheric_pressure, 1,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.air_temperature, 2,
+ )
+ self.assertEqual(self.parameters.single_earth_station.param_p452.N0, 3)
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.delta_N, 4,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.percentage_p, 5,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.Dct, 6,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.Dcr, 7,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.Hte, 8,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.Hre, 9,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.tx_lat, 10,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.rx_lat, 11,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.polarization,
+ "horizontal",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_p452.clutter_loss, True,
+ )
+
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.es_position,
+ "BUILDINGSIDE",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.shadow_enabled,
+ False,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.building_loss_enabled,
+ False,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.same_building_enabled, True,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.diffraction_enabled,
+ False,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_type,
+ "FIXED_VALUE",
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_prob,
+ 0.19,
+ )
+ self.assertEqual(
+ self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_value,
+ 47,
+ )
+
+ self.parameters.single_earth_station.geometry.azimuth.uniform_dist.max = None
+ # this should still not throw, since azimuth is using fixed type
+ self.parameters.single_earth_station.validate()
+
+ # now it should throw:
+ with self.assertRaises(ValueError) as err_context:
+ self.parameters.single_earth_station.geometry.azimuth.type = "UNIFORM_DIST"
+ self.parameters.single_earth_station.validate()
+
+ self.assertTrue(
+ 'azimuth.uniform_dist.max' in str(
+ err_context.exception,
+ ),
+ )
+
+ def test_parametes_mss_d2d(self):
+ """Test ParametersRas
+ """
+ with assertDoesNotRaise(self):
+ self.parameters.mss_d2d.validate("mss_d2d")
+ self.assertEqual(self.parameters.mss_d2d.name, 'SystemA')
+ self.assertEqual(self.parameters.mss_d2d.frequency, 2170.0)
+ self.assertEqual(self.parameters.mss_d2d.bandwidth, 5.0)
+ self.assertEqual(self.parameters.mss_d2d.cell_radius, 19000)
+ self.assertEqual(self.parameters.mss_d2d.tx_power_density, -30)
+ self.assertEqual(self.parameters.mss_d2d.num_sectors, 19)
+ self.assertEqual(self.parameters.mss_d2d.antenna_diamter, 1.0)
+ self.assertEqual(self.parameters.mss_d2d.antenna_l_s, -6.75)
+ self.assertEqual(self.parameters.mss_d2d.antenna_3_dB_bw, 4.4127)
+ self.assertEqual(self.parameters.mss_d2d.antenna_pattern, 'ITU-R-S.1528-Taylor')
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.antenna_pattern, 'ITU-R-S.1528-Taylor')
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.antenna_gain, 34.1)
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.slr, 20)
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.n_side_lobes, 2)
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.l_r, 1.6)
+ self.assertEqual(self.parameters.mss_d2d.antenna_s1528.l_t, 1.6)
+ self.assertEqual(self.parameters.mss_d2d.channel_model, 'P619')
+ self.assertEqual(self.parameters.mss_d2d.param_p619.earth_station_alt_m, 0.0)
+ self.assertEqual(self.parameters.mss_d2d.param_p619.earth_station_lat_deg, 0.0)
+ self.assertEqual(self.parameters.mss_d2d.param_p619.earth_station_long_diff_deg, 0.0)
+
+ self.assertEqual(
+ self.parameters.mss_d2d.sat_is_active_if.conditions,
+ ["LAT_LONG_INSIDE_COUNTRY", "MINIMUM_ELEVATION_FROM_ES", "MAXIMUM_ELEVATION_FROM_ES"],
+ )
+ self.assertEqual(len(self.parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.country_names), 2)
+ self.assertEqual(self.parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.country_names[0], "Brazil")
+ self.assertEqual(self.parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.country_names[1], "Ecuador")
+
+ self.assertEqual(self.parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.margin_from_border, 11.1241)
+ self.assertEqual(self.parameters.mss_d2d.sat_is_active_if.minimum_elevation_from_es, 1.112)
+ self.assertEqual(self.parameters.mss_d2d.sat_is_active_if.maximum_elevation_from_es, 1.113)
+ self.assertEqual(self.parameters.mss_d2d.param_p619.season, 'SUMMER')
+ self.assertTrue(isinstance(self.parameters.mss_d2d.orbits, list))
+ expected_orbit_params = [
+ {
+ 'n_planes': 20,
+ 'inclination_deg': 54.5,
+ 'perigee_alt_km': 525.0,
+ 'apogee_alt_km': 525.0,
+ 'sats_per_plane': 32,
+ 'long_asc_deg': 18.0,
+ 'phasing_deg': 3.9,
+ },
+ {
+ 'n_planes': 12,
+ 'inclination_deg': 26.0,
+ 'perigee_alt_km': 580.0,
+ 'apogee_alt_km': 580.0,
+ 'sats_per_plane': 20,
+ 'long_asc_deg': 30.0,
+ 'phasing_deg': 2.0,
+ },
+ {
+ 'n_planes': 26,
+ 'inclination_deg': 97.77,
+ 'perigee_alt_km': 595.0,
+ 'apogee_alt_km': 595.0,
+ 'sats_per_plane': 30,
+ 'long_asc_deg': 14.0,
+ 'phasing_deg': 7.8,
+ },
+ ]
+ for i, orbit_params in enumerate(self.parameters.mss_d2d.orbits):
+ self.assertEqual(orbit_params.n_planes, expected_orbit_params[i]['n_planes'])
+ self.assertEqual(orbit_params.inclination_deg, expected_orbit_params[i]['inclination_deg'])
+ self.assertEqual(orbit_params.perigee_alt_km, expected_orbit_params[i]['perigee_alt_km'])
+ self.assertEqual(orbit_params.apogee_alt_km, expected_orbit_params[i]['apogee_alt_km'])
+ self.assertEqual(orbit_params.sats_per_plane, expected_orbit_params[i]['sats_per_plane'])
+ self.assertEqual(orbit_params.long_asc_deg, expected_orbit_params[i]['long_asc_deg'])
+ self.assertEqual(orbit_params.phasing_deg, expected_orbit_params[i]['phasing_deg'])
+
+ def test_parameters_single_space_station(self):
+ """Test ParametersSinglespaceStation
+ """
+ self.assertEqual(self.parameters.single_space_station.is_space_to_earth, True)
+ self.assertEqual(self.parameters.single_space_station.frequency, 1234)
+ self.assertEqual(self.parameters.single_space_station.bandwidth, 456)
+ self.assertEqual(
+ self.parameters.single_space_station.adjacent_ch_selectivity, 13.1,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.tx_power_density, -65.0,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.noise_temperature, 300,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.altitude, 6,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.es_altitude, 33,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.es_lat_deg, 11,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.es_long_deg, 3.9,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.azimuth.type,
+ "FIXED",
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.azimuth.fixed, 0,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.elevation.type,
+ "FIXED",
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.elevation.fixed, 60,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.location.type,
+ "FIXED",
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.location.fixed.lat_deg, 10,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.geometry.location.fixed.long_deg, 100,
+ )
+ self.assertEqual(self.parameters.single_space_station.antenna.gain, 28)
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_r_f_699.diameter, 1.1,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_r_f_699.frequency,
+ self.parameters.single_space_station.frequency,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_r_f_699.antenna_gain,
+ self.parameters.single_space_station.antenna.gain,
+ )
+
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_reg_rr_a7_3.diameter,
+ 2.12,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_reg_rr_a7_3.frequency,
+ self.parameters.single_space_station.frequency,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.antenna.itu_reg_rr_a7_3.antenna_gain,
+ self.parameters.single_space_station.antenna.gain,
+ )
+
+ self.assertEqual(
+ self.parameters.single_space_station.param_p619.earth_station_alt_m, self.parameters.single_space_station.geometry.es_altitude,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.param_p619.space_station_alt_m,
+ self.parameters.single_space_station.geometry.altitude,
+ )
+ self.assertEqual(
+ self.parameters.single_space_station.param_p619.earth_station_lat_deg, self.parameters.single_space_station.geometry.es_lat_deg,
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_adjacent_channel.py b/tests/test_adjacent_channel.py
index 2b785f714..020f8a7e6 100644
--- a/tests/test_adjacent_channel.py
+++ b/tests/test_adjacent_channel.py
@@ -16,6 +16,9 @@
from sharc.antenna.antenna_omni import AntennaOmni
from sharc.station_factory import StationFactory
from sharc.propagation.propagation_factory import PropagationFactory
+from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology
+from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS
+
class SimulationAdjacentTest(unittest.TestCase):
@@ -28,88 +31,95 @@ def setUp(self):
self.param.general.overwrite_output = True
self.param.general.seed = 101
- self.param.imt.topology = "SINGLE_BS"
- self.param.imt.wrap_around = False
- self.param.imt.num_clusters = 2
- self.param.imt.intersite_distance = 150
+ self.param.imt.topology = ParametersImtTopology(
+ type="SINGLE_BS",
+ single_bs=ParametersSingleBS(
+ num_clusters=2,
+ intersite_distance=150,
+ cell_radius=2 * 150 / 3,
+ ),
+ )
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
- self.param.imt.frequency = 10000
+ self.param.imt.frequency = 10000.0
self.param.imt.bandwidth = 100
self.param.imt.spectral_mask = "IMT-2020"
self.param.imt.spurious_emissions = -13
self.param.imt.rb_bandwidth = 0.180
self.param.imt.guard_band_ratio = 0.1
self.param.imt.ho_margin = 3
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 10
- self.param.imt.bs_height = 6
- self.param.imt.bs_acs = 30
- self.param.imt.bs_noise_figure = 7
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 2
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 0
- self.param.imt.ue_distribution_distance = "RAYLEIGH"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 0.8
- self.param.imt.ue_p_cmax = 20
- self.param.imt.ue_conducted_power = 10
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_aclr = 20
- self.param.imt.ue_acs = 25
- self.param.imt.ue_noise_figure = 9
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 10
+ self.param.imt.bs.height = 6
+ self.param.imt.bs.acs = 30
+ self.param.imt.bs.noise_figure = 7
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 2
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 0
+ self.param.imt.ue.distribution_distance = "RAYLEIGH"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 0.8
+ self.param.imt.ue.p_cmax = 20
+ self.param.imt.ue.conducted_power = 10
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.aclr = 20
+ self.param.imt.ue.acs = 25
+ self.param.imt.ue.noise_figure = 9
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
- self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL)
+ # probability of line-of-sight (not for FSPL)
+ self.param.imt.line_of_sight_prob = 0.75
self.param.imt.shadowing = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.ue_normalization = False
-
- self.param.antenna_imt.bs_normalization_file = None
- self.param.antenna_imt.bs_element_pattern = "M2101"
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_element_max_g = 10
- self.param.antenna_imt.bs_element_phi_3db = 80
- self.param.antenna_imt.bs_element_theta_3db = 80
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 16
- self.param.antenna_imt.bs_n_columns = 16
- self.param.antenna_imt.bs_element_horiz_spacing = 1
- self.param.antenna_imt.bs_element_vert_spacing = 1
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 10
-
- self.param.antenna_imt.ue_element_pattern = "M2101"
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_normalization_file = None
- self.param.antenna_imt.ue_element_max_g = 5
- self.param.antenna_imt.ue_element_phi_3db = 65
- self.param.antenna_imt.ue_element_theta_3db = 65
- self.param.antenna_imt.ue_element_am = 30
- self.param.antenna_imt.ue_element_sla_v = 30
- self.param.antenna_imt.ue_n_rows = 2
- self.param.antenna_imt.ue_n_columns = 1
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
+
+ self.param.imt.bs.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.type = "ARRAY"
+
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.normalization = False
+
+ self.param.imt.bs.antenna.array.normalization_file = None
+ self.param.imt.bs.antenna.array.element_pattern = "M2101"
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.element_max_g = 10
+ self.param.imt.bs.antenna.array.element_phi_3db = 80
+ self.param.imt.bs.antenna.array.element_theta_3db = 80
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 16
+ self.param.imt.bs.antenna.array.n_columns = 16
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 1
+ self.param.imt.bs.antenna.array.element_vert_spacing = 1
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 10
+
+ self.param.imt.ue.antenna.array.element_pattern = "M2101"
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.normalization_file = None
+ self.param.imt.ue.antenna.array.element_max_g = 5
+ self.param.imt.ue.antenna.array.element_phi_3db = 65
+ self.param.imt.ue.antenna.array.element_theta_3db = 65
+ self.param.imt.ue.antenna.array.element_am = 30
+ self.param.imt.ue.antenna.array.element_sla_v = 30
+ self.param.imt.ue.antenna.array.n_rows = 2
+ self.param.imt.ue.antenna.array.n_columns = 1
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
self.param.fss_ss.frequency = 5000
self.param.fss_ss.bandwidth = 100
@@ -123,7 +133,7 @@ def setUp(self):
self.param.fss_ss.antenna_pattern = "OMNI"
self.param.fss_ss.imt_altitude = 1000
self.param.fss_ss.imt_lat_deg = -23.5629739
- self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75)
+ self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75)
self.param.fss_ss.channel_model = "FSPL"
self.param.fss_ss.line_of_sight_prob = 0.01
self.param.fss_ss.surf_water_vapour_density = 7.5
@@ -131,8 +141,6 @@ def setUp(self):
self.param.fss_ss.time_ratio = 0.5
self.param.fss_ss.antenna_l_s = -20
self.param.fss_ss.acs = 10
- self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_ss.EARTH_RADIUS = 6371000
def test_simulation_2bs_4ue_downlink(self):
self.param.general.imt_link = "DOWNLINK"
@@ -147,85 +155,125 @@ def test_simulation_2bs_4ue_downlink(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
# test connection method
self.simulation.connect_ue_to_bs()
- self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]})
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
# We do not test the selection method here because in this specific
# scenario we do not want to change the order of the UE's
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23],
- [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]]),
- atol=1e-2)
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ np.array([
+ [88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23],
+ [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23],
+ ]),
+ atol=1e-2,
+ )
# test scheduler and bandwidth allocation
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ npt.assert_allclose(
+ self.simulation.ue.bandwidth,
+ bandwidth_per_ue * np.ones(4),
+ atol=1e-2,
+ )
# there is no power control, so BS's will transmit at maximum power
self.simulation.power_control()
- tx_power = 10 - 10*math.log10(2)
- npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2)
- npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2)
-
- npt.assert_equal(self.simulation.bs.spectral_mask.mask_dbm,np.array([-50, -20, -10, -10, -10, -20, -50]))
+ tx_power = 10 - 10 * math.log10(2)
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[0], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[1], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
# create system
- self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss)
- self.simulation.system.x = np.array([0.01]) # avoids zero-division
+ self.simulation.system = StationFactory.generate_fss_space_station(
+ self.param.fss_ss,
+ )
+ self.simulation.system.x = np.array([0.01]) # avoids zero-division
self.simulation.system.y = np.array([0])
+ self.simulation.system.z = np.array([self.param.fss_ss.altitude])
self.simulation.system.height = np.array([self.param.fss_ss.altitude])
- # test the method that calculates interference from IMT UE to FSS space station
+ # test the method that calculates interference from IMT UE to FSS space
+ # station
self.simulation.calculate_external_interference()
# check coupling loss
- coupling_loss_imt_system_adj = np.array([209.52-51-1, 209.52-51-1, 209.52-51-2, 209.52-51-2])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system_adjacent,
- coupling_loss_imt_system_adj,
- atol=1e-2)
+ coupling_loss_imt_system_adj = np.array(
+ [209.52 - 51 - 1, 209.52 - 51 - 1, 209.52 - 51 - 2, 209.52 - 51 - 2],
+ ).reshape(-1, 1)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system_adjacent,
+ coupling_loss_imt_system_adj,
+ atol=1e-2,
+ )
# check interference generated by BS to FSS space station
- interf_pow = np.power(10, 0.1*(-50))*100
- rx_interf_bs1 = 10*math.log10(interf_pow)\
- - coupling_loss_imt_system_adj[0]
- rx_interf_bs2 = 10*math.log10(interf_pow)\
- - coupling_loss_imt_system_adj[2]
- rx_interference = 10*math.log10(math.pow(10,0.1*rx_interf_bs1) + \
- math.pow(10,0.1*rx_interf_bs2)) + 3
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
-
+ interf_pow = np.power(10, 0.1 * (self.param.imt.bs.conducted_power))
+ rx_interf_bs1 = 10 * math.log10(interf_pow)\
+ - coupling_loss_imt_system_adj[0]
+ rx_interf_bs2 = 10 * math.log10(interf_pow)\
+ - coupling_loss_imt_system_adj[2]
+ rx_interference = 10 * math.log10(
+ math.pow(10, 0.1 * rx_interf_bs1) +
+ math.pow(10, 0.1 * rx_interf_bs2),
+ )
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
def test_simulation_2bs_4ue_uplink(self):
self.param.general.imt_link = "UPLINK"
@@ -240,82 +288,118 @@ def test_simulation_2bs_4ue_uplink(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
# test connection method
self.simulation.connect_ue_to_bs()
- self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]})
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
# We do not test the selection method here because in this specific
# scenario we do not want to change the order of the UE's
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
- coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23],
- [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]])
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- coupling_loss_imt,
- atol=1e-2)
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
+ coupling_loss_imt = np.array([
+ [88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23],
+ [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23],
+ ])
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ coupling_loss_imt,
+ atol=1e-2,
+ )
# test scheduler and bandwidth allocation
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ npt.assert_allclose(
+ self.simulation.ue.bandwidth,
+ bandwidth_per_ue * np.ones(4),
+ atol=1e-2,
+ )
# there is no power control, so UE's will transmit at maximum power
self.simulation.power_control()
- npt.assert_equal(self.simulation.ue_power_diff,np.zeros(4))
+ npt.assert_equal(self.simulation.ue_power_diff, np.zeros(4))
tx_power = 20
- npt.assert_allclose(self.simulation.ue.tx_power, tx_power*np.ones(4))
+ npt.assert_allclose(self.simulation.ue.tx_power, tx_power * np.ones(4))
- npt.assert_equal(self.simulation.ue.spectral_mask.mask_dbm,np.array([-13, -13, -5, -20, -5, -13, -13]))
+ npt.assert_equal(
+ self.simulation.ue.spectral_mask.mask_dbm,
+ np.array([-13, -13, -5, -20, -5, -13, -13]),
+ )
# create system
- self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss)
+ self.simulation.system = StationFactory.generate_fss_space_station(
+ self.param.fss_ss,
+ )
self.simulation.system.x = np.array([0])
self.simulation.system.y = np.array([0])
+ self.simulation.system.z = np.array([self.param.fss_ss.altitude])
self.simulation.system.height = np.array([self.param.fss_ss.altitude])
- # test the method that calculates interference from IMT UE to FSS space station
+ # test the method that calculates interference from IMT UE to FSS space
+ # station
self.simulation.calculate_external_interference()
# check coupling loss
- coupling_loss_imt_system_adj = np.array([213.52-51-10, 213.52-51-11, 213.52-51-22, 213.52-51-23])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system_adjacent,
- coupling_loss_imt_system_adj,
- atol=1e-2)
+ coupling_loss_imt_system_adj = np.array(
+ [213.52 - 51 - 10, 213.52 - 51 - 11, 213.52 - 51 - 22, 213.52 - 51 - 23],
+ ).reshape(-1, 1)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system_adjacent,
+ coupling_loss_imt_system_adj,
+ atol=1e-2,
+ )
# check interference generated by UE to FSS space station
- interf_pow = np.power(10, 0.1*(-13))*100
- interference = 10*math.log10(interf_pow) \
- - coupling_loss_imt_system_adj
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + 3
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
-
+ interf_pow = np.power(10, 0.1 * (-13)) * 100
+ interference = 10 * math.log10(interf_pow) \
+ - coupling_loss_imt_system_adj
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference))) + 3
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
if __name__ == '__main__':
diff --git a/tests/test_antenna_beamforming_imt.py b/tests/test_antenna_beamforming_imt.py
index c520e8a76..d6c004698 100644
--- a/tests/test_antenna_beamforming_imt.py
+++ b/tests/test_antenna_beamforming_imt.py
@@ -10,107 +10,110 @@
import numpy.testing as npt
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.support.named_tuples import AntennaPar
from sharc.support.enumerations import StationType
+
class AntennaBeamformingImtTest(unittest.TestCase):
def setUp(self):
- #Array parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_normalization = False
- self.param.bs_normalization_file = None
- self.param.bs_element_pattern = "M2101"
- self.param.bs_minimum_array_gain = -200
- self.param.bs_downtilt = 0
-
- self.param.bs_element_max_g = 5
- self.param.bs_element_phi_3db = 80
- self.param.bs_element_theta_3db = 60
- self.param.bs_element_am = 30
- self.param.bs_element_sla_v = 30
- self.param.bs_n_rows = 16
- self.param.bs_n_columns = 16
- self.param.bs_element_horiz_spacing = 1
- self.param.bs_element_vert_spacing = 1
- self.param.bs_multiplication_factor = 12
-
- self.param.ue_element_pattern = "M2101"
- self.param.ue_normalization = False
- self.param.ue_normalization_file = None
- self.param.ue_minimum_array_gain = -200
-
- self.param.ue_element_max_g = 10
- self.param.ue_element_phi_3db = 75
- self.param.ue_element_theta_3db = 65
- self.param.ue_element_am = 25
- self.param.ue_element_sla_v = 35
- self.param.ue_n_rows = 2
- self.param.ue_n_columns = 2
- self.param.ue_element_horiz_spacing = 0.5
- self.param.ue_element_vert_spacing = 0.5
- self.param.ue_multiplication_factor = 12
+ # Array parameters
+ self.bs_param = ParametersAntennaImt()
+ self.ue_param = ParametersAntennaImt()
+
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.normalization = False
+ self.bs_param.normalization_file = None
+ self.bs_param.element_pattern = "M2101"
+ self.bs_param.minimum_array_gain = -200
+ self.bs_param.downtilt = 0
+
+ self.bs_param.element_max_g = 5
+ self.bs_param.element_phi_3db = 80
+ self.bs_param.element_theta_3db = 60
+ self.bs_param.element_am = 30
+ self.bs_param.element_sla_v = 30
+ self.bs_param.n_rows = 16
+ self.bs_param.n_columns = 16
+ self.bs_param.element_horiz_spacing = 1
+ self.bs_param.element_vert_spacing = 1
+ self.bs_param.multiplication_factor = 12
+
+ self.ue_param.element_pattern = "M2101"
+ self.ue_param.normalization = False
+ self.ue_param.normalization_file = None
+ self.ue_param.minimum_array_gain = -200
+
+ self.ue_param.element_max_g = 10
+ self.ue_param.element_phi_3db = 75
+ self.ue_param.element_theta_3db = 65
+ self.ue_param.element_am = 25
+ self.ue_param.element_sla_v = 35
+ self.ue_param.n_rows = 2
+ self.ue_param.n_columns = 2
+ self.ue_param.element_horiz_spacing = 0.5
+ self.ue_param.element_vert_spacing = 0.5
+ self.ue_param.multiplication_factor = 12
# Create antenna objects
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.antenna1 = AntennaBeamformingImt(par,300,-10)
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.antenna2 = AntennaBeamformingImt(par,-33.21,-5.31)
+ par = self.bs_param.get_antenna_parameters()
+ self.antenna1 = AntennaBeamformingImt(par, 300, -10)
+ par = self.ue_param.get_antenna_parameters()
+ self.antenna2 = AntennaBeamformingImt(par, -33.21, -5.31)
def test_azimuth(self):
- self.assertEqual(self.antenna1.azimuth,300)
- self.assertEqual(self.antenna2.azimuth,-33.21)
+ self.assertEqual(self.antenna1.azimuth, 300)
+ self.assertEqual(self.antenna2.azimuth, -33.21)
def test_elevation(self):
- self.assertEqual(self.antenna1.elevation,-10)
- self.assertEqual(self.antenna2.elevation,-5.31)
+ self.assertEqual(self.antenna1.elevation, -10)
+ self.assertEqual(self.antenna2.elevation, -5.31)
def test_g_max(self):
- self.assertEqual(self.antenna1.element.g_max,5)
- self.assertEqual(self.antenna2.element.g_max,10)
+ self.assertEqual(self.antenna1.element.g_max, 5)
+ self.assertEqual(self.antenna2.element.g_max, 10)
def test_phi_3db(self):
- self.assertEqual(self.antenna1.element.phi_3db,80)
- self.assertEqual(self.antenna2.element.phi_3db,75)
+ self.assertEqual(self.antenna1.element.phi_3db, 80)
+ self.assertEqual(self.antenna2.element.phi_3db, 75)
def test_theta_3db(self):
- self.assertEqual(self.antenna1.element.theta_3db,60)
- self.assertEqual(self.antenna2.element.theta_3db,65)
+ self.assertEqual(self.antenna1.element.theta_3db, 60)
+ self.assertEqual(self.antenna2.element.theta_3db, 65)
def test_am(self):
- self.assertEqual(self.antenna1.element.am,30)
- self.assertEqual(self.antenna2.element.am,25)
+ self.assertEqual(self.antenna1.element.am, 30)
+ self.assertEqual(self.antenna2.element.am, 25)
def test_sla_v(self):
- self.assertEqual(self.antenna1.element.sla_v,30)
- self.assertEqual(self.antenna2.element.sla_v,35)
+ self.assertEqual(self.antenna1.element.sla_v, 30)
+ self.assertEqual(self.antenna2.element.sla_v, 35)
def test_n_rows(self):
- self.assertEqual(self.antenna1.n_rows,16)
- self.assertEqual(self.antenna2.n_rows,2)
+ self.assertEqual(self.antenna1.n_rows, 16)
+ self.assertEqual(self.antenna2.n_rows, 2)
def test_n_cols(self):
- self.assertEqual(self.antenna1.n_cols,16)
- self.assertEqual(self.antenna2.n_cols,2)
+ self.assertEqual(self.antenna1.n_cols, 16)
+ self.assertEqual(self.antenna2.n_cols, 2)
def test_dh(self):
- self.assertEqual(self.antenna1.dh,1)
- self.assertEqual(self.antenna2.dh,0.5)
+ self.assertEqual(self.antenna1.dh, 1)
+ self.assertEqual(self.antenna2.dh, 0.5)
def test_dv(self):
- self.assertEqual(self.antenna1.dv,1)
- self.assertEqual(self.antenna2.dv,0.5)
+ self.assertEqual(self.antenna1.dv, 1)
+ self.assertEqual(self.antenna2.dv, 0.5)
def test_beams_list(self):
- self.assertEqual(len(self.antenna1.beams_list),0)
- self.assertEqual(len(self.antenna2.beams_list),0)
+ self.assertEqual(len(self.antenna1.beams_list), 0)
+ self.assertEqual(len(self.antenna2.beams_list), 0)
def test_w_vec_list(self):
- self.assertEqual(len(self.antenna1.w_vec_list),0)
- self.assertEqual(len(self.antenna2.w_vec_list),0)
+ self.assertEqual(len(self.antenna1.w_vec_list), 0)
+ self.assertEqual(len(self.antenna2.w_vec_list), 0)
def test_super_position_vector(self):
# Error margin
@@ -120,43 +123,79 @@ def test_super_position_vector(self):
phi = 0
theta = 0
v_vec = self.antenna2._super_position_vector(phi, theta)
- expected_v_vec = np.array([[1.0, 1.0],[-1.0, -1.0]])
- self.assertTrue(np.allclose(np.real(v_vec),\
- np.real(expected_v_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(v_vec),\
- np.imag(expected_v_vec),rtol = eps))
+ expected_v_vec = np.array([[1.0, 1.0], [-1.0, -1.0]])
+ self.assertTrue(
+ np.allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec), rtol=eps,
+ ),
+ )
# Test 2
phi = 90
theta = 90
v_vec = self.antenna2._super_position_vector(phi, theta)
- expected_v_vec = np.array([[1.0, -1.0],[1.0, -1.0]])
- self.assertTrue(np.allclose(np.real(v_vec),\
- np.real(expected_v_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(v_vec),\
- np.imag(expected_v_vec),rtol = eps))
+ expected_v_vec = np.array([[1.0, -1.0], [1.0, -1.0]])
+ self.assertTrue(
+ np.allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec), rtol=eps,
+ ),
+ )
# Test 3
phi = 45
theta = 45
v_vec = self.antenna2._super_position_vector(phi, theta)
- expected_v_vec = np.array([[1.0 + 0.0j, 0.0 + 1.0j],\
- [-0.6056998+0.7956932j, -0.7956932-0.6056998j]])
- self.assertTrue(np.allclose(np.real(v_vec),\
- np.real(expected_v_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(v_vec),\
- np.imag(expected_v_vec),rtol = eps))
+ expected_v_vec = np.array([
+ [1.0 + 0.0j, 0.0 + 1.0j],
+ [-0.6056998 + 0.7956932j, -0.7956932 - 0.6056998j],
+ ])
+ self.assertTrue(
+ np.allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec), rtol=eps,
+ ),
+ )
# Test 4
phi = 60
theta = 90
v_vec = self.antenna2._super_position_vector(phi, theta)
- expected_v_vec = np.array([[1.0 + 0.0j, -0.912724 + 0.408576j],\
- [1.0 + 0.0j, -0.912724 + 0.408576j]])
- self.assertTrue(np.allclose(np.real(v_vec),\
- np.real(expected_v_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(v_vec),\
- np.imag(expected_v_vec),rtol = eps))
+ expected_v_vec = np.array([
+ [1.0 + 0.0j, -0.912724 + 0.408576j],
+ [1.0 + 0.0j, -0.912724 + 0.408576j],
+ ])
+ self.assertTrue(
+ np.allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec), rtol=eps,
+ ),
+ )
def test_weight_vector(self):
# Error margin
@@ -166,111 +205,172 @@ def test_weight_vector(self):
phi_scan = 0
theta_tilt = 0
w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt)
- expected_w_vec = np.array([[0.5, 0.5],[0.5, 0.5]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([[0.5, 0.5], [0.5, 0.5]])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Test 2
phi_scan = 90
theta_tilt = 90
w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt)
- expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Test 3
phi_scan = 45
theta_tilt = 45
w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt)
- expected_w_vec = np.array([[0.5 + 0.0j, 0.0 - 0.5j],\
- [-0.3028499+0.3978466j, 0.3978466+0.3028499j]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([
+ [0.5 + 0.0j, 0.0 - 0.5j],
+ [-0.3028499 + 0.3978466j, 0.3978466 + 0.3028499j],
+ ])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Test 4
phi_scan = 0
theta_tilt = 90
w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt)
- expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Test 5
phi_scan = 45
theta_tilt = 30
w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt)
- expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j],\
- [0.0 + 0.5j, 0.469165 - 0.172870j]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
-
+ expected_w_vec = np.array([
+ [0.5 + 0.0j, -0.172870 - 0.469169j],
+ [0.0 + 0.5j, 0.469165 - 0.172870j],
+ ])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
def test_add_beam(self):
# Error margin and antenna object
eps = 1e-5
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.antenna2 = AntennaBeamformingImt(par,0,0)
+ par = self.ue_param.get_antenna_parameters()
+ self.antenna2 = AntennaBeamformingImt(par, 0, 0)
# Add first beam
phi_scan = 45
theta_tilt = 120
- self.antenna2.add_beam(phi_scan,theta_tilt)
+ self.antenna2.add_beam(phi_scan, theta_tilt)
- self.assertEqual(len(self.antenna2.beams_list),1)
- self.assertEqual(len(self.antenna2.w_vec_list),1)
+ self.assertEqual(len(self.antenna2.beams_list), 1)
+ self.assertEqual(len(self.antenna2.w_vec_list), 1)
# Add second beam
phi_scan = 90
theta_tilt = 180.0
- self.antenna2.add_beam(phi_scan,theta_tilt)
+ self.antenna2.add_beam(phi_scan, theta_tilt)
# Test beams_list
- self.assertEqual(len(self.antenna2.beams_list),2)
- self.assertEqual(len(self.antenna2.w_vec_list),2)
+ self.assertEqual(len(self.antenna2.beams_list), 2)
+ self.assertEqual(len(self.antenna2.w_vec_list), 2)
# Test first beam
- self.assertAlmostEqual(self.antenna2.beams_list[0][0],45,delta=eps)
- self.assertAlmostEqual(self.antenna2.beams_list[0][1],30,delta=eps)
+ self.assertAlmostEqual(self.antenna2.beams_list[0][0], 45, delta=eps)
+ self.assertAlmostEqual(self.antenna2.beams_list[0][1], 30, delta=eps)
w_vec = self.antenna2.w_vec_list[0]
- expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j],\
- [0.0 + 0.5j, 0.469165 - 0.172870j]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([
+ [0.5 + 0.0j, -0.172870 - 0.469169j],
+ [0.0 + 0.5j, 0.469165 - 0.172870j],
+ ])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Test second beam
- self.assertEqual(self.antenna2.beams_list[1][0],90)
- self.assertEqual(self.antenna2.beams_list[1][1],90)
+ self.assertEqual(self.antenna2.beams_list[1][0], 90)
+ self.assertEqual(self.antenna2.beams_list[1][1], 90)
w_vec = self.antenna2.w_vec_list[1]
- expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]])
- self.assertTrue(np.allclose(np.real(w_vec),\
- np.real(expected_w_vec),rtol = eps))
- self.assertTrue(np.allclose(np.imag(w_vec),\
- np.imag(expected_w_vec),rtol = eps))
+ expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]])
+ self.assertTrue(
+ np.allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec), rtol=eps,
+ ),
+ )
+ self.assertTrue(
+ np.allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec), rtol=eps,
+ ),
+ )
# Reset beams and test
self.antenna2.reset_beams()
- self.assertEqual(len(self.antenna2.beams_list),0)
- self.assertEqual(len(self.antenna2.w_vec_list),0)
+ self.assertEqual(len(self.antenna2.beams_list), 0)
+ self.assertEqual(len(self.antenna2.w_vec_list), 0)
def test_beam_gain(self):
# Error margin and antenna
eps = 1e-4
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.antenna2 = AntennaBeamformingImt(par,0,0)
+ par = self.ue_param.get_antenna_parameters()
+ self.antenna2 = AntennaBeamformingImt(par, 0, 0)
# Test 1
phi = 45
@@ -278,9 +378,9 @@ def test_beam_gain(self):
beam = 0
phi_scan = 45
theta_tilt = 135
- self.antenna2.add_beam(phi_scan,theta_tilt)
- beam_g = self.antenna2._beam_gain(phi,theta,beam)
- self.assertAlmostEqual(beam_g,1.594268,delta = eps)
+ self.antenna2.add_beam(phi_scan, theta_tilt)
+ beam_g = self.antenna2._beam_gain(phi, theta, beam)
+ self.assertAlmostEqual(beam_g, 1.594268, delta=eps)
# Test 2
phi = 0
@@ -288,29 +388,31 @@ def test_beam_gain(self):
beam = 1
phi_scan = 45
theta_tilt = 180
- self.antenna2.add_beam(phi_scan,theta_tilt)
- beam_g = self.antenna2._beam_gain(phi,theta,beam)
- self.assertAlmostEqual(beam_g,10.454087,delta = eps)
+ self.antenna2.add_beam(phi_scan, theta_tilt)
+ beam_g = self.antenna2._beam_gain(phi, theta, beam)
+ self.assertAlmostEqual(beam_g, 10.454087, delta=eps)
# Test 3
phi = 32.5
theta = 115.2
- beam_g = self.antenna2._beam_gain(phi,theta)
- self.assertAlmostEqual(beam_g,11.9636,delta = eps)
+ beam_g = self.antenna2._beam_gain(phi, theta)
+ self.assertAlmostEqual(beam_g, 11.9636, delta=eps)
def test_calculate_gain(self):
# Error margin and antenna
eps = 1e-4
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.antenna1 = AntennaBeamformingImt(par,0,0)
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.antenna2 = AntennaBeamformingImt(par,0,0)
+ par = self.bs_param.get_antenna_parameters()
+ self.antenna1 = AntennaBeamformingImt(par, 0, 0)
+ par = self.ue_param.get_antenna_parameters()
+ self.antenna2 = AntennaBeamformingImt(par, 0, 0)
# Test 1
phi_vec = np.array([45.0, 32.5])
theta_vec = np.array([45.0, 115.2])
- gains = self.antenna2.calculate_gain(phi_vec=phi_vec, theta_vec=theta_vec)
- npt.assert_allclose(gains,np.array([5.9491,11.9636]),atol=eps)
+ gains = self.antenna2.calculate_gain(
+ phi_vec=phi_vec, theta_vec=theta_vec,
+ )
+ npt.assert_allclose(gains, np.array([5.9491, 11.9636]), atol=eps)
# Test 2
phi = 0.0
@@ -319,30 +421,36 @@ def test_calculate_gain(self):
theta_tilt = 180
self.antenna2.add_beam(phi_scan, theta_tilt)
beams_l = np.zeros_like(phi_vec, dtype=int)
- gains = self.antenna2.calculate_gain(phi_vec=phi, theta_vec=theta,
- beams_l=beams_l)
- npt.assert_allclose(gains,np.array([10.454087]),atol=eps)
+ gains = self.antenna2.calculate_gain(
+ phi_vec=phi, theta_vec=theta,
+ beams_l=beams_l,
+ )
+ npt.assert_allclose(gains, np.array([10.454087]), atol=eps)
# Test 3
phi = 40
theta = 100
- gains = self.antenna1.calculate_gain(phi_vec=phi, theta_vec=theta,
- co_channel=False)
- npt.assert_allclose(gains,np.array([1.6667]),atol=eps)
-
+ gains = self.antenna1.calculate_gain(
+ phi_vec=phi, theta_vec=theta,
+ co_channel=False,
+ )
+ npt.assert_allclose(gains, np.array([1.6667]), atol=eps)
+
def test_normalization(self):
# Create dummy normalization data
adjacent_antenna_model = "SINGLE_ELEMENT"
normalization = True
- norm_data = {'norm_file': 'dummy_file.npz',
- 'resolution': 1,
- 'phi_range': (-180,+180),
- 'theta_range': (0,180),
- 'correction_factor_co_channel': np.ones((360,180)),
- 'error_co_channel': 0.0,
- 'correction_factor_adj_channel': 5,
- 'error_adj_channel': 0.0,
- 'parameters': None}
+ norm_data = {
+ 'norm_file': 'dummy_file.npz',
+ 'resolution': 1,
+ 'phi_range': (-180, +180),
+ 'theta_range': (0, 180),
+ 'correction_factor_co_channel': np.ones((360, 180)),
+ 'error_co_channel': 0.0,
+ 'correction_factor_adj_channel': 5,
+ 'error_adj_channel': 0.0,
+ 'parameters': None,
+ }
element_pattern = "M2101"
element_max_g = 5
element_phi_3db = 65
@@ -356,163 +464,174 @@ def test_normalization(self):
minimum_array_gain = -200
multiplication_factor = 12
downtilt = 0
- par = AntennaPar(adjacent_antenna_model,
- normalization,
- norm_data,
- element_pattern,
- element_max_g,
- element_phi_3db,
- element_theta_3db,
- element_am,
- element_sla_v,
- n_rows,
- n_columns,
- horiz_spacing,
- vert_spacing,
- multiplication_factor,
- minimum_array_gain,
- downtilt)
-
-
+ par = AntennaPar(
+ adjacent_antenna_model,
+ normalization,
+ norm_data,
+ element_pattern,
+ element_max_g,
+ element_phi_3db,
+ element_theta_3db,
+ element_am,
+ element_sla_v,
+ n_rows,
+ n_columns,
+ horiz_spacing,
+ vert_spacing,
+ multiplication_factor,
+ minimum_array_gain,
+ downtilt,
+ )
+
# Create antenna objects
- self.antenna3 = AntennaBeamformingImt(par,0.0,0.0) # Normalized
- par = par._replace(normalization = False)
- self.antenna4 = AntennaBeamformingImt(par,0.0,0.0) # Unormalized
-
+ self.antenna3 = AntennaBeamformingImt(par, 0.0, 0.0) # Normalized
+ par = par._replace(normalization=False)
+ self.antenna4 = AntennaBeamformingImt(par, 0.0, 0.0) # Unormalized
+
# Test co-channel gain: no beam
phi_v = np.array([11.79, -0.71])
theta_v = np.array([50.31, 120.51])
- gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v)
+ gain_ref = self.antenna4.calculate_gain(
+ phi_vec=phi_v, theta_vec=theta_v,
+ )
gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v)
- npt.assert_equal(gain,gain_ref + 1)
-
+ npt.assert_equal(gain, gain_ref + 1)
+
# Test co-channel gain: add beam
phi_scan = 11.79
theta_tilt = 185.31
self.antenna3.add_beam(phi_scan, theta_tilt)
self.antenna4.add_beam(phi_scan, theta_tilt)
beams_l = np.zeros_like(phi_v, dtype=int)
- gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v,
- beams_l=beams_l)
- gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v,
- beams_l=beams_l)
- npt.assert_equal(gain,gain_ref + 1)
-
+ gain_ref = self.antenna4.calculate_gain(
+ phi_vec=phi_v, theta_vec=theta_v,
+ beams_l=beams_l,
+ )
+ gain = self.antenna3.calculate_gain(
+ phi_vec=phi_v, theta_vec=theta_v,
+ beams_l=beams_l,
+ )
+ npt.assert_equal(gain, gain_ref + 1)
+
# Test adjacent channel
phi_v = np.array([11.79, -0.71])
theta_v = np.array([50.31, 120.51])
- gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, co_channel=False)
- gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, co_channel=False)
- npt.assert_equal(gain,gain_ref + 5)
+ gain_ref = self.antenna4.calculate_gain(
+ phi_vec=phi_v, theta_vec=theta_v, co_channel=False,
+ )
+ gain = self.antenna3.calculate_gain(
+ phi_vec=phi_v, theta_vec=theta_v, co_channel=False,
+ )
+ npt.assert_equal(gain, gain_ref + 5)
def test_to_local_coord(self):
# Test 1
# Create antenna object
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
azi = 0.0
ele = 90.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted: z, x and y axis
- phi = np.array([ 0, 90, 0])
+ phi = np.array([0, 90, 0])
theta = np.array([90, 90, 0])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
- exp_lo_phi = np.array([0,90,0])
- exp_lo_theta = np.array([180,90,90])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
+ exp_lo_phi = np.array([0, 90, 0])
+ exp_lo_theta = np.array([180, 90, 90])
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
# Test 2
# Create antenna object
azi = -15.0
ele = 90.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted
- phi = np.array([ 0, 0, 90, 10])
+ phi = np.array([0, 0, 90, 10])
theta = np.array([0, 90, 90, 90])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
exp_lo_phi = np.array([0, 90, 90, 90])
exp_lo_theta = np.array([90, 165, 75, 155])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
# Test 3
# Create antenna object
azi = +15.0
ele = 90.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted: z, x and y axis
- phi = np.array([ 0, 0, 90])
+ phi = np.array([0, 0, 90])
theta = np.array([0, 90, 90])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
- exp_lo_phi = np.array([0,-90, 90])
+ exp_lo_phi = np.array([0, -90, 90])
exp_lo_theta = np.array([90, 165, 105])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
# Test 4
# Create antenna object
azi = 0.0
ele = 15.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted: z, x and y axis
- phi = np.array([ 0, 0, 90])
+ phi = np.array([0, 0, 90])
theta = np.array([0, 90, 90])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
exp_lo_phi = np.array([0, 0, 90])
exp_lo_theta = np.array([15, 105, 90])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
# Test 5
# Create antenna object
azi = 0.0
ele = -15.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted: z, x and y axis
- phi = np.array([ 0, 0, 90])
+ phi = np.array([0, 0, 90])
theta = np.array([0, 90, 90])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
exp_lo_phi = np.array([180, 0, 90])
exp_lo_theta = np.array([15, 75, 90])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
# Test 6
# Create antenna object
azi = 180.0
ele = 45.0
- self.antenna3 = AntennaBeamformingImt(par,azi,ele)
-
+ self.antenna3 = AntennaBeamformingImt(par, azi, ele)
+
# Angles to be converted: z, x and y axis
- phi = np.array([ 0, 0, 90])
+ phi = np.array([0, 0, 90])
theta = np.array([0, 90, 90])
# Convert to local coordinates
lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta)
exp_lo_phi = np.array([0, -180, -90])
exp_lo_theta = np.array([45, 45, 90])
- npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2)
- npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2)
-
-
+ npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2)
+ npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2)
+
+
if __name__ == '__main__':
unittest.main()
-#
+#
# suite = unittest.TestSuite()
# suite.addTest(AntennaBeamformingImtTest('test_calculate_gain'))
# unittest.TextTestRunner().run(suite)
diff --git a/tests/test_antenna_beamforming_imt_f1336.py b/tests/test_antenna_beamforming_imt_f1336.py
index 5de5cc399..40bec322b 100644
--- a/tests/test_antenna_beamforming_imt_f1336.py
+++ b/tests/test_antenna_beamforming_imt_f1336.py
@@ -10,181 +10,208 @@
import numpy.testing as npt
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.support.named_tuples import AntennaPar
from sharc.support.enumerations import StationType
+
class AntennaBeamformingImtF1336Test(unittest.TestCase):
def setUp(self):
- #Array parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_element_pattern = "F1336"
- self.param.bs_minimum_array_gain = -200
- self.param.bs_normalization = False
- self.param.bs_downtilt = 0
-
- self.param.bs_normalization_file = None
- self.param.bs_element_max_g = 18
- self.param.bs_element_phi_3db = 65
- self.param.bs_element_theta_3db = 0
- self.param.bs_n_rows = 1
- self.param.bs_n_columns = 1
-
- self.param.bs_element_am = 30
- self.param.bs_element_sla_v = 30
- self.param.bs_element_horiz_spacing = 0.5
- self.param.bs_element_vert_spacing = 0.5
- self.param.bs_multiplication_factor = 12
+ # Array parameters
+ self.bs_param = ParametersAntennaImt()
+
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.element_pattern = "F1336"
+ self.bs_param.minimum_array_gain = -200
+ self.bs_param.normalization = False
+ self.bs_param.downtilt = 0
+
+ self.bs_param.normalization_file = None
+ self.bs_param.element_max_g = 18
+ self.bs_param.element_phi_3db = 65
+ self.bs_param.element_theta_3db = 0
+ self.bs_param.n_rows = 1
+ self.bs_param.n_columns = 1
+
+ self.bs_param.element_am = 30
+ self.bs_param.element_sla_v = 30
+ self.bs_param.element_horiz_spacing = 0.5
+ self.bs_param.element_vert_spacing = 0.5
+ self.bs_param.multiplication_factor = 12
# Create antenna objects
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
self.antenna1 = AntennaBeamformingImt(par, 300, -10)
-
def test_azimuth(self):
self.assertEqual(self.antenna1.azimuth, 300)
-
def test_elevation(self):
self.assertEqual(self.antenna1.elevation, -10)
-
def test_g_max(self):
self.assertEqual(self.antenna1.element.g_max, 18)
-
def test_phi_3db(self):
self.assertEqual(self.antenna1.element.phi_3db, 65)
-
def test_theta_3db(self):
- self.assertAlmostEqual(self.antenna1.element.theta_3db, 7.55, delta = 1e-2)
-
+ self.assertAlmostEqual(
+ self.antenna1.element.theta_3db, 7.55, delta=1e-2,
+ )
def test_n_rows(self):
self.assertEqual(self.antenna1.n_rows, 1)
-
def test_n_cols(self):
self.assertEqual(self.antenna1.n_cols, 1)
-
def test_beams_list(self):
self.assertEqual(len(self.antenna1.beams_list), 0)
-
def test_w_vec_list(self):
self.assertEqual(len(self.antenna1.w_vec_list), 0)
-
def test_super_position_vector(self):
phi = 0
theta = 0
v_vec = self.antenna1._super_position_vector(phi, theta)
expected_v_vec = np.array([[1]])
- npt.assert_allclose(np.real(v_vec),
- np.real(expected_v_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(v_vec),
- np.imag(expected_v_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec),
+ atol=1e-2,
+ )
phi = 90
theta = 90
v_vec = self.antenna1._super_position_vector(phi, theta)
expected_v_vec = np.array([[1]])
- npt.assert_allclose(np.real(v_vec),
- np.real(expected_v_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(v_vec),
- np.imag(expected_v_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec),
+ atol=1e-2,
+ )
phi = 45
theta = 45
v_vec = self.antenna1._super_position_vector(phi, theta)
expected_v_vec = np.array([[1]])
- npt.assert_allclose(np.real(v_vec),
- np.real(expected_v_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(v_vec),
- np.imag(expected_v_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec),
+ atol=1e-2,
+ )
phi = 60
theta = 90
v_vec = self.antenna1._super_position_vector(phi, theta)
expected_v_vec = np.array([[1]])
- npt.assert_allclose(np.real(v_vec),
- np.real(expected_v_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(v_vec),
- np.imag(expected_v_vec),
- atol = 1e-2)
-
+ npt.assert_allclose(
+ np.real(v_vec),
+ np.real(expected_v_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(v_vec),
+ np.imag(expected_v_vec),
+ atol=1e-2,
+ )
def test_weight_vector(self):
phi_scan = 0
theta_tilt = 0
w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt)
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
phi_scan = 90
theta_tilt = 90
w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt)
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
phi_scan = 45
theta_tilt = 45
w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt)
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
phi_scan = 0
theta_tilt = 90
w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt)
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
phi_scan = 45
theta_tilt = 30
w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt)
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
-
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
def test_add_beam(self):
eps = 1e-5
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
self.antenna1 = AntennaBeamformingImt(par, 0, 0)
# Add first beam
@@ -205,17 +232,21 @@ def test_add_beam(self):
self.assertEqual(len(self.antenna1.w_vec_list), 2)
# Test first beam
- self.assertAlmostEqual(self.antenna1.beams_list[0][0], 45, delta = eps)
- self.assertAlmostEqual(self.antenna1.beams_list[0][1], 30, delta = eps)
+ self.assertAlmostEqual(self.antenna1.beams_list[0][0], 45, delta=eps)
+ self.assertAlmostEqual(self.antenna1.beams_list[0][1], 30, delta=eps)
w_vec = self.antenna1.w_vec_list[0]
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
# Test second beam
self.assertEqual(self.antenna1.beams_list[1][0], 90)
@@ -223,12 +254,16 @@ def test_add_beam(self):
w_vec = self.antenna1.w_vec_list[1]
expected_w_vec = np.array([[1]])
- npt.assert_allclose(np.real(w_vec),
- np.real(expected_w_vec),
- atol = 1e-2)
- npt.assert_allclose(np.imag(w_vec),
- np.imag(expected_w_vec),
- atol = 1e-2)
+ npt.assert_allclose(
+ np.real(w_vec),
+ np.real(expected_w_vec),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ np.imag(w_vec),
+ np.imag(expected_w_vec),
+ atol=1e-2,
+ )
# Reset beams and test
self.antenna1.reset_beams()
@@ -238,7 +273,7 @@ def test_add_beam(self):
def test_beam_gain(self):
# Error margin and antenna
eps = 1e-2
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
self.antenna1 = AntennaBeamformingImt(par, 0, 0)
# Test 1
@@ -249,7 +284,7 @@ def test_beam_gain(self):
theta_tilt = 135
self.antenna1.add_beam(phi_scan, theta_tilt)
beam_g = self.antenna1._beam_gain(phi, theta, beam)
- self.assertAlmostEqual(beam_g, 18, delta = eps)
+ self.assertAlmostEqual(beam_g, 18, delta=eps)
# Test 2
phi = 30
@@ -259,26 +294,28 @@ def test_beam_gain(self):
theta_tilt = 180
self.antenna1.add_beam(phi_scan, theta_tilt)
beam_g = self.antenna1._beam_gain(phi, theta, beam)
- self.assertAlmostEqual(beam_g, -1.48, delta = eps)
+ self.assertAlmostEqual(beam_g, -1.48, delta=eps)
# Test 3
phi = 150
theta = 180
beam_g = self.antenna1._beam_gain(phi, theta)
- self.assertAlmostEqual(beam_g, -6.45, delta = eps)
+ self.assertAlmostEqual(beam_g, -6.45, delta=eps)
def test_calculate_gain(self):
# Error margin and antenna
eps = 1e-2
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.antenna1 = AntennaBeamformingImt(par,0,0)
+ par = self.bs_param.get_antenna_parameters()
+ self.antenna1 = AntennaBeamformingImt(par, 0, 0)
# Test 1
phi_vec = np.array([0, 30])
theta_vec = np.array([90, 100])
- gains = self.antenna1.calculate_gain(phi_vec = phi_vec,
- theta_vec = theta_vec)
- npt.assert_allclose(gains,np.array([18, 4.52]), atol=eps)
+ gains = self.antenna1.calculate_gain(
+ phi_vec=phi_vec,
+ theta_vec=theta_vec,
+ )
+ npt.assert_allclose(gains, np.array([18, 4.52]), atol=eps)
# Test 2
phi = 0
@@ -287,19 +324,23 @@ def test_calculate_gain(self):
theta_tilt = 180
self.antenna1.add_beam(phi_scan, theta_tilt)
beams_l = np.zeros_like(phi_vec, dtype=int)
- gains = self.antenna1.calculate_gain(phi_vec = phi,
- theta_vec = theta,
- beams_l = beams_l)
+ gains = self.antenna1.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ beams_l=beams_l,
+ )
npt.assert_allclose(gains, np.array([12.74]), atol=eps)
# Test 3
phi = 5
theta = 98
- gains = self.antenna1.calculate_gain(phi_vec = phi,
- theta_vec = theta,
- co_channel = False)
- npt.assert_allclose(gains, np.array([6.81]), atol = eps)
-
-
+ gains = self.antenna1.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ co_channel=False,
+ )
+ npt.assert_allclose(gains, np.array([6.81]), atol=eps)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_element_imt.py b/tests/test_antenna_element_imt.py
index 92aa90357..e05df5482 100644
--- a/tests/test_antenna_element_imt.py
+++ b/tests/test_antenna_element_imt.py
@@ -9,142 +9,147 @@
import unittest
from sharc.antenna.antenna_element_imt_m2101 import AntennaElementImtM2101
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.support.enumerations import StationType
+
class AntennaImtTest(unittest.TestCase):
def setUp(self):
- #Element parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_element_pattern = "M2101"
- self.param.ue_element_pattern = "M2101"
- self.param.bs_minimum_array_gain = -200
- self.param.ue_minimum_array_gain = -200
- self.param.bs_normalization = False
- self.param.bs_downtilt = 0
-
- self.param.bs_normalization_file = None
- self.param.bs_element_max_g = 5
- self.param.bs_element_phi_3db = 80
- self.param.bs_element_theta_3db = 60
- self.param.bs_element_am = 30
- self.param.bs_element_sla_v = 30
- self.param.bs_n_rows = 8
- self.param.bs_n_columns = 8
- self.param.bs_element_horiz_spacing = 0.5
- self.param.bs_element_vert_spacing = 0.5
- self.param.bs_multiplication_factor = 12
-
- self.param.ue_normalization_file = None
- self.param.ue_normalization = False
- self.param.ue_element_max_g = 10
- self.param.ue_element_phi_3db = 75
- self.param.ue_element_theta_3db = 65
- self.param.ue_element_am = 25
- self.param.ue_element_sla_v = 35
- self.param.ue_n_rows = 4
- self.param.ue_n_columns = 4
- self.param.ue_element_horiz_spacing = 0.5
- self.param.ue_element_vert_spacing = 0.5
- self.param.ue_multiplication_factor = 12
+ # Element parameters
+ self.ue_param = ParametersAntennaImt()
+ self.bs_param = ParametersAntennaImt()
+
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.element_pattern = "M2101"
+ self.ue_param.element_pattern = "M2101"
+ self.bs_param.minimum_array_gain = -200
+ self.ue_param.minimum_array_gain = -200
+ self.bs_param.normalization = False
+ self.bs_param.downtilt = 0
+
+ self.bs_param.normalization_file = None
+ self.bs_param.element_max_g = 5
+ self.bs_param.element_phi_3db = 80
+ self.bs_param.element_theta_3db = 60
+ self.bs_param.element_am = 30
+ self.bs_param.element_sla_v = 30
+ self.bs_param.n_rows = 8
+ self.bs_param.n_columns = 8
+ self.bs_param.element_horiz_spacing = 0.5
+ self.bs_param.element_vert_spacing = 0.5
+ self.bs_param.multiplication_factor = 12
+
+ self.ue_param.normalization_file = None
+ self.ue_param.normalization = False
+ self.ue_param.element_max_g = 10
+ self.ue_param.element_phi_3db = 75
+ self.ue_param.element_theta_3db = 65
+ self.ue_param.element_am = 25
+ self.ue_param.element_sla_v = 35
+ self.ue_param.n_rows = 4
+ self.ue_param.n_columns = 4
+ self.ue_param.element_horiz_spacing = 0.5
+ self.ue_param.element_vert_spacing = 0.5
+ self.ue_param.multiplication_factor = 12
# Create antenna IMT objects
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
self.antenna1 = AntennaElementImtM2101(par)
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
+ par = self.ue_param.get_antenna_parameters()
self.antenna2 = AntennaElementImtM2101(par)
def test_g_max(self):
- self.assertEqual(self.antenna1.g_max,5)
- self.assertEqual(self.antenna2.g_max,10)
+ self.assertEqual(self.antenna1.g_max, 5)
+ self.assertEqual(self.antenna2.g_max, 10)
def test_phi_3db(self):
- self.assertEqual(self.antenna1.phi_3db,80)
- self.assertEqual(self.antenna2.phi_3db,75)
+ self.assertEqual(self.antenna1.phi_3db, 80)
+ self.assertEqual(self.antenna2.phi_3db, 75)
def test_theta_3db(self):
- self.assertEqual(self.antenna1.theta_3db,60)
- self.assertEqual(self.antenna2.theta_3db,65)
+ self.assertEqual(self.antenna1.theta_3db, 60)
+ self.assertEqual(self.antenna2.theta_3db, 65)
def test_am(self):
- self.assertEqual(self.antenna1.am,30)
- self.assertEqual(self.antenna2.am,25)
+ self.assertEqual(self.antenna1.am, 30)
+ self.assertEqual(self.antenna2.am, 25)
def test_sla_v(self):
- self.assertEqual(self.antenna1.sla_v,30)
- self.assertEqual(self.antenna2.sla_v,35)
+ self.assertEqual(self.antenna1.sla_v, 30)
+ self.assertEqual(self.antenna2.sla_v, 35)
def test_horizontal_pattern(self):
# phi = 0 results in zero gain
phi = 0
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertEqual(h_att,0.0)
+ self.assertEqual(h_att, 0.0)
# phi = 120 implies horizontal gain of of -27 dB
phi = 120
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertEqual(h_att,-27.0)
+ self.assertEqual(h_att, -27.0)
# phi = 150, horizontal attenuation equals to the front-to-back ratio
phi = 150
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertEqual(h_att,-30)
- self.assertEqual(h_att,-1.0*self.antenna1.am)
+ self.assertEqual(h_att, -30)
+ self.assertEqual(h_att, -1.0 * self.antenna1.am)
# Test vector
phi = np.array([0, 120, 150])
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertTrue(np.all(h_att == np.array([0.0,-27.0,-30.0])))
+ self.assertTrue(np.all(h_att == np.array([0.0, -27.0, -30.0])))
def test_vertical_pattern(self):
# theta = 90 results in zero gain
theta = 90
v_att = self.antenna1.vertical_pattern(theta)
- self.assertEqual(v_att,0.0)
+ self.assertEqual(v_att, 0.0)
# theta = 180 implies vertical gain of -27 dB
theta = 180
v_att = self.antenna1.vertical_pattern(theta)
- self.assertEqual(v_att,-27.0)
+ self.assertEqual(v_att, -27.0)
- # theta = 210, vertical attenuation equals vertical sidelobe attenuation
+ # theta = 210, vertical attenuation equals vertical sidelobe
+ # attenuation
theta = 210
v_att = self.antenna1.vertical_pattern(theta)
- self.assertEqual(v_att,-30)
- self.assertEqual(v_att,-1.0*self.antenna1.sla_v)
+ self.assertEqual(v_att, -30)
+ self.assertEqual(v_att, -1.0 * self.antenna1.sla_v)
# Test vector
theta = np.array([90, 180, 210])
v_att = self.antenna1.vertical_pattern(theta)
- self.assertTrue(np.all(v_att == np.array([0.0,-27.0,-30.0])))
+ self.assertTrue(np.all(v_att == np.array([0.0, -27.0, -30.0])))
def test_element_pattern(self):
# theta = 0 and phi = 90 result in maximum gain
phi = 0
theta = 90
- e_gain = self.antenna1.element_pattern(phi,theta)
- self.assertEqual(e_gain,5.0)
- self.assertEqual(e_gain,self.antenna1.g_max)
+ e_gain = self.antenna1.element_pattern(phi, theta)
+ self.assertEqual(e_gain, 5.0)
+ self.assertEqual(e_gain, self.antenna1.g_max)
phi = 80
theta = 150
- e_gain = self.antenna1.element_pattern(phi,theta)
- self.assertEqual(e_gain,-19.0)
+ e_gain = self.antenna1.element_pattern(phi, theta)
+ self.assertEqual(e_gain, -19.0)
phi = 150
theta = 210
- e_gain = self.antenna1.element_pattern(phi,theta)
- self.assertEqual(e_gain,-25.0)
- self.assertEqual(e_gain,self.antenna1.g_max - self.antenna1.am)
+ e_gain = self.antenna1.element_pattern(phi, theta)
+ self.assertEqual(e_gain, -25.0)
+ self.assertEqual(e_gain, self.antenna1.g_max - self.antenna1.am)
# Test vector
- phi = np.array([0,80,150])
- theta = np.array([90,150,210])
- e_gain = self.antenna1.element_pattern(phi,theta)
- self.assertTrue(np.all(e_gain == np.array([5.0,-19.0,-25.0])))
+ phi = np.array([0, 80, 150])
+ theta = np.array([90, 150, 210])
+ e_gain = self.antenna1.element_pattern(phi, theta)
+ self.assertTrue(np.all(e_gain == np.array([5.0, -19.0, -25.0])))
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_element_imt_f1336.py b/tests/test_antenna_element_imt_f1336.py
index e27b2beb1..49cda9954 100644
--- a/tests/test_antenna_element_imt_f1336.py
+++ b/tests/test_antenna_element_imt_f1336.py
@@ -10,36 +10,37 @@
import numpy.testing as npt
from sharc.antenna.antenna_element_imt_f1336 import AntennaElementImtF1336
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.support.enumerations import StationType
+
class AntennaElementImtF1336Test(unittest.TestCase):
def setUp(self):
- #Element parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_element_pattern = "F1336"
- self.param.bs_minimum_array_gain = -200
- self.param.bs_normalization = False
- self.param.bs_downtilt = 0
-
- self.param.bs_normalization_file = None
- self.param.bs_element_max_g = 18
- self.param.bs_element_phi_3db = 65
- self.param.bs_element_theta_3db = 0
- self.param.bs_n_rows = 1
- self.param.bs_n_columns = 1
-
- self.param.bs_element_am = 30
- self.param.bs_element_sla_v = 30
- self.param.bs_element_horiz_spacing = 0.5
- self.param.bs_element_vert_spacing = 0.5
- self.param.bs_multiplication_factor = 12
+ # Element parameters
+ self.bs_param = ParametersAntennaImt()
+
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.element_pattern = "F1336"
+ self.bs_param.minimum_array_gain = -200
+ self.bs_param.normalization = False
+ self.bs_param.downtilt = 0
+
+ self.bs_param.normalization_file = None
+ self.bs_param.element_max_g = 18
+ self.bs_param.element_phi_3db = 65
+ self.bs_param.element_theta_3db = 0
+ self.bs_param.n_rows = 1
+ self.bs_param.n_columns = 1
+
+ self.bs_param.element_am = 30
+ self.bs_param.element_sla_v = 30
+ self.bs_param.element_horiz_spacing = 0.5
+ self.bs_param.element_vert_spacing = 0.5
+ self.bs_param.multiplication_factor = 12
# Create antenna IMT objects
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
+ par = self.bs_param.get_antenna_parameters()
self.antenna1 = AntennaElementImtF1336(par)
def test_g_max(self):
@@ -49,58 +50,55 @@ def test_phi_3db(self):
self.assertEqual(self.antenna1.phi_3db, 65)
def test_theta_3db(self):
- self.assertAlmostEqual(self.antenna1.theta_3db, 7.55, delta = 1e-2)
+ self.assertAlmostEqual(self.antenna1.theta_3db, 7.55, delta=1e-2)
def test_antenna_parameters(self):
- self.assertAlmostEqual(self.antenna1.k_a, 0.7, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.k_p, 0.7, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.k_h, 0.7, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.lambda_k_h, -1.87, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.k_v, 0.3, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.incline_factor, 18.45, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.x_k, 0.94, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.lambda_k_v, 4.60, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.g_hr_180, -24.45, delta = 1e-2)
- self.assertAlmostEqual(self.antenna1.g_hr_0, 0, delta = 1e-2)
-
-
+ self.assertAlmostEqual(self.antenna1.k_a, 0.7, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.k_p, 0.7, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.k_h, 0.7, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.lambda_k_h, -1.87, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.k_v, 0.3, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.incline_factor, 18.45, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.x_k, 0.94, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.lambda_k_v, 4.60, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.g_hr_180, -24.45, delta=1e-2)
+ self.assertAlmostEqual(self.antenna1.g_hr_0, 0, delta=1e-2)
+
def test_horizontal_pattern(self):
phi = 0
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertAlmostEqual(h_att, 0, delta = 1e-2)
+ self.assertAlmostEqual(h_att, 0, delta=1e-2)
phi = 30
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertAlmostEqual(h_att, -2.55, delta = 1e-2)
+ self.assertAlmostEqual(h_att, -2.55, delta=1e-2)
phi = 150
h_att = self.antenna1.horizontal_pattern(phi)
- self.assertAlmostEqual(h_att, -24.45, delta = 1e-2)
+ self.assertAlmostEqual(h_att, -24.45, delta=1e-2)
# Test vector
phi = np.array([0, 30, 150])
h_att = self.antenna1.horizontal_pattern(phi)
- npt.assert_allclose(h_att, np.array([0, -2.55, -24.45]), atol = 1e-2)
-
+ npt.assert_allclose(h_att, np.array([0, -2.55, -24.45]), atol=1e-2)
def test_vertical_pattern(self):
theta = 90
v_att = self.antenna1.vertical_pattern(theta)
- self.assertAlmostEqual(v_att, 0, delta = 1e-2)
+ self.assertAlmostEqual(v_att, 0, delta=1e-2)
theta = 135
v_att = self.antenna1.vertical_pattern(theta)
- self.assertAlmostEqual(v_att, -18.90, delta = 1e-2)
+ self.assertAlmostEqual(v_att, -18.90, delta=1e-2)
theta = 180
v_att = self.antenna1.vertical_pattern(theta)
- self.assertAlmostEqual(v_att, -24.45, delta = 1e-2)
+ self.assertAlmostEqual(v_att, -24.45, delta=1e-2)
# Test vector
theta = np.array([90, 135, 180])
v_att = self.antenna1.vertical_pattern(theta)
- npt.assert_allclose(v_att, np.array([0, -18.90, -24.45]), atol = 1e-2)
-
+ npt.assert_allclose(v_att, np.array([0, -18.90, -24.45]), atol=1e-2)
def test_element_pattern(self):
phi = 0
@@ -112,18 +110,19 @@ def test_element_pattern(self):
phi = 30
theta = 135
e_gain = self.antenna1.element_pattern(phi, theta)
- self.assertAlmostEqual(e_gain, -1.48, delta = 1e-2)
+ self.assertAlmostEqual(e_gain, -1.48, delta=1e-2)
phi = 150
theta = 180
e_gain = self.antenna1.element_pattern(phi, theta)
- self.assertAlmostEqual(e_gain, -6.45, delta = 1e-2)
+ self.assertAlmostEqual(e_gain, -6.45, delta=1e-2)
# Test vector
phi = np.array([0, 30, 150])
theta = np.array([90, 135, 180])
e_gain = self.antenna1.element_pattern(phi, theta)
- npt.assert_allclose(e_gain, np.array([18, -1.48, -6.45]), atol = 1e-2)
+ npt.assert_allclose(e_gain, np.array([18, -1.48, -6.45]), atol=1e-2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_fss_ss.py b/tests/test_antenna_fss_ss.py
index 4b4976e40..c19efc562 100644
--- a/tests/test_antenna_fss_ss.py
+++ b/tests/test_antenna_fss_ss.py
@@ -13,6 +13,7 @@
import numpy as np
import numpy.testing as npt
+
class AntennaFssSsTest(unittest.TestCase):
def setUp(self):
@@ -27,16 +28,23 @@ def setUp(self):
param.antenna_l_s = -30
self.antenna30 = AntennaFssSs(param)
-
def test_calculate_gain(self):
psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100])
- ref_gain25 = np.array([0, -3, -12, -28, -28, -28, -28, -29.12, -30.57, -31.85, -33, -53])
- gain25 = self.antenna25.calculate_gain(off_axis_angle_vec=psi) - self.antenna25.peak_gain
+ ref_gain25 = np.array(
+ [0, -3, -12, -28, -28, -28, -28, -29.12, -30.57, -31.85, -33, -53],
+ )
+ gain25 = self.antenna25.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna25.peak_gain
npt.assert_allclose(gain25, ref_gain25, atol=1e-2)
- ref_gain30 = np.array([0, -3, -12, -27, -33, -33, -33, -34.12, -35.57, -36.85, -38, -53])
- gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain
+ ref_gain30 = np.array(
+ [0, -3, -12, -27, -33, -33, -33, -34.12, -35.57, -36.85, -38, -53],
+ )
+ gain30 = self.antenna30.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna30.peak_gain
npt.assert_allclose(gain30, ref_gain30, atol=1e-2)
diff --git a/tests/test_antenna_multiple_transceiver.py b/tests/test_antenna_multiple_transceiver.py
new file mode 100644
index 000000000..8194ef5d2
--- /dev/null
+++ b/tests/test_antenna_multiple_transceiver.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import numpy.testing as npt
+import numpy as np
+import math
+
+from sharc.antenna.antenna_s1528 import AntennaS1528Taylor
+from sharc.antenna.antenna_multiple_transceiver import AntennaMultipleTransceiver
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
+from sharc.station_manager import StationManager
+
+
+class AntennaAntennaMultipleTransceiverTest(unittest.TestCase):
+ def setUp(self):
+ param = ParametersAntennaS1528()
+ param.antenna_gain = 30
+ param.frequency = 2170.0
+ param.bandwidth = 5.0
+ param.antenna_3_dB_bw = 4.4127
+
+ self.base_antenna = AntennaS1528Taylor(param)
+
+ self.single_antenna = AntennaMultipleTransceiver(
+ azimuths=np.array([0.0]),
+ elevations=np.array([0.0]), # so we point at horizon for test
+ num_beams=1,
+ transceiver_radiation_pattern=self.base_antenna,
+ )
+ self.double_antenna_pointing_same_way = AntennaMultipleTransceiver(
+ azimuths=np.array([0.0, 0.0]),
+ elevations=np.array([0.0, 0.0]), # so we point at horizon for test
+ num_beams=2,
+ transceiver_radiation_pattern=self.base_antenna,
+ )
+
+ altitude = 1000
+ cell_radius = 100
+ center_dist = 2 * cell_radius
+ first_layer_angles = np.linspace(-150, 150, 6)
+ sectors7_x = np.concatenate(([0.0], center_dist * np.cos(first_layer_angles)))
+ sectors7_y = np.concatenate(([0.0], center_dist * np.sin(first_layer_angles)))
+
+ self.sectors7_azimuth = np.concatenate(([0.0], first_layer_angles))
+ self.sectors7_elevation = np.concatenate(([-90.0], np.repeat(np.rad2deg(np.arctan2(center_dist, altitude)) - 90, 6)))
+
+ self.sectors7_antenna = AntennaMultipleTransceiver(
+ azimuths=self.sectors7_azimuth,
+ elevations=self.sectors7_elevation,
+ num_beams=7,
+ transceiver_radiation_pattern=self.base_antenna,
+ )
+
+ def test_calculate_gain(self):
+ """
+ We simply compare gains when using antennas separately
+ and when using the multiple transceiver
+ since P_lin = sum_{i=0}^{n_beams} P_in * loss * g_other_sys * g_transceiver(i) (linear)
+ P_lin = P_in * loss * g_other_sys * sum_{i=0}^{n_beams} g_transceiver(i) (linear)
+ so we compare the output of the multiple transceiver antenna with
+ gain_mult_transceiver = sum_{i=0}^{n_beams} g_transceiver(i)
+ """
+ phi = np.array([
+ 0.0, 0.0, 0.0,
+ ])
+ theta = np.array([
+ 90.0, 120.0, 150.0,
+ ])
+
+ off_axis_angle = np.array([0.0, 30.0, 60.0])
+ expected = self.base_antenna.calculate_gain(
+ off_axis_angle_vec=off_axis_angle,
+ theta_vec=theta,
+ )
+ actual = self.single_antenna.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ )
+
+ self.assertEqual(actual.shape, expected.shape)
+ npt.assert_allclose(actual, expected)
+
+ actual_2 = self.double_antenna_pointing_same_way.calculate_gain(
+ phi_vec=phi,
+ theta_vec=theta,
+ )
+
+ self.assertEqual(actual_2.shape, expected.shape)
+ # x * 2 (linear) == x + 3.01... (dB)
+ npt.assert_allclose(actual_2, expected + 3.01029995664)
+
+ # when calculating gain at (0,0,0), considering antenna at (0,0,altitude),
+ # off axis angle will always be elevation + 90
+ off_axis_angle = self.sectors7_elevation + 90.
+ # and theta will always be 90 - elevation
+ theta = 90 - self.sectors7_elevation
+ # and phi will always be -azimuth (since antenna is pointing at +az, to reach 0,0 we need -az)
+ phi = -self.sectors7_azimuth
+
+ # antenna pointing downwards
+ actual7_sec = self.sectors7_antenna.calculate_gain(
+ phi_vec=np.array([0.0]),
+ theta_vec=np.array([180.0]),
+ )
+
+ expected_gains = self.base_antenna.calculate_gain(
+ theta_vec=theta,
+ off_axis_angle_vec=off_axis_angle,
+ )
+
+ expected = np.array([10 * np.log10(np.sum(10**(expected_gains / 10)))])
+
+ self.assertEqual(actual7_sec.shape, expected.shape)
+ npt.assert_allclose(actual7_sec, expected)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_antenna_omni.py b/tests/test_antenna_omni.py
index 66d2f39b0..327318bd6 100644
--- a/tests/test_antenna_omni.py
+++ b/tests/test_antenna_omni.py
@@ -10,40 +10,41 @@
from sharc.antenna.antenna_omni import AntennaOmni
+
class AntennaOmniTest(unittest.TestCase):
-
+
def setUp(self):
self.antenna1 = AntennaOmni()
self.antenna1.gain = 5
-
+
self.antenna2 = AntennaOmni()
self.antenna2.gain = 8.0
-
+
self.antenna3 = AntennaOmni(10)
-
+
def test_gain(self):
self.assertEqual(self.antenna1.gain, 5)
self.assertEqual(self.antenna2.gain, 8)
self.assertEqual(self.antenna3.gain, 10)
-
+
def test_calculate_gain(self):
phi = [30, 60, 90, 45]
-
+
# Test antenna1
gains1 = self.antenna1.calculate_gain(phi_vec=phi)
self.assertEqual(len(gains1), len(phi))
npt.assert_allclose(gains1, self.antenna1.gain)
-
+
# Test antenna2
gains2 = self.antenna2.calculate_gain(phi_vec=phi)
self.assertEqual(len(gains2), len(phi))
npt.assert_allclose(gains2, self.antenna2.gain)
-
+
# Test antenna3
gains3 = self.antenna3.calculate_gain(phi_vec=phi)
self.assertEqual(len(gains3), len(phi))
npt.assert_allclose(gains3, self.antenna3.gain)
-
-
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_rra7_3.py b/tests/test_antenna_rra7_3.py
new file mode 100644
index 000000000..e5ee12c59
--- /dev/null
+++ b/tests/test_antenna_rra7_3.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Mon Feb 6 16:39:34 2017
+
+@author: edgar
+"""
+
+import unittest
+import numpy.testing as npt
+import math
+
+from sharc.antenna.antenna_rra7_3 import AntennaReg_RR_A7_3
+from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter
+
+
+class AntennaReg_RR_A7_3Test(unittest.TestCase):
+
+ def setUp(self):
+ self.antenna1_params = ParametersAntennaWithDiameter(
+ diameter=3,
+ frequency=10000,
+ antenna_gain=50,
+ )
+
+ self.antenna1 = AntennaReg_RR_A7_3(
+ self.antenna1_params,
+ )
+ self.antenna2_params = ParametersAntennaWithDiameter(
+ diameter=3,
+ frequency=4000,
+ antenna_gain=30,
+ )
+ self.antenna2 = AntennaReg_RR_A7_3(
+ self.antenna2_params,
+ )
+ self.antenna3_params = ParametersAntennaWithDiameter(
+ frequency=10000,
+ antenna_gain=50,
+ )
+ self.antenna3 = AntennaReg_RR_A7_3(
+ self.antenna3_params,
+ )
+
+ def test_diameter_inference(self):
+ """Document specifies a diameter to assume for when no diameter is given"""
+ self.assertAlmostEqual(
+ 20 * math.log10(self.antenna3.D_lmbda),
+ self.antenna3_params.antenna_gain - 7.7,
+ delta=1e-10,
+ )
+
+ def test_gain(self):
+ self.assertEqual(self.antenna1.peak_gain, self.antenna1_params.antenna_gain)
+ self.assertEqual(self.antenna1.D_lmbda, 100)
+ self.assertEqual(self.antenna1.g1, 29)
+ self.assertAlmostEqual(self.antenna1.phi_r, 1.000067391, delta=1e-11)
+ self.assertAlmostEqual(self.antenna1.phi_m, 0.91651513899, delta=1e-11)
+
+ self.assertEqual(self.antenna2.peak_gain, self.antenna2_params.antenna_gain)
+ self.assertEqual(self.antenna2.D_lmbda, 40)
+ self.assertAlmostEqual(self.antenna2.g1, 19.0514997832, delta=1e-11)
+ self.assertAlmostEqual(self.antenna2.phi_r, 2.5, delta=1e-11)
+ self.assertAlmostEqual(self.antenna2.phi_m, 1.65442589867, delta=1e-11)
+
+ def test_invalid_antenna(self):
+ """
+ The appendix 7 Annex 3 does not specify what should happen
+ in case phi_r < phi_m. We're throwing an error, but if you
+ happen to know that isn't an error, remove it and verify the behavior you want
+ """
+
+ with self.assertRaises(ValueError):
+ AntennaReg_RR_A7_3(
+ ParametersAntennaWithDiameter(
+ diameter=3,
+ frequency=10000,
+ antenna_gain=100,
+ ),
+ )
+
+ def test_calculate_gain(self):
+ # Test antenna1
+ phi = [
+ 0.9,
+ 0.92,
+ 1.1,
+ 35.9,
+ 36,
+ 180,
+ ]
+ expected = [
+ self.antenna1.peak_gain - 2.5 * 1e-3 * self.antenna1.D_lmbda * phi[0] * self.antenna1.D_lmbda * phi[0],
+ self.antenna1.g1,
+ 29 - 25 * math.log10(phi[2]),
+ 29 - 25 * math.log10(phi[3]),
+ -10,
+ -10,
+ ]
+
+ # Test antenna2
+ phi = [
+ 1.6,
+ 1.7,
+ 2.4,
+ 2.6,
+ 35.9,
+ 36,
+ 180,
+ ]
+ expected = [
+ self.antenna2.peak_gain - 2.5 * 1e-3 * self.antenna2.D_lmbda * phi[0] * self.antenna2.D_lmbda * phi[0],
+ self.antenna2.g1,
+ self.antenna2.g1,
+ 29 - 25 * math.log10(phi[3]),
+ 29 - 25 * math.log10(phi[4]),
+ -10,
+ -10,
+ ]
+
+ # Test antenna1
+ gains2 = self.antenna2.calculate_gain(off_axis_angle_vec=phi)
+ self.assertEqual(len(gains2), len(phi))
+ npt.assert_allclose(gains2, expected)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_antenna_s1528.py b/tests/test_antenna_s1528.py
index f2984a769..3ec2b24ef 100644
--- a/tests/test_antenna_s1528.py
+++ b/tests/test_antenna_s1528.py
@@ -7,19 +7,20 @@
import unittest
-from sharc.antenna.antenna_s1528 import AntennaS1528
-from sharc.parameters.parameters_fss_ss import ParametersFssSs
+from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Taylor
+from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528
import numpy as np
import numpy.testing as npt
+
class AntennaS1528Test(unittest.TestCase):
def setUp(self):
- param = ParametersFssSs()
+ param = ParametersAntennaS1528()
param.antenna_gain = 39
param.antenna_pattern = "ITU-R S.1528-0"
- param.antenna_3_dB = 2
+ param.antenna_3_dB_bw = 2
param.antenna_l_s = -20
self.antenna20 = AntennaS1528(param)
@@ -30,13 +31,55 @@ def setUp(self):
def test_calculate_gain(self):
psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 80, 100])
- ref_gain20 = np.array([0, -3, -8.48, -20, -20, -20, -20, -21.10, -22.55, -23.83, -24.98, -39, -34.25])
- gain20 = self.antenna20.calculate_gain(off_axis_angle_vec=psi) - self.antenna20.peak_gain
+ ref_gain20 = np.array([
+ 0, -
+ 3, -
+ 8.48, -
+ 20, -
+ 20, -
+ 20, -
+ 20, -
+ 21.10, -
+ 22.55, -
+ 23.83, -
+ 24.98, -
+ 39, -
+ 34.25,
+ ])
+ gain20 = self.antenna20.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna20.peak_gain
npt.assert_allclose(gain20, ref_gain20, atol=1e-2)
- ref_gain30 = np.array([0, -3, -8.48, -30, -30, -30, -30, -31.10, -32.55, -33.83, -34.98, -39, -39])
- gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain
+ ref_gain30 = np.array(
+ [0, -3, -8.48, -30, -30, -30, -30, -31.10, -32.55, -33.83, -34.98, -39, -39],
+ )
+ gain30 = self.antenna30.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna30.peak_gain
npt.assert_allclose(gain30, ref_gain30, atol=1e-2)
+ def test_calculate_params_bessel(self):
+ """Compare the parameteres calculated by the class with the reference values present in the Recommendation
+ S.1528-5 - Annex II - Examples for recommends 1.4
+ """
+ params_rolloff_7 = ParametersAntennaS1528(
+ antenna_gain=0,
+ frequency=12000,
+ bandwidth=0,
+ slr=20,
+ n_side_lobes=4,
+ )
+
+ # Create an instance of AntennaS1528Taylor
+ antenna_rolloff_7 = AntennaS1528Taylor(params_rolloff_7)
+ ref_primary_roots = np.array([1.219, 2.233, 3.238])
+ ref_A = 0.95277
+ ref_sigma = 1.1692
+ npt.assert_allclose(antenna_rolloff_7.mu, ref_primary_roots, atol=1e-3)
+ npt.assert_allclose(antenna_rolloff_7.A, ref_A, atol=1e-5)
+ npt.assert_allclose(antenna_rolloff_7.sigma, ref_sigma, atol=1e-4)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_s1855.py b/tests/test_antenna_s1855.py
index a23c770b0..d47470e11 100644
--- a/tests/test_antenna_s1855.py
+++ b/tests/test_antenna_s1855.py
@@ -13,10 +13,11 @@
from sharc.antenna.antenna_s1855 import AntennaS1855
from sharc.parameters.parameters_fss_es import ParametersFssEs
+
class AntennaS1855Test(unittest.TestCase):
def setUp(self):
- #Earth Station Antenna parameters
+ # Earth Station Antenna parameters
params = ParametersFssEs()
params.diameter = 9.1
params.frequency = 27200
@@ -31,10 +32,13 @@ def test_get_gain(self):
off_axis_angle = np.array([7, 8, 15, 100])
theta = np.array([90, 45, 45, 45])
- expected_result = np.array([ 10.87, 8.71, 2.59, -10 ])
- gain = self.antenna.calculate_gain(off_axis_angle_vec = off_axis_angle,
- theta_vec = theta)
+ expected_result = np.array([10.87, 8.71, 2.59, -10])
+ gain = self.antenna.calculate_gain(
+ off_axis_angle_vec=off_axis_angle,
+ theta_vec=theta,
+ )
npt.assert_allclose(gain, expected_result, atol=1e-2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_s672.py b/tests/test_antenna_s672.py
index e694e0cdb..f89bf04b2 100644
--- a/tests/test_antenna_s672.py
+++ b/tests/test_antenna_s672.py
@@ -13,6 +13,7 @@
import numpy as np
import numpy.testing as npt
+
class AntennaS672Test(unittest.TestCase):
def setUp(self):
@@ -27,17 +28,25 @@ def setUp(self):
param.antenna_l_s = -30
self.antenna30 = AntennaS672(param)
-
def test_calculate_gain(self):
psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100])
- ref_gain20 = np.array([0, -3, -12, -20, -20, -20, -20, -21.12, -22.57, -23.85, -25, -50])
- gain20 = self.antenna20.calculate_gain(off_axis_angle_vec=psi) - self.antenna20.peak_gain
+ ref_gain20 = np.array(
+ [0, -3, -12, -20, -20, -20, -20, -21.12, -22.57, -23.85, -25, -50],
+ )
+ gain20 = self.antenna20.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna20.peak_gain
npt.assert_allclose(gain20, ref_gain20, atol=1e-2)
- ref_gain30 = np.array([0, -3, -12, -27, -30, -30, -30, -31.12, -32.57, -33.85, -35, -50])
- gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain
+ ref_gain30 = np.array(
+ [0, -3, -12, -27, -30, -30, -30, -31.12, -32.57, -33.85, -35, -50],
+ )
+ gain30 = self.antenna30.calculate_gain(
+ off_axis_angle_vec=psi,
+ ) - self.antenna30.peak_gain
npt.assert_allclose(gain30, ref_gain30, atol=1e-2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_antenna_sa509.py b/tests/test_antenna_sa509.py
index 04586b5ac..d148b6442 100644
--- a/tests/test_antenna_sa509.py
+++ b/tests/test_antenna_sa509.py
@@ -12,29 +12,29 @@
import unittest
import numpy.testing as npt
+
class AntennaSA509Test(unittest.TestCase):
def setUp(self):
- self.par = ParametersRas();
+ self.par = ParametersRas()
self.par.diameter = 10
self.par.antenna_efficiency = 1
self.par.frequency = 30000
- self.par.SPEED_OF_LIGHT = 3e8
self.antenna = AntennaSA509(self.par)
def test_construction(self):
- self.assertEqual(self.antenna.diameter,10)
- self.assertEqual(self.antenna.efficiency,1)
- self.assertEqual(self.antenna.wavelength,1e-2)
+ self.assertEqual(self.antenna.diameter, 10)
+ self.assertEqual(self.antenna.efficiency, 1)
+ self.assertAlmostEqual(self.antenna.wavelength, 1e-2, places=3)
- self.assertAlmostEqual(self.antenna.effective_area,78.539,delta=1e-2)
+ self.assertAlmostEqual(self.antenna.effective_area, 78.539, delta=1e-2)
- self.assertAlmostEqual(self.antenna.g_0,69.943,delta=1e-2)
- self.assertAlmostEqual(self.antenna.phi_0,0.03464,delta=1e-4)
+ self.assertAlmostEqual(self.antenna.g_0, 69.943, delta=1e-2)
+ self.assertAlmostEqual(self.antenna.phi_0, 0.03464, delta=1e-4)
- self.assertAlmostEqual(self.antenna.phi_1,0.08944,delta=1e-4)
- self.assertAlmostEqual(self.antenna.phi_2,0.14531,delta=1e-4)
+ self.assertAlmostEqual(self.antenna.phi_1, 0.08944, delta=1e-4)
+ self.assertAlmostEqual(self.antenna.phi_2, 0.14531, delta=1e-4)
def test_calculate_gain(self):
phi = np.array([0.03464, 0.05, 0.1, 10, 25, 50, 100, 150])
diff --git a/tests/test_atmosphere.py b/tests/test_atmosphere.py
index 5c3dfa1e0..cfdffafc4 100644
--- a/tests/test_atmosphere.py
+++ b/tests/test_atmosphere.py
@@ -10,14 +10,15 @@
import numpy.testing as npt
from sharc.propagation.atmosphere import ReferenceAtmosphere
+
class TestAtmosphere(unittest.TestCase):
def setUp(self):
self.atmosphere = ReferenceAtmosphere()
def test_specific_attenuation(self):
- temperature = 15 + 273.15 # K
- vapour_density = 7.5 # g/m**3
+ temperature = 15 + 273.15 # K
+ vapour_density = 7.5 # g/m**3
pressure_hPa = 1013.25
vapour_pressure_hPa = vapour_density * temperature / 216.7
@@ -28,12 +29,15 @@ def test_specific_attenuation(self):
specific_att_p676_upper = [5e-1, 2e1, 5e-1, 4, 7e1, 8e2]
for index in range(len(f_GHz_vec)):
- specific_att[index] = self.atmosphere._get_specific_attenuation(pressure_hPa,
- vapour_pressure_hPa,
- temperature,
- float(f_GHz_vec[index]) * 1000)
+ specific_att[index] = self.atmosphere._get_specific_attenuation(
+ pressure_hPa,
+ vapour_pressure_hPa,
+ temperature,
+ float(f_GHz_vec[index]) * 1000,
+ )
npt.assert_array_less(specific_att_p676_lower, specific_att)
npt.assert_array_less(specific_att, specific_att_p676_upper)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_footprint.py b/tests/test_footprint.py
index a2710d4c8..b697ce934 100644
--- a/tests/test_footprint.py
+++ b/tests/test_footprint.py
@@ -3,54 +3,152 @@
Created on Mon Nov 27 08:53:15 2017
@author: Calil
+
+Updated on Sun Jul 29 21:34:07 2024
+
+Request for Issue 33 correction suggestion, adding the unitary tests for LEO type satellite in 600Km and 1200Km heights.
+
+@author: Thiago Ferreira
"""
+# Thiago Ferreira - 231025717
+
+# from footprint import Footprint # Old importing for testing and debugging space
+# Importing Footprint class from the given module in repository
+from sharc.support.footprint import Footprint
import unittest
import numpy as np
import numpy.testing as npt
-from sharc.support.footprint import Footprint
+# Added the matplotlib for plotting the footprint generated in the test
+import matplotlib.pyplot as plt
+
+
+class FootprintTest(unittest.TestCase):
+ """
+ Unit testing class for Footprint calculations, focusing on different satellite configurations
+ such as beam width, elevation angle, and satellite height.
+ """
-class FootprintAreaTest(unittest.TestCase):
-
def setUp(self):
- self.fa1 = Footprint(0.1,bore_lat_deg=0,bore_subsat_long_deg=0.0)
- self.fa2 = Footprint(0.325,bore_lat_deg = 0)
- self.fa3 = Footprint(0.325,elevation_deg = 20)
-
+ """
+ Set up the test environment by initializing Footprint instances with different parameters.
+ """
+ # Geostationary (GEO type) satellite height (35786000 m) and different
+ # beam widths and elevations
+ self.fa1 = Footprint(0.1, bore_lat_deg=0, bore_subsat_long_deg=0.0)
+ self.fa2 = Footprint(0.325, bore_lat_deg=0)
+ self.fa3 = Footprint(0.325, elevation_deg=20)
+ self.fa4 = Footprint(0.325, elevation_deg=30, sat_height=1200000)
+ self.fa5 = Footprint(0.325, elevation_deg=30, sat_height=600000)
+ self.fa6 = Footprint(
+ 0.325,
+ sat_height=1200000,
+ bore_lat_deg=0,
+ bore_subsat_long_deg=17.744178387,
+ )
+
+ """
+ Requested tests for Low Earth Orbit (LEO) Satellite at 1200Km and 600Km added bellow (Issue 33).
+ """
+
+ # New tests obtained for the LEO satellite type heights (1200km and
+ # 600km)
+ # Different satellite heights (1200 km and 600 km)
+ self.sat_heights = [1200000, 600000]
+
def test_construction(self):
- self.assertEqual(self.fa1.bore_lat_deg,0)
- self.assertEqual(self.fa1.bore_subsat_long_deg,0)
- self.assertEqual(self.fa1.beam_width_deg,0.1)
- self.assertEqual(self.fa1.bore_lat_rad,0)
- self.assertEqual(self.fa1.bore_subsat_long_rad,0)
- self.assertEqual(self.fa1.beam_width_rad,np.pi/1800)
- self.assertEqual(self.fa1.beta,0)
- self.assertEqual(self.fa1.bore_tilt,0)
-
- self.assertEqual(self.fa2.bore_lat_deg,0)
- self.assertEqual(self.fa2.bore_subsat_long_deg,0)
- self.assertEqual(self.fa2.bore_lat_rad,0)
- self.assertEqual(self.fa2.bore_subsat_long_rad,0)
-
- self.assertEqual(self.fa3.bore_lat_deg,0)
- self.assertAlmostEqual(self.fa3.bore_subsat_long_deg,61.84,delta=0.01)
-
+ """
+ Test the correct construction of Footprint instances with expected values.
+ """
+ # Verify properties of fa1
+ self.assertEqual(self.fa1.bore_lat_deg, 0)
+ self.assertEqual(self.fa1.bore_subsat_long_deg, 0)
+ self.assertEqual(self.fa1.beam_width_deg, 0.1)
+ self.assertEqual(self.fa1.bore_lat_rad, 0)
+ self.assertEqual(self.fa1.bore_subsat_long_rad, 0)
+ self.assertEqual(self.fa1.beam_width_rad, np.pi / 1800)
+ self.assertEqual(self.fa1.beta, 0)
+ self.assertEqual(self.fa1.bore_tilt, 0)
+
+ # Verify properties of fa2
+ self.assertEqual(self.fa2.bore_lat_deg, 0)
+ self.assertEqual(self.fa2.bore_subsat_long_deg, 0)
+ self.assertEqual(self.fa2.bore_lat_rad, 0)
+ self.assertEqual(self.fa2.bore_subsat_long_rad, 0)
+
+ # Verify properties of fa3
+ self.assertEqual(self.fa3.bore_lat_deg, 0)
+ self.assertAlmostEqual(
+ self.fa3.bore_subsat_long_deg, 61.84, delta=0.01,
+ )
+
+ # Verify properties of fa4
+ self.assertEqual(self.fa4.bore_lat_deg, 0)
+ self.assertAlmostEqual(
+ self.fa4.bore_subsat_long_deg, 13.22, delta=0.01,
+ )
+
+ # Verify properties of fa5
+ self.assertEqual(self.fa5.bore_lat_deg, 0)
+ self.assertAlmostEqual(self.fa5.bore_subsat_long_deg, 7.68, delta=0.01)
+
+ self.assertEqual(self.fa6.sat_height, 1200000)
+ self.assertAlmostEqual(self.fa6.elevation_deg, 20, delta=0.01)
+
def test_set_elevation(self):
+ """
+ Test the set_elevation method to ensure it correctly updates the elevation angle.
+ """
self.fa2.set_elevation(20)
- self.assertEqual(self.fa2.bore_lat_deg,0)
- self.assertAlmostEqual(self.fa2.bore_subsat_long_deg,61.84,delta=0.01)
-
+ self.assertEqual(self.fa2.bore_lat_deg, 0)
+ self.assertAlmostEqual(
+ self.fa2.bore_subsat_long_deg, 61.84, delta=0.01,
+ )
+
def test_calc_footprint(self):
+ """
+ Test the calc_footprint method to verify the coordinates of the generated footprint polygon.
+ """
fp_long, fp_lat = self.fa1.calc_footprint(4)
- npt.assert_allclose(fp_long,np.array([0.0, 0.487, -0.487, 0.0]),atol=1e-2)
- npt.assert_allclose(fp_lat,np.array([-0.562, 0.281, 0.281, -0.562]),atol=1e-2)
-
+ npt.assert_allclose(
+ fp_long, np.array(
+ [0.0, 0.487, -0.487, 0.0],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ fp_lat, np.array(
+ [-0.562, 0.281, 0.281, -0.562],
+ ), atol=1e-2,
+ )
+
def test_calc_area(self):
+ """
+ Test the calc_area method to verify the calculation of the footprint area.
+ """
a1 = self.fa2.calc_area(1000)
- self.assertAlmostEqual(a1,130000,delta=200)
+ self.assertAlmostEqual(a1, 130000, delta=130000 * 0.0025)
a2 = self.fa3.calc_area(1000)
- self.assertAlmostEqual(a2,486300,delta=200)
-
+ self.assertAlmostEqual(a2, 486300, delta=486300 * 0.0025)
+ a3 = self.fa4.calc_area(1000)
+ self.assertAlmostEqual(a3, 810, delta=810 * 0.0025)
+ a4 = self.fa5.calc_area(1000)
+ self.assertAlmostEqual(a4, 234, delta=234 * 0.0025)
+
+ for height in self.sat_heights:
+ beam_deg = 0.325
+ footprint = Footprint(
+ beam_deg, elevation_deg=90, sat_height=height,
+ )
+ cone_radius_in_km = height * np.tan(np.deg2rad(beam_deg)) / 1000
+ cone_base_area_in_km2 = np.pi * (cone_radius_in_km**2)
+ footprint_area_in_km2 = footprint.calc_area(1000)
+ self.assertAlmostEqual(
+ footprint_area_in_km2,
+ cone_base_area_in_km2,
+ delta=cone_base_area_in_km2 * 0.01,
+ )
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_orbit_model.py b/tests/test_orbit_model.py
new file mode 100644
index 000000000..6336f8f88
--- /dev/null
+++ b/tests/test_orbit_model.py
@@ -0,0 +1,67 @@
+import unittest
+import numpy as np
+from sharc.satellite.ngso.orbit_model import OrbitModel
+
+
+class TestOrbitModel(unittest.TestCase):
+
+ def setUp(self):
+ self.orbit = OrbitModel(
+ Nsp=6,
+ Np=8,
+ phasing=7.5,
+ long_asc=0,
+ omega=0,
+ delta=52,
+ hp=1414,
+ ha=1414,
+ Mo=0,
+ )
+
+ def test_initialization(self):
+ self.assertEqual(self.orbit.Nsp, 6)
+ self.assertEqual(self.orbit.Np, 8)
+ self.assertAlmostEqual(self.orbit.phasing, 7.5)
+ self.assertAlmostEqual(self.orbit.long_asc, 0)
+ self.assertAlmostEqual(self.orbit.omega, 0)
+ self.assertAlmostEqual(self.orbit.delta, 52)
+ self.assertAlmostEqual(self.orbit.perigee_alt_km, 1414)
+ self.assertAlmostEqual(self.orbit.apogee_alt_km, 1414)
+ self.assertAlmostEqual(self.orbit.Mo, 0)
+
+ def test_orbit_parameters(self):
+ self.assertAlmostEqual(self.orbit.semi_major_axis, 7792.145)
+ self.assertAlmostEqual(self.orbit.eccentricity, 0.0)
+ self.assertAlmostEqual(self.orbit.orbital_period_sec, 6845.3519, places=4)
+ self.assertAlmostEqual(self.orbit.sat_sep_angle_deg, 60.0)
+ self.assertAlmostEqual(self.orbit.orbital_plane_inclination, 45.0)
+
+ def test_mean_anomalies(self):
+ self.orbit.get_orbit_positions_time_instant(time_instant_secs=0)
+ ma_deg = np.degrees(self.orbit.mean_anomaly.reshape((self.orbit.Np, self.orbit.Nsp)))
+
+ # Check phasing between satellites in the same plane
+ r = np.ones((ma_deg.shape[0] - 1, ma_deg.shape[1])) * self.orbit.phasing
+ np.testing.assert_array_almost_equal(np.diff(ma_deg, axis=0), r, decimal=4)
+
+ # Check phase between planes
+ r = np.ones((ma_deg.shape[0], ma_deg.shape[1] - 1)) * self.orbit.sat_sep_angle_deg
+ np.testing.assert_array_almost_equal(np.diff(ma_deg, axis=1), r, decimal=4)
+
+ # Check mean anomaly values with randon time
+ self.orbit.get_orbit_positions_random(rng=np.random.RandomState())
+ ma_deg = np.unwrap(np.degrees(self.orbit.mean_anomaly.reshape((self.orbit.Np, self.orbit.Nsp))), period=360)
+
+ # Check phasing between satellites in the same plane
+ r = np.ones((ma_deg.shape[0] - 1, ma_deg.shape[1])) * self.orbit.phasing
+ phasing_diff = np.diff(ma_deg, axis=0)
+ phasing_diff[phasing_diff < 0] += 360
+ np.testing.assert_array_almost_equal(phasing_diff, r, decimal=4)
+
+ # Check phase between planes
+ r = np.ones((ma_deg.shape[0], ma_deg.shape[1] - 1)) * self.orbit.sat_sep_angle_deg
+ np.testing.assert_array_almost_equal(np.diff(ma_deg, axis=1), r, decimal=4)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_post_processor.py b/tests/test_post_processor.py
new file mode 100644
index 000000000..af0a8d956
--- /dev/null
+++ b/tests/test_post_processor.py
@@ -0,0 +1,36 @@
+
+import unittest
+
+from sharc.results import Results
+from sharc.post_processor import PostProcessor
+
+
+class StationTest(unittest.TestCase):
+ def setUp(self):
+ self.post_processor = PostProcessor()
+ # We just prepare to write beacause Results class is not fully initialized
+ # before preparing to read or loading from previous results
+ self.results = Results().prepare_to_write(
+ None,
+ True,
+ )
+
+ def test_generate_and_add_cdf_plots_from_results(self):
+ self.results.imt_coupling_loss.extend([0, 1, 2, 3, 4, 5])
+ self.results.imt_dl_inr.extend([0, 1, 2, 3, 4, 5])
+
+ trace_legend = "any legendd. Lorem ipsum"
+ self.post_processor.add_plot_legend_pattern(dir_name_contains="output", legend=trace_legend)
+
+ self.post_processor.add_plots(
+ self.post_processor.generate_cdf_plots_from_results(
+ [self.results],
+ ),
+ )
+
+ self.assertEqual(len(self.post_processor.plots), 2)
+ self.assertEqual(self.post_processor.plots[0].data[0].name, trace_legend)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_propagation_abg.py b/tests/test_propagation_abg.py
index 887422176..a3385283a 100644
--- a/tests/test_propagation_abg.py
+++ b/tests/test_propagation_abg.py
@@ -13,46 +13,41 @@
from sharc.propagation.propagation_abg import PropagationABG
+
class PropagationABGTest(unittest.TestCase):
def setUp(self):
- self.abg = PropagationABG(np.random.RandomState())
+ self.abg = PropagationABG(
+ random_number_gen=np.random.RandomState(),
+ alpha=3.4,
+ beta=19.2,
+ gamma=2.3,
+ building_loss=20,
+ shadowing_sigma_dB=6.5,
+ )
def test_loss(self):
- d = np.array([[100, 500],[400, 60]])
- f = 27000
- indoor = np.zeros(d.shape, dtype=bool)
- alpha = 3.4
- beta = 19.2
- gamma = 2.3
- shadowing = 0
- loss = np.array ([[120.121, 143.886347],[140.591406, 112.578509]])
-
- npt.assert_allclose(self.abg.get_loss(distance_2D = d,
- frequency = f,
- indoor_stations = indoor,
- line_of_sight_prob = 1,
- alpha = alpha,
- beta = beta,
- gamma = gamma,
- shadowing = shadowing),
- loss,atol=1e-2)
-
- d = np.array([500, 3000])
- f = np.array([27000, 40000])
- indoor = np.zeros(d.shape, dtype=bool)
- alpha = 3.4
- beta = 19.2
- gamma = 2.3
- shadowing = 0
-
- loss = np.array ([143.886,174.269])
- npt.assert_allclose(self.abg.get_loss(distance_2D = d,
- frequency = f,
- indoor_stations = indoor,
- line_of_sight_prob = 1,
- alpha = alpha,
- beta = beta,
- gamma = gamma,
- shadowing = shadowing),
- loss ,atol=1e-2)
+ d = np.array([[100, 500], [400, 60]])
+ f = np.ones(d.shape, dtype=float) * 27000.0
+ indoor = np.zeros(d.shape[0], dtype=bool)
+ shadowing = False
+ loss = np.array([[120.121, 143.886347], [140.591406, 112.578509]])
+
+ npt.assert_allclose(
+ self.abg.get_loss(d, f, indoor, shadowing),
+ loss, atol=1e-2,
+ )
+
+ d = np.array([500, 3000])[:, np.newaxis]
+ f = np.array([27000, 40000])[:, np.newaxis]
+ indoor = np.zeros(d.shape[0], dtype=bool)
+ shadowing = False
+ loss = np.array([143.886, 174.269])[:, np.newaxis]
+ npt.assert_allclose(
+ self.abg.get_loss(d, f, indoor, shadowing),
+ loss, atol=1e-2,
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_propagation_building_entry_loss.py b/tests/test_propagation_building_entry_loss.py
index c8fa6740e..b2fed2f20 100644
--- a/tests/test_propagation_building_entry_loss.py
+++ b/tests/test_propagation_building_entry_loss.py
@@ -14,7 +14,9 @@
class TestPropagationBuildingEntryLoss(unittest.TestCase):
def setUp(self):
- self.building_entry_loss = PropagationBuildingEntryLoss(np.random.RandomState())
+ self.building_entry_loss = PropagationBuildingEntryLoss(
+ np.random.RandomState(),
+ )
def test_building_entry_loss(self):
# compare with benchmark from ITU-R P-2109-0 Fig. 1
@@ -25,8 +27,9 @@ def test_building_entry_loss(self):
loss_lower = np.array([9, 10, 11, 15, 16, 19, 21])
loss_upper = np.array([10, 12, 15, 17, 18, 21, 25])
-
- loss = self.building_entry_loss.get_loss(f_GHz_vec * 1000, 0, prob=.5, test=True)
+ loss = self.building_entry_loss.get_loss(
+ f_GHz_vec * 1000, 0, prob=.5, test=True,
+ )
npt.assert_array_less(loss_lower, loss)
npt.assert_array_less(loss, loss_upper)
@@ -34,8 +37,10 @@ def test_building_entry_loss(self):
loss_lower = [39, 30, 25, 30, 32, 40, 55]
loss_upper = [40, 35, 30, 31, 35, 43, 57]
- loss = self.building_entry_loss.get_loss(f_GHz_vec * 1000, 0, prob=.5, test=True,
- building_class="THERMALLY_EFFICIENT")
+ loss = self.building_entry_loss.get_loss(
+ f_GHz_vec * 1000, 0, prob=.5, test=True,
+ building_class="THERMALLY_EFFICIENT",
+ )
npt.assert_array_less(loss_lower, loss)
npt.assert_array_less(loss, loss_upper)
diff --git a/tests/test_propagation_clear_air.py b/tests/test_propagation_clear_air.py
index 5061f311a..2ec63fa5d 100644
--- a/tests/test_propagation_clear_air.py
+++ b/tests/test_propagation_clear_air.py
@@ -3,75 +3,61 @@
import unittest
import numpy as np
-import matplotlib.pyplot as plt
-from sharc.parameters.parameters_fss_es import ParametersFssEs
-
import numpy.testing as npt
-
+from sharc.parameters.parameters_p452 import ParametersP452
from sharc.propagation.propagation_clear_air_452 import PropagationClearAir
+
class PropagationClearAirTest(unittest.TestCase):
def setUp(self):
- self.__ClearAir = PropagationClearAir(np.random.RandomState())
-
- def test_loss(self):
-
- params = ParametersFssEs()
-
- d = 10000
- f = 27000
- params.atmospheric_pressure = 1013
- params.air_temperature = 288
- params.water_vapour = 7.5
- params.Dlt = 30
- params.Dlr = 10
- params.Dct = 10
- params.Dcr = 10
-
- params.Hts = 244
- params.Hrs = 280
- params.Hte = 50
- params.Hre = 50
-
- params.theta_tx = 20
- params.theta_rx = 20
+ param_p452 = ParametersP452()
+ # param_p452.atmospheric_pressure = 1013
+ # param_p452.air_temperature = 288
+ # param_p452.Dct = 10
+ # param_p452.Dcr = 10
- params.N0 = 355
- params.delta_N = 60
- params.percentage_p = 40
+ # param_p452.Hte = 50
+ # param_p452.Hre = 50
- params.omega = 0
- params.phi = 60
- params.dtm = .8
- params.dlm = .8
+ # param_p452.N0 = 355
+ # param_p452.delta_N = 60
+ # param_p452.percentage_p = 40
- params.epsilon = 3.5
+ # param_p452.clutter_loss = False
- params.hm = 15
- params.Hsr = 45
- params.Hst = 48
+ self.prop_clear_air = PropagationClearAir(
+ np.random.RandomState(), param_p452,
+ )
- params.H0 = 15
- params.Hn = 17
-
- params.thetaJ = 0.3
- params.par_ep = 0.8
-
- params.clutter_loss = False
-
- Gt = 10
- Gr = 10
-
- di = [1,1,1]
- hi = [2,4,6]
-
-
-# Ld50, Ldbeta, Ldb = self.__Diffraction.get_loss(beta = Beta, distance=d, frequency=f, atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro, delta_N=deltaN, Hrs=hrs, Hts=hts, Hte=hte, Hre=hre, Hsr=hsr, Hst=hst, H0=h0, Hn=hn, dist_di=di, hight_hi=hi, omega=omega, Dlt=dlt ,Dlr=dlr, percentage_p=p)
-#
-# npt.assert_allclose(158.491,Ldb,atol=1e-3)
+ def test_loss(self):
- #Grafico da perda de difraรงao em funรงao da distancia e da frequencia
+ # distance between stations in meters
+ distances = np.ones((1, 1), dtype=float) * 1000
+ frequencies = np.ones((1, 1), dtype=float) * 27000 # frequency in MHz
+ indoor_stations = np.zeros((1, 1), dtype=bool)
+ # elevation between stations in degrees
+ elevations = np.zeros((1, 1), dtype=float)
+ tx_gain = np.ones((1, 1), dtype=float) * 0
+ rx_gain = np.ones((1, 1), dtype=float) * 0
+
+ loss = self.prop_clear_air.get_loss(
+ distances,
+ frequencies,
+ indoor_stations,
+ elevations,
+ tx_gain,
+ rx_gain,
+ )
+ # npt.assert_allclose(158.491, loss, atol=1e-3)
+
+ # Ld50, Ldbeta, Ldb = self.__Diffraction.get_loss(beta = Beta, distance=d, frequency=f, atmospheric_pressure=Ph,
+ # air_temperature=T, water_vapour=ro, delta_N=deltaN, Hrs=hrs, Hts=hts, Hte=hte, Hre=hre, Hsr=hsr, Hst=hst, H0=h0,
+ # Hn=hn, dist_di=di, hight_hi=hi, omega=omega, Dlt=dlt ,Dlr=dlr, percentage_p=p)
+
+ # npt.assert_allclose(158.491,Ldb,atol=1e-3)
+
+ # Grafico da perda de difraรงao em funรงao da distancia e da frequencia
# data1 = []
# data2 = []
# data3 = []
diff --git a/tests/test_propagation_clutter.py b/tests/test_propagation_clutter.py
index 257cd0af0..f70edc535 100644
--- a/tests/test_propagation_clutter.py
+++ b/tests/test_propagation_clutter.py
@@ -1,63 +1,63 @@
-# -*- coding: utf-8 -*-
+import unittest
+import numpy as np
+from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
+from sharc.support.enumerations import StationType
-"""
-Created on Tue Mai 02 15:02:31 2017
-@author: LeticiaValle_Mac
-"""
+class TestPropagationClutterLoss(unittest.TestCase):
+ def setUp(self):
+ self.clutter_loss = PropagationClutterLoss(np.random.RandomState(42))
-import unittest
-import numpy as np
+ def test_spatial_clutter_loss(self):
+ frequency = np.array([27000]) # MHz
+ elevation = np.array([0, 45, 90])
+ loc_percentage = np.array([0.1, 0.5, 0.9])
+ distance = np.array([1000]) # meters, dummy value
+ loss = self.clutter_loss.get_loss(
+ distance=distance,
+ frequency=frequency,
+ elevation=elevation,
+ loc_percentage=loc_percentage,
+ station_type=StationType.FSS_SS,
+ )
-from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss
+ # Check the shape of the output
+ self.assertEqual(loss.shape, (3,))
-class PropagationClutterLossTest(unittest.TestCase):
+ # Check if loss decreases with increasing elevation
+ self.assertTrue(loss[0] >= loss[1] >= loss[2])
- def setUp(self):
- self.__ClutterAtt = PropagationClutterLoss(np.random.RandomState())
-
- def test_loss(self):
-
- f = 27000 #GHz
- theta = 0
- per_p =50
- d = 10000
- P = 0.9
- dist = 10
- r = 12.64
- s = 3.72
- t = 0.96
- u = 9.6
- v = 2
- w = 9.1
- x = -3
- y = 4.5
- z = -2
- #npt.assert_allclose(73.150,
- # self.__ClutterAtt.get_loss(frequency=f, distance = d,percentage_p = per_p, dist = dist, elevation_angle_facade=theta, probability_loss_notExceeded=P, coeff_r=r, coeff_s=s, coeff_t=t, coeff_u=u,coeff_v=v, coeff_w=w,coeff_x=x,coeff_y=y,coeff_z=z),atol=1e-3)
-
-
-# f = [10,20] #GHz
-# d = 10
-# Ph = 1013
-# T = 288
-# ro = 7.5
-# npt.assert_allclose([0.140, 1.088],
-# self.__gasAtt.get_loss_Ag(distance=d, frequency=f, atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro),atol=1e-3)
-
-
-# d = [[10, 20, 30],[40, 50, 60]]
-# f = 10
-# Ph = 1013
-# T = 288
-# ro = 7.5
-# self.assertTrue(np.all(np.isclose([0.140, 0.280, 0.420],[0.560, 0.700, 0.840],
-# self.__gasAtt.get_loss_Ag(distance=d, frequency=f,atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro), atol=1e-3)))
-#
-#
+ def test_terrestrial_clutter_loss(self):
+ frequency = np.array([2000, 6000]) # MHz
+ distance = np.array([500, 2000]) # meters
+ # Using a single value for location percentage
+ loc_percentage = np.array([0.5])
+
+ loss = self.clutter_loss.get_loss(
+ frequency=frequency,
+ distance=distance,
+ loc_percentage=loc_percentage,
+ station_type=StationType.IMT_BS,
+ )
+
+ self.assertEqual(loss.shape, (2,))
+
+ self.assertTrue(loss[1] >= loss[0])
+
+ def test_random_loc_percentage(self):
+ frequency = np.array([4000]) # MHz
+ distance = np.array([1000]) # meters
+
+ loss = self.clutter_loss.get_loss(
+ frequency=frequency,
+ distance=distance,
+ loc_percentage="RANDOM",
+ station_type=StationType.IMT_UE,
+ )
+
+ self.assertTrue(0 <= loss <= 100)
if __name__ == '__main__':
unittest.main()
-
diff --git a/tests/test_propagation_free_space.py b/tests/test_propagation_free_space.py
index 7eb24fe71..cab9ffd61 100644
--- a/tests/test_propagation_free_space.py
+++ b/tests/test_propagation_free_space.py
@@ -20,27 +20,29 @@ def setUp(self):
def test_loss(self):
d = np.array([10])
f = np.array([10])
- loss = self.freeSpace.get_loss(distance_2D=d, frequency=f)
+ loss = self.freeSpace.get_loss(d, f)
ref_loss = np.array([12.45])
npt.assert_allclose(ref_loss, loss, atol=1e-2)
- d = np.array([ 10, 100 ])
- f = np.array([ 10, 100 ])
- loss = self.freeSpace.get_loss(distance_2D=d, frequency=f)
+ d = np.array([10, 100])
+ f = np.array([10, 100])
+ loss = self.freeSpace.get_loss(d, f)
ref_loss = np.array([12.45, 52.45])
npt.assert_allclose(ref_loss, loss, atol=1e-2)
- d = np.array([ 10, 100, 1000 ])
- f = np.array([ 10, 100, 1000 ])
- loss = self.freeSpace.get_loss(distance_2D=d, frequency=f)
+ d = np.array([10, 100, 1000])
+ f = np.array([10, 100, 1000])
+ loss = self.freeSpace.get_loss(d, f)
ref_loss = np.array([12.45, 52.45, 92.45])
npt.assert_allclose(ref_loss, loss, atol=1e-2)
d = np.array([[10, 20, 30], [40, 50, 60]])
- f = np.array([ 100 ])
- loss = self.freeSpace.get_loss(distance_2D=d, frequency=f)
- ref_loss = np.array([[ 32.45, 38.47, 41.99],
- [ 44.49, 46.42, 48.01]])
+ f = np.array([100])
+ loss = self.freeSpace.get_loss(d, f)
+ ref_loss = np.array([
+ [32.45, 38.47, 41.99],
+ [44.49, 46.42, 48.01],
+ ])
npt.assert_allclose(ref_loss, loss, atol=1e-2)
diff --git a/tests/test_propagation_hdfss_building_side.py b/tests/test_propagation_hdfss_building_side.py
index 1ef143040..ed747de75 100644
--- a/tests/test_propagation_hdfss_building_side.py
+++ b/tests/test_propagation_hdfss_building_side.py
@@ -9,16 +9,17 @@
import numpy as np
import numpy.testing as npt
-from sharc.parameters.parameters_fss_es import ParametersFssEs
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
from sharc.support.enumerations import StationType
from sharc.propagation.propagation_hdfss_building_side import PropagationHDFSSBuildingSide
+
class PropagationHDFSSBuildingSideTest(unittest.TestCase):
def setUp(self):
# Basic propagation
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = False
par.shadow_enabled = False
par.same_building_enabled = True
@@ -26,11 +27,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.5
par.bs_building_entry_loss_value = 50
- self.propagation = PropagationHDFSSBuildingSide(par,rnd)
-
+ self.propagation = PropagationHDFSSBuildingSide(par, rnd)
+
# Propagation with fixed BEL
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -38,11 +39,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_fixed_value = PropagationHDFSSBuildingSide(par,rnd)
-
+ self.propagation_fixed_value = PropagationHDFSSBuildingSide(par, rnd)
+
# Propagation with fixed probability
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -50,11 +51,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'P2109_FIXED'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_fixed_prob = PropagationHDFSSBuildingSide(par,rnd)
-
+ self.propagation_fixed_prob = PropagationHDFSSBuildingSide(par, rnd)
+
# Propagation with random probability
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -62,108 +63,126 @@ def setUp(self):
par.bs_building_entry_loss_type = 'P2109_RANDOM'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_random_prob = PropagationHDFSSBuildingSide(par,rnd)
-
+ self.propagation_random_prob = PropagationHDFSSBuildingSide(par, rnd)
+
def test_get_loss(self):
# On same building
d = np.array([[10.0, 80.0, 200.0]])
- f = 40000*np.ones_like(d)
+ f = 40000 * np.ones_like(d)
ele = np.transpose(np.zeros_like(d))
es_x = np.array([0.0])
es_y = np.array([25.0])
es_z = np.array([10.0])
- imt_x = np.array([ 0.0, 0.0, -200.0])
- imt_y = np.array([15.0, 80.0, 25.0])
- imt_z = np.array([ 1.5, 6.0, 7.5])
-
- loss = self.propagation.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=StationType.IMT_BS,
- imt_x=imt_x,
- imt_y=imt_y,
- imt_z=imt_z,
- es_x=es_x,
- es_y=es_y,
- es_z=es_z)
+ imt_x = np.array([0.0, 0.0, -200.0])
+ imt_y = np.array([15.0, 80.0, 25.0])
+ imt_z = np.array([1.5, 6.0, 7.5])
+
+ loss = self.propagation.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=StationType.IMT_BS,
+ imt_x=imt_x,
+ imt_y=imt_y,
+ imt_z=imt_z,
+ es_x=es_x,
+ es_y=es_y,
+ es_z=es_z,
+ )
loss = loss[0]
-
+
expected_loss = np.array([[84.48, 103.35, 140.05]])
-
- npt.assert_allclose(loss,expected_loss,atol=1e-1)
-
+
+ npt.assert_allclose(loss, expected_loss, atol=1e-1)
+
def test_get_build_loss(self):
# Initialize variables
- ele = np.array([[ 0.0, 45.0, 90.0]])
- f = 40000*np.ones_like(ele)
+ ele = np.array([[0.0, 45.0, 90.0]])
+ f = 40000 * np.ones_like(ele)
sta_type = StationType.IMT_BS
-
+
# Test 1: fixed value
expected_build_loss = 50.0
- build_loss = self.propagation_fixed_value.get_building_loss(sta_type,
- f,
- ele)
- self.assertEqual(build_loss,expected_build_loss)
-
+ build_loss = self.propagation_fixed_value.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ self.assertEqual(build_loss, expected_build_loss)
+
# Test 2: fixed probability
expected_build_loss = np.array([[24.4, 33.9, 43.4]])
- build_loss = self.propagation_fixed_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_fixed_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
# Test 3: random probability
expected_build_loss = np.array([[21.7, 32.9, 15.9]])
- build_loss = self.propagation_random_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_random_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
# Test 4: UE station
sta_type = StationType.IMT_UE
expected_build_loss = np.array([[21.7, 32.9, 15.9]])
- build_loss = self.propagation_fixed_value.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
- build_loss = self.propagation_fixed_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
+ build_loss = self.propagation_fixed_value.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+ build_loss = self.propagation_fixed_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
expected_build_loss = np.array([[10.1, 36.8, 52.6]])
- build_loss = self.propagation_random_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_random_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
def test_is_same_build(self):
# Test is_same_building()
es_x = np.array([0.0])
es_y = np.array([25.0])
- imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0])
- imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6])
-
- expected_in_build = np.array([True,False,False,False,True])
- in_build = self.propagation.is_same_building(imt_x,
- imt_y,
- es_x,
- es_y)
- npt.assert_array_equal(in_build,expected_in_build)
-
+ imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0])
+ imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6])
+
+ expected_in_build = np.array([True, False, False, False, True])
+ in_build = self.propagation.is_same_building(
+ imt_x,
+ imt_y,
+ es_x,
+ es_y,
+ )
+ npt.assert_array_equal(in_build, expected_in_build)
+
def test_is_next_build(self):
# Test is_same_building()
es_x = np.array([0.0])
es_y = np.array([25.0])
- imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0])
- imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6]) + 80.0
-
- expected_in_build = np.array([True,False,False,False,True])
- in_build = self.propagation.is_next_building(imt_x,
- imt_y,
- es_x,
- es_y)
- npt.assert_array_equal(in_build,expected_in_build)
-
-
+ imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0])
+ imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6]) + 80.0
+
+ expected_in_build = np.array([True, False, False, False, True])
+ in_build = self.propagation.is_next_building(
+ imt_x,
+ imt_y,
+ es_x,
+ es_y,
+ )
+ npt.assert_array_equal(in_build, expected_in_build)
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_propagation_hdfss_roof_top.py b/tests/test_propagation_hdfss_roof_top.py
index 5a92432da..e6b6d8903 100644
--- a/tests/test_propagation_hdfss_roof_top.py
+++ b/tests/test_propagation_hdfss_roof_top.py
@@ -9,15 +9,16 @@
import numpy as np
import numpy.testing as npt
-from sharc.parameters.parameters_fss_es import ParametersFssEs
+from sharc.parameters.parameters_hdfss import ParametersHDFSS
from sharc.support.enumerations import StationType
from sharc.propagation.propagation_hdfss_roof_top import PropagationHDFSSRoofTop
+
class PropagationHDFSSRoofTopTest(unittest.TestCase):
def setUp(self):
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = False
par.shadow_enabled = False
par.same_building_enabled = True
@@ -25,11 +26,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.5
par.bs_building_entry_loss_value = 50
- self.propagation = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation = PropagationHDFSSRoofTop(par, rnd)
+
# Propagation with fixed BEL
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -37,11 +38,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_fixed_value = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation_fixed_value = PropagationHDFSSRoofTop(par, rnd)
+
# Propagation with fixed probability
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -49,11 +50,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'P2109_FIXED'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_fixed_prob = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation_fixed_prob = PropagationHDFSSRoofTop(par, rnd)
+
# Propagation with random probability
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = True
par.shadow_enabled = False
par.same_building_enabled = True
@@ -61,11 +62,11 @@ def setUp(self):
par.bs_building_entry_loss_type = 'P2109_RANDOM'
par.bs_building_entry_loss_prob = 0.6
par.bs_building_entry_loss_value = 50
- self.propagation_random_prob = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation_random_prob = PropagationHDFSSRoofTop(par, rnd)
+
# Same building disabled
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = False
par.shadow_enabled = False
par.same_building_enabled = False
@@ -73,11 +74,13 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.5
par.bs_building_entry_loss_value = 50
- self.propagation_same_build_disabled = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation_same_build_disabled = PropagationHDFSSRoofTop(
+ par, rnd,
+ )
+
# Diffraction loss enabled
rnd = np.random.RandomState(101)
- par = ParametersFssEs()
+ par = ParametersHDFSS()
par.building_loss_enabled = False
par.shadow_enabled = False
par.same_building_enabled = True
@@ -85,182 +88,212 @@ def setUp(self):
par.bs_building_entry_loss_type = 'FIXED_VALUE'
par.bs_building_entry_loss_prob = 0.5
par.bs_building_entry_loss_value = 50
- self.propagation_diff_enabled = PropagationHDFSSRoofTop(par,rnd)
-
+ self.propagation_diff_enabled = PropagationHDFSSRoofTop(par, rnd)
+
def test_get_loss(self):
# Not on same building
d = np.array([[10.0, 20.0, 30.0, 60.0, 90.0, 300.0, 1000.0]])
- f = 40000*np.ones_like(d)
+ f = 40000 * np.ones_like(d)
ele = np.transpose(np.zeros_like(d))
-
- loss = self.propagation.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=StationType.IMT_BS,
- imt_x = 100.0*np.ones(7),
- imt_y = 100.0*np.ones(7),
- imt_z = 100.0*np.ones(7),
- es_x = np.array([0.0]),
- es_y = np.array([0.0]),
- es_z = np.array([0.0]))
+
+ loss = self.propagation.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=StationType.IMT_BS,
+ imt_x=100.0 * np.ones(7),
+ imt_y=100.0 * np.ones(7),
+ imt_z=100.0 * np.ones(7),
+ es_x=np.array([0.0]),
+ es_y=np.array([0.0]),
+ es_z=np.array([0.0]),
+ )
loss = loss[0]
-
- expected_loss = np.array([[84.48, 90.50, 94.02, 100.72, 104.75, 139.33, 162.28]])
-
- npt.assert_allclose(loss,expected_loss,atol=1e-1)
-
+
+ expected_loss = np.array(
+ [[84.48, 90.50, 94.02, 100.72, 104.75, 139.33, 162.28]],
+ )
+
+ npt.assert_allclose(loss, expected_loss, atol=1e-1)
+
# On same building
d = np.array([[10.0, 20.0, 30.0]])
- f = 40000*np.ones_like(d)
+ f = 40000 * np.ones_like(d)
ele = np.transpose(np.zeros_like(d))
es_x = np.array([0.0])
es_y = np.array([0.0])
es_z = np.array([10.0])
- imt_x = np.array([ 0.0, 20.0, 30.0])
- imt_y = np.array([10.0, 0.0, 0.0])
- imt_z = np.array([ 1.5, 6.0, 7.5])
-
- loss = self.propagation.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=StationType.IMT_BS,
- imt_x=imt_x,
- imt_y=imt_y,
- imt_z=imt_z,
- es_x=es_x,
- es_y=es_y,
- es_z=es_z)
+ imt_x = np.array([0.0, 20.0, 30.0])
+ imt_y = np.array([10.0, 0.0, 0.0])
+ imt_z = np.array([1.5, 6.0, 7.5])
+
+ loss = self.propagation.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=StationType.IMT_BS,
+ imt_x=imt_x,
+ imt_y=imt_y,
+ imt_z=imt_z,
+ es_x=es_x,
+ es_y=es_y,
+ es_z=es_z,
+ )
loss = loss[0]
-
+
expected_loss = np.array([[150 + 84.48, 100 + 90.50, 50 + 94.02]])
-
- npt.assert_allclose(loss,expected_loss,atol=1e-1)
-
+
+ npt.assert_allclose(loss, expected_loss, atol=1e-1)
+
def test_get_build_loss(self):
# Initialize variables
- ele = np.array([[ 0.0, 45.0, 90.0]])
- f = 40000*np.ones_like(ele)
+ ele = np.array([[0.0, 45.0, 90.0]])
+ f = 40000 * np.ones_like(ele)
sta_type = StationType.IMT_BS
-
+
# Test 1: fixed value
expected_build_loss = 50.0
- build_loss = self.propagation_fixed_value.get_building_loss(sta_type,
- f,
- ele)
- self.assertEqual(build_loss,expected_build_loss)
-
+ build_loss = self.propagation_fixed_value.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ self.assertEqual(build_loss, expected_build_loss)
+
# Test 2: fixed probability
expected_build_loss = np.array([[24.4, 33.9, 43.4]])
- build_loss = self.propagation_fixed_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_fixed_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
# Test 3: random probability
expected_build_loss = np.array([[21.7, 32.9, 15.9]])
- build_loss = self.propagation_random_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_random_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
# Test 4: UE station
sta_type = StationType.IMT_UE
expected_build_loss = np.array([[21.7, 32.9, 15.9]])
- build_loss = self.propagation_fixed_value.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
- build_loss = self.propagation_fixed_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
+ build_loss = self.propagation_fixed_value.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+ build_loss = self.propagation_fixed_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
expected_build_loss = np.array([[10.1, 36.8, 52.6]])
- build_loss = self.propagation_random_prob.get_building_loss(sta_type,
- f,
- ele)
- npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1)
-
+ build_loss = self.propagation_random_prob.get_building_loss(
+ sta_type,
+ f,
+ ele,
+ )
+ npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1)
+
def test_same_building(self):
# Test is_same_building()
es_x = np.array([0.0])
es_y = np.array([0.0])
es_z = np.array([19.0])
- imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0])
- imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6])
- imt_z = 3*np.ones_like(imt_x)
-
- expected_in_build = np.array([True,False,False,False,True])
- in_build = self.propagation_same_build_disabled.is_same_building(imt_x,
- imt_y,
- es_x,
- es_y)
- npt.assert_array_equal(in_build,expected_in_build)
-
+ imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0])
+ imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6])
+ imt_z = 3 * np.ones_like(imt_x)
+
+ expected_in_build = np.array([True, False, False, False, True])
+ in_build = self.propagation_same_build_disabled.is_same_building(
+ imt_x,
+ imt_y,
+ es_x,
+ es_y,
+ )
+ npt.assert_array_equal(in_build, expected_in_build)
+
# Test loss
- d = np.sqrt(np.power(imt_x,2) + np.power(imt_y,2))
+ d = np.sqrt(np.power(imt_x, 2) + np.power(imt_y, 2))
d = np.array([list(d)])
- f = 40000*np.ones_like(d)
+ f = 40000 * np.ones_like(d)
ele = np.transpose(np.zeros_like(d))
-
- loss = self.propagation_same_build_disabled.get_loss(distance_3D=d,
- frequency=f,
- elevation=ele,
- imt_sta_type=StationType.IMT_BS,
- imt_x=imt_x,
- imt_y=imt_y,
- imt_z=imt_z,
- es_x=es_x,
- es_y=es_y,
- es_z=es_z)
+
+ loss = self.propagation_same_build_disabled.get_loss(
+ distance_3D=d,
+ frequency=f,
+ elevation=ele,
+ imt_sta_type=StationType.IMT_BS,
+ imt_x=imt_x,
+ imt_y=imt_y,
+ imt_z=imt_z,
+ es_x=es_x,
+ es_y=es_y,
+ es_z=es_z,
+ )
loss = loss[0]
- expected_loss = np.array([[4067.5,94.0,103.6,103.1,4086.5]])
-
- npt.assert_allclose(loss,expected_loss,atol=1e-1)
-
+ expected_loss = np.array([[4067.5, 94.0, 103.6, 103.1, 4086.5]])
+
+ npt.assert_allclose(loss, expected_loss, atol=1e-1)
+
def test_get_diff_distances(self):
es_x = np.array([10.0])
es_y = np.array([15.0])
es_z = np.array([19.0])
- imt_x = np.array([ 80.0, 50.0, 10.0,-80.0, 0.0])
- imt_y = np.array([ 15.0, 55.0, 95.0, 15.0,-40.0])
- imt_z = np.array([ 1.5, 3.0, 6.0, 7.5, 20.5])
-
+ imt_x = np.array([80.0, 50.0, 10.0, -80.0, 0.0])
+ imt_y = np.array([15.0, 55.0, 95.0, 15.0, -40.0])
+ imt_z = np.array([1.5, 3.0, 6.0, 7.5, 20.5])
+
# 2D distances
- distances = self.propagation.get_diff_distances(imt_x,
- imt_y,
- imt_z,
- es_x,
- es_y,
- es_z,
- dist_2D=True)
- expected_distances = (np.array([60.0,35.4,25.0,60.0,25.4]),
- np.array([10.0,21.2,55.0,30.0,30.5]))
- npt.assert_allclose(distances,expected_distances,atol=1e-1)
-
+ distances = self.propagation.get_diff_distances(
+ imt_x,
+ imt_y,
+ imt_z,
+ es_x,
+ es_y,
+ es_z,
+ dist_2D=True,
+ )
+ expected_distances = (
+ np.array([60.0, 35.4, 25.0, 60.0, 25.4]),
+ np.array([10.0, 21.2, 55.0, 30.0, 30.5]),
+ )
+ npt.assert_allclose(distances, expected_distances, atol=1e-1)
+
# 3D distances
- distances = self.propagation.get_diff_distances(imt_x,
- imt_y,
- imt_z,
- es_x,
- es_y,
- es_z)
- expected_distances = (np.array([ 14.0, 9.0, 3.0, 6.7, -1.7]),
- np.array([ 60.0, 35.4, 25.0, 60.0, 25.4]),
- np.array([ 19.3, 25.9, 56.3, 31.8, 30.6]))
- npt.assert_allclose(distances,expected_distances,atol=1e-1)
-
+ distances = self.propagation.get_diff_distances(
+ imt_x,
+ imt_y,
+ imt_z,
+ es_x,
+ es_y,
+ es_z,
+ )
+ expected_distances = (
+ np.array([14.0, 9.0, 3.0, 6.7, -1.7]),
+ np.array([60.0, 35.4, 25.0, 60.0, 25.4]),
+ np.array([19.3, 25.9, 56.3, 31.8, 30.6]),
+ )
+ npt.assert_allclose(distances, expected_distances, atol=1e-1)
+
def test_diffration_loss(self):
# Test diffraction loss
- h = np.array([7.64,-0.56,-1.2,-0.1])
- d1 = np.array([34.99,1060.15,5.0,120.0])
- d2 = np.array([25.02,25.02,2.33,245.0])
- f = 40000*np.ones_like(h)
-
- loss = self.propagation_diff_enabled.get_diffraction_loss(h,d1,d2,f)
- expected_loss = np.array([43.17,0.0,0.0,4.48])
-
- npt.assert_allclose(loss,expected_loss,atol=1e-1)
-
-
+ h = np.array([7.64, -0.56, -1.2, -0.1])
+ d1 = np.array([34.99, 1060.15, 5.0, 120.0])
+ d2 = np.array([25.02, 25.02, 2.33, 245.0])
+ f = 40000 * np.ones_like(h)
+
+ loss = self.propagation_diff_enabled.get_diffraction_loss(h, d1, d2, f)
+ expected_loss = np.array([43.17, 0.0, 0.0, 4.48])
+
+ npt.assert_allclose(loss, expected_loss, atol=1e-1)
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_propagation_indoor.py b/tests/test_propagation_indoor.py
index 8740ef1e5..f77b8b36c 100644
--- a/tests/test_propagation_indoor.py
+++ b/tests/test_propagation_indoor.py
@@ -6,43 +6,49 @@
"""
import unittest
-import numpy as np
-import numpy.testing as npt
+# import numpy as np
-from sharc.propagation.propagation_indoor import PropagationIndoor
-from sharc.parameters.parameters_indoor import ParametersIndoor
+# from sharc.propagation.propagation_indoor import PropagationIndoor
+# from sharc.parameters.parameters_indoor import ParametersIndoor
class PropagationIndoorTest(unittest.TestCase):
+ """Tests PropagationIndoor class
+ """
def setUp(self):
pass
def test_loss(self):
- params = ParametersIndoor()
- params.basic_path_loss = "INH_OFFICE"
- params.n_rows = 3
- params.n_colums = 1
- # params.street_width = 30
- params.ue_indoor_percent = .95
- params.building_class = "TRADITIONAL"
- params.num_cells = 3
-
- bs_per_building = 3
- ue_per_bs = 3
-
- num_bs = bs_per_building*params.n_rows*params.n_colums
- num_ue = num_bs*ue_per_bs
- distance_2D = 150*np.random.random((num_bs, num_ue))
- frequency = 27000*np.ones(distance_2D.shape)
- indoor = np.random.rand(num_bs) < params.ue_indoor_percent
- h_bs = 3*np.ones(num_bs)
- h_ue = 1.5*np.ones(num_ue)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1))
- elevation = np.degrees(np.arctan(height_diff/distance_2D))
-
- propagation_indoor = PropagationIndoor(np.random.RandomState(), params, ue_per_bs)
+ """Tests the get_loss method
+ """
+ pass
+ # params = ParametersIndoor()
+ # params.basic_path_loss = "INH_OFFICE"
+ # params.n_rows = 3
+ # params.n_colums = 1
+ # # params.street_width = 30
+ # params.ue_indoor_percent = .95
+ # params.building_class = "TRADITIONAL"
+ # params.num_cells = 3
+
+ # bs_per_building = 3
+ # ue_per_bs = 3
+
+ # num_bs = bs_per_building * params.n_rows * params.n_colums
+ # num_ue = num_bs * ue_per_bs
+ # distance_2D = 150 * np.random.random((num_bs, num_ue))
+ # frequency = 27000 * np.ones(distance_2D.shape)
+ # indoor = np.random.rand(num_bs) < params.ue_indoor_percent
+ # h_bs = 3 * np.ones(num_bs)
+ # h_ue = 1.5 * np.ones(num_ue)
+ # distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2)
+ # height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1))
+ # elevation = np.degrees(np.arctan(height_diff / distance_2D))
+
+ # propagation_indoor = PropagationIndoor(
+ # np.random.RandomState(), params, ue_per_bs)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_propagation_sat_simple.py b/tests/test_propagation_sat_simple.py
index 715d71c7f..cadca5172 100644
--- a/tests/test_propagation_sat_simple.py
+++ b/tests/test_propagation_sat_simple.py
@@ -15,64 +15,93 @@
class PropagationSatSimpleTest(unittest.TestCase):
def setUp(self):
- self.propagation = PropagationSatSimple(np.random.RandomState())
+ self.propagation = PropagationSatSimple(
+ random_number_gen=np.random.RandomState(),
+ enable_clutter_loss=False,
+ )
def test_loss(self):
d = np.array(10)
f = np.array(10)
- loc_percentage = 0
- elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)}
+ elevation = {
+ 'free_space': 90 *
+ np.ones(
+ d.shape,
+ ),
+ 'apparent': 90 *
+ np.ones(
+ d.shape,
+ ),
+ }
indoor_stations = np.zeros(d.shape, dtype=bool)
- self.assertAlmostEqual(13.19,
- self.propagation.get_loss(distance_3D=d,
- frequency=f,
- loc_percentage=loc_percentage,
- indoor_stations=indoor_stations,
- elevation=elevation,
- enable_clutter_loss=False),
- delta = 1e-2)
+ self.assertAlmostEqual(
+ 13.19,
+ self.propagation.get_loss(
+ d, f, indoor_stations, elevation,
+ ),
+ delta=1e-2,
+ )
- d = np.array([ 10, 100 ])
- f = np.array([ 10, 100 ])
- loc_percentage = 0
- elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)}
+ d = np.array([10, 100])
+ f = np.array([10, 100])
+ elevation = {
+ 'free_space': 90 *
+ np.ones(
+ d.shape,
+ ),
+ 'apparent': 90 *
+ np.ones(
+ d.shape,
+ ),
+ }
indoor_stations = np.zeros(d.shape, dtype=bool)
- npt.assert_allclose([13.2, 53.2],
- self.propagation.get_loss(distance_3D=d,
- frequency=f,
- loc_percentage=loc_percentage,
- indoor_stations=indoor_stations,
- elevation=elevation,
- enable_clutter_loss=False),
- atol=1e-2)
+ npt.assert_allclose(
+ [13.2, 53.2],
+ self.propagation.get_loss(
+ d, f, indoor_stations, elevation,
+ ),
+ atol=1e-2,
+ )
- d = np.array([ 10, 100, 1000 ])
- f = np.array([ 10, 100, 1000 ])
- loc_percentage = 0
- elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)}
+ d = np.array([10, 100, 1000])
+ f = np.array([10, 100, 1000])
+ elevation = {
+ 'free_space': 90 *
+ np.ones(
+ d.shape,
+ ),
+ 'apparent': 90 *
+ np.ones(
+ d.shape,
+ ),
+ }
indoor_stations = np.array([0, 0, 0], dtype=bool)
- npt.assert_allclose([13.2, 53.2, 93.2],
- self.propagation.get_loss(distance_3D=d,
- frequency=f,
- loc_percentage=loc_percentage,
- indoor_stations=indoor_stations,
- elevation=elevation,
- enable_clutter_loss=False),
- atol=1e-2)
+ npt.assert_allclose(
+ [13.2, 53.2, 93.2],
+ self.propagation.get_loss(
+ d, f, indoor_stations, elevation,
+ ),
+ atol=1e-2,
+ )
- d = np.array([[10, 20, 30],[40, 50, 60]])
- f = np.array([ 100 ])
- loc_percentage = 0
- elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)}
+ d = np.array([[10, 20, 30], [40, 50, 60]])
+ f = np.array([100])
+ elevation = {
+ 'free_space': 90 *
+ np.ones(
+ d.shape,
+ ),
+ 'apparent': 90 *
+ np.ones(
+ d.shape,
+ ),
+ }
indoor_stations = np.zeros(d.shape, dtype=bool)
- ref_loss = [[ 33.2, 39.2, 42.7],
- [ 45.2, 47.1, 48.7]]
- loss = self.propagation.get_loss(distance_3D=d,
- frequency=f,
- loc_percentage=loc_percentage,
- indoor_stations=indoor_stations,
- elevation=elevation,
- enable_clutter_loss=False)
+ ref_loss = [
+ [33.2, 39.2, 42.7],
+ [45.2, 47.1, 48.7],
+ ]
+ loss = self.propagation.get_loss(d, f, indoor_stations, elevation)
npt.assert_allclose(ref_loss, loss, atol=1e-1)
diff --git a/tests/test_propagation_uma.py b/tests/test_propagation_uma.py
index 2132ece60..ba572f8aa 100644
--- a/tests/test_propagation_uma.py
+++ b/tests/test_propagation_uma.py
@@ -11,99 +11,151 @@
from sharc.propagation.propagation_uma import PropagationUMa
+
class PropagationUMaTest(unittest.TestCase):
def setUp(self):
self.uma = PropagationUMa(np.random.RandomState())
def test_los_probability(self):
- distance_2D = np.array([[10, 15, 40],
- [17, 60, 80]])
+ distance_2D = np.array([
+ [10, 15, 40],
+ [17, 60, 80],
+ ])
h_ue = np.array([1.5, 8, 15])
- los_probability = np.array([[1, 1, 0.74],
- [1, 0.57, 0.45]])
- npt.assert_allclose(self.uma.get_los_probability(distance_2D, h_ue),
- los_probability,
- atol=1e-2)
-
+ los_probability = np.array([
+ [1, 1, 0.74],
+ [1, 0.57, 0.45],
+ ])
+ npt.assert_allclose(
+ self.uma.get_los_probability(distance_2D, h_ue),
+ los_probability,
+ atol=1e-2,
+ )
def test_breakpoint_distance(self):
h_bs = np.array([15, 20, 25, 30])
h_ue = np.array([3, 4])
- h_e = np.ones((len(h_bs), len(h_ue)))
- frequency = 30000*np.ones((len(h_bs), len(h_ue)))
- breakpoint_distance = np.array([[ 11200, 16800],
- [ 15200, 22800],
- [ 19200, 28800],
- [ 23200, 34800]])
- npt.assert_array_equal(self.uma.get_breakpoint_distance(frequency, h_bs, h_ue, h_e),
- breakpoint_distance)
-
+ h_e = np.ones((h_ue.size, h_bs.size))
+ frequency = 30000 * np.ones(h_e.shape)
+ breakpoint_distance = np.array([
+ [11200, 15200, 19200, 23200],
+ [16800, 22800, 28800, 34800],
+ ])
+ npt.assert_array_equal(
+ self.uma.get_breakpoint_distance(frequency, h_bs, h_ue, h_e),
+ breakpoint_distance,
+ )
def test_loss_los(self):
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 30000*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 30000 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[102.32, 108.09, 111.56, 114.05],
- [115.99, 117.56, 118.90, 120.06]])
- npt.assert_allclose(self.uma.get_loss_los(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
-
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ loss = np.array([
+ [102.32, 115.99],
+ [108.09, 117.56],
+ [111.56, 118.90],
+ [114.05, 120.06],
+ ])
+ npt.assert_allclose(
+ self.uma.get_loss_los(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
+
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 300*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 300 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[62.32, 68.09, 71.56, 74.05],
- [87.06, 84.39, 83.57, 83.40]])
- npt.assert_allclose(self.uma.get_loss_los(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
+ loss = np.array([
+ [62.32, 87.06],
+ [68.09, 84.39],
+ [71.56, 83.57],
+ [74.05, 83.40],
+ ])
+ npt.assert_allclose(
+ self.uma.get_loss_los(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
def test_loss_nlos(self):
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 30000*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 30000 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[121.58, 132.25, 138.45, 142.70],
- [148.29, 150.77, 152.78, 154.44]])
- npt.assert_allclose(self.uma.get_loss_nlos(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
-
- distance_2D = np.array([[1000, 2000, 5000, 4000],
- [3000, 6000, 7000, 8000]])
+ loss = np.array([
+ [121.58, 148.29],
+ [132.25, 150.77],
+ [138.45, 152.78],
+ [142.70, 154.44],
+ ])
+ npt.assert_allclose(
+ self.uma.get_loss_nlos(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
+
+ distance_2D = np.array([
+ [1000, 3000],
+ [2000, 6000],
+ [5000, 7000],
+ [4000, 8000],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 300*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 300 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[120.02, 131.18, 146.13, 141.75],
- [138.66, 149.83, 151.84, 153.51]])
- npt.assert_allclose(self.uma.get_loss_nlos(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
+ loss = np.array([
+ [120.02, 138.66],
+ [131.18, 149.83],
+ [146.13, 151.84],
+ [141.75, 153.51],
+ ])
+ npt.assert_allclose(
+ self.uma.get_loss_nlos(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
if __name__ == '__main__':
diff --git a/tests/test_propagation_umi.py b/tests/test_propagation_umi.py
index fb3ac1371..7a1df8294 100644
--- a/tests/test_propagation_umi.py
+++ b/tests/test_propagation_umi.py
@@ -12,100 +12,157 @@
from sharc.propagation.propagation_umi import PropagationUMi
+
class PropagationUMiTest(unittest.TestCase):
def setUp(self):
los_adjustment_factor = 18
- self.umi = PropagationUMi(np.random.RandomState(), los_adjustment_factor)
+ self.umi = PropagationUMi(
+ np.random.RandomState(),
+ los_adjustment_factor,
+ )
def test_los_probability(self):
- distance_2D = np.array([[10, 15, 40],
- [17, 60, 80]])
- los_probability = np.array([[1, 1, 0.631],
- [1, 0.432, 0.308]])
- npt.assert_allclose(self.umi.get_los_probability(distance_2D,
- self.umi.los_adjustment_factor),
- los_probability,
- atol=1e-2)
-
+ distance_2D = np.array([
+ [10, 15, 40],
+ [17, 60, 80],
+ ])
+ los_probability = np.array([
+ [1, 1, 0.631],
+ [1, 0.432, 0.308],
+ ])
+ npt.assert_allclose(
+ self.umi.get_los_probability(
+ distance_2D,
+ self.umi.los_adjustment_factor,
+ ),
+ los_probability,
+ atol=1e-2,
+ )
def test_breakpoint_distance(self):
h_bs = np.array([15, 20, 25, 30])
h_ue = np.array([3, 4])
- h_e = np.ones((len(h_bs), len(h_ue)))
- frequency = 30000*np.ones((len(h_bs), len(h_ue)))
- breakpoint_distance = np.array([[ 11200, 16800],
- [ 15200, 22800],
- [ 19200, 28800],
- [ 23200, 34800]])
- npt.assert_array_equal(self.umi.get_breakpoint_distance(frequency, h_bs, h_ue, h_e),
- breakpoint_distance)
-
+ h_e = np.ones((h_ue.size, h_bs.size))
+ frequency = 30000 * np.ones(h_e.shape)
+ breakpoint_distance = np.array([
+ [11200, 15200, 19200, 23200],
+ [16800, 22800, 28800, 34800],
+ ])
+ npt.assert_array_equal(
+ self.umi.get_breakpoint_distance(frequency, h_bs, h_ue, h_e),
+ breakpoint_distance,
+ )
def test_loss_los(self):
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 30000*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 30000 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[104.336, 110.396, 114.046, 116.653],
- [118.690, 120.346, 121.748, 122.963]])
- npt.assert_allclose(self.umi.get_loss_los(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
-
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ loss = np.array([
+ [104.336, 118.690],
+ [110.396, 120.346],
+ [114.046, 121.748],
+ [116.653, 122.963],
+ ])
+ npt.assert_allclose(
+ self.umi.get_loss_los(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
+
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 300*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 300 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[64.336, 70.396, 74.046, 76.653],
- [89.215, 86.829, 86.187, 86.139]])
- npt.assert_allclose(self.umi.get_loss_los(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
+ loss = np.array([
+ [64.336, 89.215],
+ [70.396, 86.829],
+ [74.046, 86.187],
+ [76.653, 86.139],
+ ])
+ npt.assert_allclose(
+ self.umi.get_loss_los(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
def test_loss_nlos(self):
- distance_2D = np.array([[100, 200, 300, 400],
- [500, 600, 700, 800]])
+ distance_2D = np.array([
+ [100, 500],
+ [200, 600],
+ [300, 700],
+ [400, 800],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 30000*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 30000 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[128.84, 138.72, 144.56, 148.64],
- [152.96, 155.45, 157.50, 159.25]])
- npt.assert_allclose(self.umi.get_loss_nlos(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
-
-
- distance_2D = np.array([[1000, 2000, 5000, 4000],
- [3000, 6000, 7000, 8000]])
+ loss = np.array([
+ [128.84, 152.96],
+ [138.72, 155.45],
+ [144.56, 157.50],
+ [148.64, 159.25],
+ ])
+ npt.assert_allclose(
+ self.umi.get_loss_nlos(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
+
+ distance_2D = np.array([
+ [1000, 3000],
+ [2000, 6000],
+ [5000, 7000],
+ [4000, 8000],
+ ])
h_bs = np.array([30, 35])
h_ue = np.array([2, 3, 4, 5])
h_e = np.ones(distance_2D.shape)
- distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2)
- frequency = 300*np.ones(distance_2D.shape)
+ distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2)
+ frequency = 300 * np.ones(distance_2D.shape)
shadowing_std = 0
- loss = np.array([[ 120.96 , 131.29, 145.03, 141.31],
- [ 137.80 , 148.13 , 150.19, 151.94]])
- npt.assert_allclose(self.umi.get_loss_nlos(distance_2D, distance_3D, frequency,
- h_bs, h_ue, h_e, shadowing_std),
- loss,
- atol=1e-2)
+ loss = np.array([
+ [120.96, 137.80],
+ [131.29, 148.13],
+ [145.03, 150.19],
+ [141.31, 151.94],
+ ])
+ npt.assert_allclose(
+ self.umi.get_loss_nlos(
+ distance_2D, distance_3D, frequency,
+ h_bs, h_ue, h_e, shadowing_std,
+ ),
+ loss,
+ atol=1e-2,
+ )
if __name__ == '__main__':
diff --git a/tests/test_results.py b/tests/test_results.py
new file mode 100644
index 000000000..969ae2489
--- /dev/null
+++ b/tests/test_results.py
@@ -0,0 +1,101 @@
+import unittest
+
+from sharc.results import Results
+
+
+class StationTest(unittest.TestCase):
+ def setUp(self):
+ self.results = Results().prepare_to_write(
+ None,
+ True,
+ output_dir="output",
+ output_dir_prefix="out",
+ )
+
+ def test_flush_to_and_load_from_file(self):
+ arr1 = [1., 2., 3., 4., 5., 6., 7., 8., 9., 100.]
+ self.results.imt_coupling_loss.extend(arr1)
+ self.results.imt_bs_antenna_gain.extend(arr1)
+ self.assertGreater(len(self.results.imt_coupling_loss), 0)
+ self.assertGreater(len(self.results.imt_bs_antenna_gain), 0)
+ # Results should flush
+ self.results.write_files(1)
+ # check that no results are left in arr
+ self.assertEqual(len(self.results.imt_coupling_loss), 0)
+ self.assertEqual(len(self.results.imt_bs_antenna_gain), 0)
+
+ arr2 = [101., 102., 103., 104., 105., 106., 107., 108., 109.]
+ self.results.imt_coupling_loss.extend(arr2)
+ self.results.imt_bs_antenna_gain.extend(arr2)
+ self.assertGreater(len(self.results.imt_coupling_loss), 0)
+ self.assertGreater(len(self.results.imt_bs_antenna_gain), 0)
+ self.results.write_files(2)
+ # check that no results are left in arr
+ self.assertEqual(len(self.results.imt_coupling_loss), 0)
+ self.assertEqual(len(self.results.imt_bs_antenna_gain), 0)
+
+ results_recuperated_from_file = Results().load_from_dir(self.results.output_directory)
+
+ results_arr = arr1
+ results_arr.extend(arr2)
+
+ self.assertEqual(results_recuperated_from_file.imt_coupling_loss, results_arr)
+ self.assertEqual(results_recuperated_from_file.imt_bs_antenna_gain, results_arr)
+
+ results_recuperated_from_file = Results().load_from_dir(
+ self.results.output_directory, only_samples=["imt_bs_antenna_gain"],
+ )
+
+ self.assertEqual(results_recuperated_from_file.imt_coupling_loss, [])
+ self.assertEqual(results_recuperated_from_file.imt_bs_antenna_gain, results_arr)
+
+ def test_get_most_recent_dirs(self):
+ dir_2024_01_01_04 = "caminho_abs/prefixo_2024-01-01_04"
+ dir_2024_01_01_10 = "caminho_abs/prefixo_2024-01-01_10"
+
+ dir_2024_01_02_01 = "caminho_abs/prefixo_2024-01-02_01"
+ dir_2024_10_01_01 = "caminho_abs/prefixo_2024-10-01_01"
+ another_dir = "caminho_abs/prefixo2_2024-10-01_01"
+
+ dirs = self.results.get_most_recent_outputs_for_each_prefix([
+ dir_2024_01_01_04,
+ dir_2024_01_01_10,
+ dir_2024_01_02_01,
+ dir_2024_10_01_01,
+ another_dir,
+ ])
+
+ self.assertEqual(len(dirs), 2)
+
+ self.assertIn(dir_2024_10_01_01, dirs)
+ self.assertIn(another_dir, dirs)
+
+ dirs = self.results.get_most_recent_outputs_for_each_prefix([
+ dir_2024_01_01_04,
+ dir_2024_01_01_10,
+ dir_2024_01_02_01,
+ ])
+
+ self.assertEqual(len(dirs), 1)
+
+ self.assertIn(dir_2024_01_02_01, dirs)
+
+ dirs = self.results.get_most_recent_outputs_for_each_prefix([
+ dir_2024_01_01_04,
+ dir_2024_01_01_10,
+ ])
+
+ self.assertEqual(len(dirs), 1)
+
+ self.assertIn(dir_2024_01_01_10, dirs)
+
+ def test_get_prefix_date_and_id(self):
+ dir_2024_01_01_04 = "caminho_abs/prefixo_2024-01-01_04"
+ prefix, date, id = Results.get_prefix_date_and_id(dir_2024_01_01_04)
+ self.assertEqual(prefix, "caminho_abs/prefixo_")
+ self.assertEqual(date, "2024-01-01")
+ self.assertEqual(id, "04")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_sat_utils.py b/tests/test_sat_utils.py
new file mode 100644
index 000000000..163931160
--- /dev/null
+++ b/tests/test_sat_utils.py
@@ -0,0 +1,125 @@
+import unittest
+import numpy as np
+import numpy.testing as npt
+import sharc.satellite.utils.sat_utils as sat_utils
+
+
+class TestSatUtils(unittest.TestCase):
+
+ def setUp(self):
+ pass
+
+ def test_ecef2lla(self):
+ # Object is over the meridional plane at 1414km of altitude
+ sx = 7792137.0
+ sy = 0.0
+ sz = 0.0
+ lat, lng, alt = sat_utils.ecef2lla(sx, sy, sz)
+ npt.assert_almost_equal(lat, 0.0, 2)
+ npt.assert_almost_equal(lng, 0.0, 2)
+ npt.assert_almost_equal(alt, 1414000.0, 1)
+
+ # Object is over the meridional plane at sea level
+ sx = 6378137.0
+ sy = 0.0
+ sz = 0.0
+ lat, lng, alt = sat_utils.ecef2lla(sx, sy, sz)
+ npt.assert_almost_equal(lat, 0.0, 2)
+ npt.assert_almost_equal(lng, 0.0, 2)
+ npt.assert_almost_equal(alt, 0.0, 1)
+
+ sx = 7792137.0
+ sy = 6378137.0
+ sz = 3264751.4
+ lat, lng, alt = sat_utils.ecef2lla(sx, sy, sz)
+ npt.assert_almost_equal(lat, 18.0316, 3)
+ npt.assert_almost_equal(lng, 39.30153, 3)
+ npt.assert_almost_equal(alt, 4209582, 1)
+
+ # test the array form
+ sx = [7792137.0, 6378137.0, 7792137.0]
+ sy = [0.0, 0.0, 6378137.0]
+ sz = [0.0, 0.0, 3264751.4]
+ lat, lng, alt = sat_utils.ecef2lla(sx, sy, sz)
+ npt.assert_almost_equal(lat, [0.0, 0.0, 18.0316], 3)
+ npt.assert_almost_equal(lng, [0.0, 0.0, 39.30153], 3)
+ npt.assert_almost_equal(alt, [1414000.0, 0.0, 4209582], 1)
+
+ def test_lla2ecef(self):
+ # Object is over the meridional plane at 1414km of altitude
+ lat = 0.0
+ lng = 0.0
+ alt = 1414000.0
+ sx, sy, sz = sat_utils.lla2ecef(lat, lng, alt)
+ npt.assert_almost_equal(sx, 7792137.0, 1)
+ npt.assert_almost_equal(sy, 0.0, 1)
+ npt.assert_almost_equal(sz, 0.0, 1)
+
+ # Object is over the meridional plane at sea level
+ lat = 0.0
+ lng = 0.0
+ alt = 0.0
+ sx, sy, sz = sat_utils.lla2ecef(lat, lng, alt)
+ npt.assert_almost_equal(sx, 6378137.0, 1)
+ npt.assert_almost_equal(sy, 0.0, 1)
+ npt.assert_almost_equal(sz, 0.0, 1)
+
+ lat = 43.0344
+ lng = 46.839308
+ alt = 141400.0
+ sx, sy, sz = sat_utils.lla2ecef(lat, lng, alt)
+ npt.assert_almost_equal(sx, 3264751.4, 1)
+ npt.assert_almost_equal(sy, 3481390.4, 1)
+ npt.assert_almost_equal(sz, 4426792.5, 1)
+
+ # test the array form
+ lat = [0.0, 0.0, 43.0344]
+ lng = [0.0, 0.0, 46.839308]
+ alt = [1414000.0, 0.0, 141400.0]
+ sx, sy, sz = sat_utils.lla2ecef(lat, lng, alt)
+ npt.assert_almost_equal(sx, [7792137.0, 6378137.0, 3264751.4], 1)
+ npt.assert_almost_equal(sy, [0.0, 0.0, 3481390.4], 1)
+ npt.assert_almost_equal(sz, [0.0, 0.0, 4426792.5], 1)
+
+ def test_calculate_elev_angle(self):
+ earth_station_coords = [
+ (0.0, 0.0),
+ (-15.0, -42.0),
+ (10.0, 20.0),
+ (-15.0, -25.0),
+ ]
+
+ space_station_coords = [
+ (0.0, 0.0),
+ (-10.0, -40.0),
+ (12.0, 25.0),
+ (0.0, -30.0),
+ ]
+
+ space_station_alts_km = [
+ 1414,
+ 525,
+ 340,
+ 35786,
+ ]
+
+ expected_elevations = [
+ 90.0,
+ 37.48,
+ 26.67,
+ 71.45,
+ ]
+
+ for i in range(len(expected_elevations)):
+ e = sat_utils.calc_elevation(
+ earth_station_coords[i][0],
+ space_station_coords[i][0],
+ earth_station_coords[i][1],
+ space_station_coords[i][1],
+ space_station_alts_km[i],
+ )
+ npt.assert_almost_equal(e, expected_elevations[i], 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_scintillation.py b/tests/test_scintillation.py
index cf7566c8a..6d54681df 100644
--- a/tests/test_scintillation.py
+++ b/tests/test_scintillation.py
@@ -22,16 +22,22 @@ def test_tropo_scintillation_attenuation(self):
frequency_MHz = 30000.
wet_refractivity = 42.5
- elevation_vec = np.array([5., 10., 20., 90., 35., 5., 10., 20., 35., 90.])
- percentage_gain_exceeded = np.array([.01, .1, 1, 3, 10, 90, 98, 99, 99.9, 99.99])
+ elevation_vec = np.array(
+ [5., 10., 20., 90., 35., 5., 10., 20., 35., 90.],
+ )
+ percentage_gain_exceeded = np.array(
+ [.01, .1, 1, 3, 10, 90, 98, 99, 99.9, 99.99],
+ )
attenuation_lower = [5, 1, .5, .1, .1, 1, 1, .5, .4, .3]
attenuation_upper = [7, 2, .6, .2, .2, 2, 2, .7, .6, .5]
sign = [-1, -1, -1, -1, -1, +1, +1, +1, +1, +1]
- attenuation = self.scintillation.get_tropospheric_attenuation(elevation=elevation_vec,
- frequency_MHz=frequency_MHz,
- antenna_gain_dB=antenna_gain,
- time_ratio=percentage_gain_exceeded / 100,
- wet_refractivity=wet_refractivity)
+ attenuation = self.scintillation.get_tropospheric_attenuation(
+ elevation=elevation_vec,
+ frequency_MHz=frequency_MHz,
+ antenna_gain_dB=antenna_gain,
+ time_ratio=percentage_gain_exceeded / 100,
+ wet_refractivity=wet_refractivity,
+ )
npt.assert_array_less(attenuation_lower, np.abs(attenuation))
npt.assert_array_less(np.abs(attenuation), attenuation_upper)
diff --git a/tests/test_simulation_downlink.py b/tests/test_simulation_downlink.py
index b590216ee..41d7fcf60 100644
--- a/tests/test_simulation_downlink.py
+++ b/tests/test_simulation_downlink.py
@@ -15,6 +15,9 @@
from sharc.antenna.antenna_omni import AntennaOmni
from sharc.station_factory import StationFactory
from sharc.propagation.propagation_factory import PropagationFactory
+from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology
+from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS
+
class SimulationDownlinkTest(unittest.TestCase):
@@ -27,88 +30,94 @@ def setUp(self):
self.param.general.enable_adjacent_channel = False
self.param.general.overwrite_output = True
- self.param.imt.topology = "SINGLE_BS"
- self.param.imt.wrap_around = False
- self.param.imt.num_clusters = 2
- self.param.imt.intersite_distance = 150
+ self.param.imt.topology = ParametersImtTopology(
+ type="SINGLE_BS",
+ single_bs=ParametersSingleBS(
+ num_clusters=2,
+ intersite_distance=150,
+ cell_radius=2 * 150 / 3,
+ ),
+ )
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
- self.param.imt.frequency = 10000
+ self.param.imt.frequency = 10000.0
self.param.imt.bandwidth = 100
self.param.imt.rb_bandwidth = 0.180
self.param.imt.spectral_mask = "IMT-2020"
self.param.imt.spurious_emissions = -13
self.param.imt.guard_band_ratio = 0.1
self.param.imt.ho_margin = 3
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 10
- self.param.imt.bs_height = 6
- self.param.imt.bs_acs = 30
- self.param.imt.bs_noise_figure = 7
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 2
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 0
- self.param.imt.ue_distribution_distance = "RAYLEIGH"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 0.8
- self.param.imt.ue_p_cmax = 20
- self.param.imt.ue_conducted_power = 10
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_acs = 25
- self.param.imt.ue_noise_figure = 9
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 10
+ self.param.imt.bs.height = 6
+ self.param.imt.bs.acs = 30
+ self.param.imt.bs.noise_figure = 7
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 2
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 0
+ self.param.imt.ue.distribution_distance = "RAYLEIGH"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 0.8
+ self.param.imt.ue.p_cmax = 20
+ self.param.imt.ue.conducted_power = 10
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.acs = 25
+ self.param.imt.ue.noise_figure = 9
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
- self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL)
+ # probability of line-of-sight (not for FSPL)
+ self.param.imt.line_of_sight_prob = 0.75
self.param.imt.shadowing = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.bs_element_pattern = "M2101"
- self.param.antenna_imt.bs_normalization_file = None
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_element_max_g = 10
- self.param.antenna_imt.bs_element_phi_3db = 80
- self.param.antenna_imt.bs_element_theta_3db = 80
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 16
- self.param.antenna_imt.bs_n_columns = 16
- self.param.antenna_imt.bs_element_horiz_spacing = 1
- self.param.antenna_imt.bs_element_vert_spacing = 1
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 10
-
- self.param.antenna_imt.ue_element_pattern = "M2101"
- self.param.antenna_imt.ue_normalization = False
- self.param.antenna_imt.ue_normalization_file = None
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_element_max_g = 5
- self.param.antenna_imt.ue_element_phi_3db = 65
- self.param.antenna_imt.ue_element_theta_3db = 65
- self.param.antenna_imt.ue_element_am = 30
- self.param.antenna_imt.ue_element_sla_v = 30
- self.param.antenna_imt.ue_n_rows = 2
- self.param.antenna_imt.ue_n_columns = 1
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
-
- self.param.fss_ss.frequency = 10000
+
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.bs.antenna.array.element_pattern = "M2101"
+ self.param.imt.bs.antenna.array.normalization_file = None
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.element_max_g = 10
+ self.param.imt.bs.antenna.array.element_phi_3db = 80
+ self.param.imt.bs.antenna.array.element_theta_3db = 80
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 16
+ self.param.imt.bs.antenna.array.n_columns = 16
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 1
+ self.param.imt.bs.antenna.array.element_vert_spacing = 1
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 10
+
+ self.param.imt.ue.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.array.element_pattern = "M2101"
+ self.param.imt.ue.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.normalization_file = None
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.element_max_g = 5
+ self.param.imt.ue.antenna.array.element_phi_3db = 65
+ self.param.imt.ue.antenna.array.element_theta_3db = 65
+ self.param.imt.ue.antenna.array.element_am = 30
+ self.param.imt.ue.antenna.array.element_sla_v = 30
+ self.param.imt.ue.antenna.array.n_rows = 2
+ self.param.imt.ue.antenna.array.n_columns = 1
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
+
+ self.param.fss_ss.frequency = 10000.0
self.param.fss_ss.bandwidth = 100
self.param.fss_ss.acs = 0
self.param.fss_ss.altitude = 35786000
@@ -121,7 +130,7 @@ def setUp(self):
self.param.fss_ss.antenna_pattern = "OMNI"
self.param.fss_ss.imt_altitude = 1000
self.param.fss_ss.imt_lat_deg = -23.5629739
- self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75)
+ self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75)
self.param.fss_ss.channel_model = "FSPL"
self.param.fss_ss.line_of_sight_prob = 0.01
self.param.fss_ss.surf_water_vapour_density = 7.5
@@ -129,8 +138,6 @@ def setUp(self):
self.param.fss_ss.time_ratio = 0.5
self.param.fss_ss.antenna_l_s = -20
self.param.fss_ss.acs = 0
- self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_ss.EARTH_RADIUS = 6371000
self.param.fss_es.x = -5000
self.param.fss_es.y = 0
@@ -139,7 +146,7 @@ def setUp(self):
self.param.fss_es.elevation_min = 20
self.param.fss_es.elevation_max = 20
self.param.fss_es.azimuth = "0"
- self.param.fss_es.frequency = 10000
+ self.param.fss_es.frequency = 10000.0
self.param.fss_es.bandwidth = 100
self.param.fss_es.noise_temperature = 100
self.param.fss_es.tx_power_density = -60
@@ -148,29 +155,25 @@ def setUp(self):
self.param.fss_es.channel_model = "FSPL"
self.param.fss_es.line_of_sight_prob = 1
self.param.fss_es.acs = 0
- self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_es.EARTH_RADIUS = 6371000
-
- self.param.ras.x = -5000
- self.param.ras.y = 0
- self.param.ras.height = 10
- self.param.ras.elevation = 20
- self.param.ras.azimuth = 0
- self.param.ras.frequency = 10000
+
+ self.param.ras.geometry.location.type = "FIXED"
+ self.param.ras.geometry.location.x = -5000
+ self.param.ras.geometry.location.y = 0
+ self.param.ras.geometry.height = 10
+ self.param.ras.geometry.elevation.type = "FIXED"
+ self.param.ras.geometry.elevation.fixed = 20
+ self.param.ras.geometry.azimuth.fixed = 0
+ self.param.ras.geometry.azimuth.type = "FIXED"
+ self.param.ras.frequency = 10000.0
self.param.ras.bandwidth = 100
- self.param.ras.antenna_noise_temperature = 50
- self.param.ras.receiver_noise_temperature = 50
- self.param.ras.antenna_gain = 50
+ self.param.ras.noise_temperature = 100
+ self.param.ras.antenna.gain = 50
self.param.ras.antenna_efficiency = 0.7
- self.param.ras.diameter = 10
self.param.ras.acs = 0
- self.param.ras.antenna_pattern = "OMNI"
+ self.param.ras.antenna.pattern = "OMNI"
self.param.ras.channel_model = "FSPL"
self.param.ras.line_of_sight_prob = 1
- self.param.ras.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.ras.EARTH_RADIUS = 6371000
- self.param.ras.SPEED_OF_LIGHT = 299792458
-
+ self.param.ras.tx_power_density = -500
def test_simulation_2bs_4ue_fss_ss(self):
self.param.general.system = "FSS_SS"
@@ -185,134 +188,202 @@ def test_simulation_2bs_4ue_fss_ss(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
# test connection method
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
- self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]})
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
# We do not test the selection method here because in this specific
# scenario we do not want to change the order of the UE's
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
- path_loss_imt = np.array([[78.47, 89.35, 93.27, 97.05],
- [97.55, 94.72, 91.53, 81.99]])
- bs_antenna_gains = np.array([[ 1, 1, 1, 1], [ 2, 2, 2, 2]])
- ue_antenna_gains = np.array([[ 10, 11, 22, 23], [ 10, 11, 22, 23]])
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
+ path_loss_imt = np.array([
+ [78.68, 89.36, 93.28, 97.06],
+ [97.55, 94.73, 91.54, 82.08],
+ ])
+ bs_antenna_gains = np.array([[1, 1, 1, 1], [2, 2, 2, 2]])
+ ue_antenna_gains = np.array([[10, 11, 22, 23], [10, 11, 22, 23]])
coupling_loss_imt = path_loss_imt - bs_antenna_gains - ue_antenna_gains \
- + self.param.imt.bs_ohmic_loss \
- + self.param.imt.ue_ohmic_loss \
- + self.param.imt.ue_body_loss
+ + self.param.imt.bs.ohmic_loss \
+ + self.param.imt.ue.ohmic_loss \
+ + self.param.imt.ue.body_loss
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- coupling_loss_imt,
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ coupling_loss_imt,
+ atol=1e-2,
+ )
# test scheduler and bandwidth allocation
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ npt.assert_allclose(
+ self.simulation.ue.bandwidth,
+ bandwidth_per_ue * np.ones(4), atol=1e-2,
+ )
# there is no power control, so BS's will transmit at maximum power
self.simulation.power_control()
- tx_power = 10 - 10*math.log10(2)
- npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2)
- npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2)
+ tx_power = 10 - 10 * math.log10(2)
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[0], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[1], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
# test method that calculates SINR
self.simulation.calculate_sinr()
# check UE received power
- rx_power = tx_power - np.concatenate((coupling_loss_imt[0][:2], coupling_loss_imt[1][2:]))
+ rx_power = tx_power - \
+ np.concatenate(
+ (coupling_loss_imt[0][:2], coupling_loss_imt[1][2:]),
+ )
npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2)
# check UE received interference
- rx_interference = tx_power - np.concatenate((coupling_loss_imt[1][:2], coupling_loss_imt[0][2:]))
- npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2)
+ rx_interference = tx_power - \
+ np.concatenate(
+ (coupling_loss_imt[1][:2], coupling_loss_imt[0][2:]),
+ )
+ npt.assert_allclose(
+ self.simulation.ue.rx_interference,
+ rx_interference, atol=1e-2,
+ )
# check UE thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
- npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9
+ npt.assert_allclose(
+ self.simulation.ue.thermal_noise,
+ thermal_noise, atol=1e-2,
+ )
# check UE thermal noise + interference
- total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise))
- npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2)
+ total_interference = 10 * \
+ np.log10(
+ np.power(10, 0.1 * rx_interference) +
+ np.power(10, 0.1 * thermal_noise),
+ )
+ npt.assert_allclose(
+ self.simulation.ue.total_interference, total_interference, atol=1e-2,
+ )
# check SNR
- npt.assert_allclose(self.simulation.ue.snr, rx_power - thermal_noise, atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.ue.snr,
+ rx_power - thermal_noise, atol=1e-2,
+ )
# check SINR
- npt.assert_allclose(self.simulation.ue.sinr, rx_power - total_interference, atol=1e-2)
-
- self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss)
- self.simulation.system.x = np.array([0.01]) # avoids zero-division
+ npt.assert_allclose(
+ self.simulation.ue.sinr,
+ rx_power - total_interference, atol=1e-2,
+ )
+
+ self.simulation.system = StationFactory.generate_fss_space_station(
+ self.param.fss_ss,
+ )
+ self.simulation.system.x = np.array([0.01]) # avoids zero-division
self.simulation.system.y = np.array([0])
+ self.simulation.system.z = np.array([self.param.fss_ss.altitude])
self.simulation.system.height = np.array([self.param.fss_ss.altitude])
- # test the method that calculates interference from IMT UE to FSS space station
+ # test the method that calculates interference from IMT UE to FSS space
+ # station
self.simulation.calculate_external_interference()
# check coupling loss
- # 4 values because we have 2 BS * 2 beams for each base station.
+ # 4 values because we have 2 BS * 2 beams for each base station.
path_loss_imt_system = 203.52
polarization_loss = 3
sat_antenna_gain = 51
bs_antenna_gain = np.array([1, 2])
coupling_loss_imt_system = path_loss_imt_system - sat_antenna_gain \
- - np.array([bs_antenna_gain[0], bs_antenna_gain[0], bs_antenna_gain[1], bs_antenna_gain[1]]) \
- + polarization_loss \
- + self.param.imt.bs_ohmic_loss
-
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ - np.array([bs_antenna_gain[0], bs_antenna_gain[0], bs_antenna_gain[1], bs_antenna_gain[1]]) \
+ + polarization_loss \
+ + self.param.imt.bs.ohmic_loss
+
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system.reshape((-1, 1)),
+ atol=1e-2,
+ )
# check interference generated by BS to FSS space station
interference = tx_power - coupling_loss_imt_system
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# check FSS space station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*950*1e3*100*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 950 * 1e3 * 100 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at FSS space station
# self.assertAlmostEqual(self.simulation.system.inr,
# np.array([ -147.448 - (-88.821) ]),
# delta=.01)
- self.assertAlmostEqual(self.simulation.system.inr,
- np.array([ rx_interference - thermal_noise ]),
- delta=.01)
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ np.array([rx_interference - thermal_noise]),
+ delta=.01,
+ )
def test_simulation_2bs_4ue_fss_es(self):
self.param.general.system = "FSS_ES"
@@ -320,125 +391,197 @@ def test_simulation_2bs_4ue_fss_es(self):
self.simulation = SimulationDownlink(self.param, "")
self.simulation.initialize()
-
self.simulation.bs_power_gain = 0
self.simulation.ue_power_gain = 0
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
self.simulation.scheduler()
self.simulation.power_control()
self.simulation.calculate_sinr()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
- tx_power = 10 - 10*math.log10(2)
- npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2)
- npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2)
+ tx_power = 10 - 10 * math.log10(2)
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[0], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[1], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
# check UE received power
- rx_power = np.array([tx_power-3-(78.47-1-10)-4-3, tx_power-3-(89.35-1-11)-4-3, tx_power-3-(91.53-2-22)-4-3, tx_power-3-(81.99-2-23)-4-3])
+ path_loss_imt = np.array([78.68, 89.37, 91.54, 82.09])
+ rx_power = np.array([
+ tx_power - 3 + 1 + 10 - 4 - 3, tx_power - 3 + 1 + 11 -
+ 4 - 3, tx_power - 3 + 2 + 22 - 4 - 3, tx_power - 3 + 2 + 23 - 4 - 3,
+ ]) - path_loss_imt
npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2)
# check UE received interference
- rx_interference = np.array([tx_power-3-(97.55-2-10)-4-3, tx_power-3-(94.72-2-11)-4-3, tx_power-3-(93.27-1-22)-4-3, tx_power-3-(97.05-1-23)-4-3])
- npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2)
+ rx_interference = np.array([
+ tx_power - 3 - (97.55 - 2 - 10) - 4 - 3, tx_power - 3 - (
+ 94.73 - 2 - 11
+ ) - 4 - 3, tx_power - 3 - (93.28 - 1 - 22) - 4 - 3, tx_power - 3 - (97.06 - 1 - 23) - 4 - 3,
+ ])
+ npt.assert_allclose(
+ self.simulation.ue.rx_interference,
+ rx_interference, atol=1e-2,
+ )
# check UE thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
- npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9
+ npt.assert_allclose(
+ self.simulation.ue.thermal_noise,
+ thermal_noise, atol=1e-2,
+ )
# check UE thermal noise + interference
- total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise))
- npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2)
-
- self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen)
+ total_interference = 10 * \
+ np.log10(
+ np.power(10, 0.1 * rx_interference) +
+ np.power(10, 0.1 * thermal_noise),
+ )
+ npt.assert_allclose(
+ self.simulation.ue.total_interference, total_interference, atol=1e-2,
+ )
+
+ self.simulation.system = StationFactory.generate_fss_earth_station(
+ self.param.fss_es, random_number_gen,
+ )
self.simulation.system.x = np.array([-2000])
self.simulation.system.y = np.array([0])
self.simulation.system.height = np.array([self.param.fss_es.height])
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_es.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_es.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# what if FSS ES is the interferer?
self.simulation.calculate_sinr_ext()
# check coupling loss between FSS_ES and IMT_UE
- coupling_loss_imt_system = np.array([128.55-50-10, 128.76-50-11, 128.93-50-22, 129.17-50-23])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ coupling_loss_imt_system = np.array(
+ [128.55 - 50 - 10, 128.77 - 50 - 11, 128.93 - 50 - 22, 129.18 - 50 - 23],
+ ).reshape((-1, 1))
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
# check interference from FSS_ES to IMT_UE
- system_tx_power = -60 + 10*math.log10(bandwidth_per_ue*1e6) + 30
- ext_interference = system_tx_power - coupling_loss_imt_system
- npt.assert_allclose(self.simulation.ue.ext_interference,
- ext_interference,
- atol=1e-2)
-
- ext_interference_total = 10*np.log10(np.power(10, 0.1*total_interference) \
- + np.power(10, 0.1*ext_interference))
-
- npt.assert_allclose(self.simulation.ue.sinr_ext,
- rx_power - ext_interference_total,
- atol=1e-2)
-
- npt.assert_allclose(self.simulation.ue.inr,
- ext_interference - thermal_noise,
- atol=1e-2)
+ system_tx_power = -60 + 10 * math.log10(bandwidth_per_ue * 1e6) + 30
+ ext_interference = (system_tx_power - coupling_loss_imt_system).flatten()
+ npt.assert_allclose(
+ self.simulation.ue.ext_interference,
+ ext_interference,
+ atol=1e-2,
+ )
+
+ ext_interference_total = 10 * np.log10(
+ np.power(10, 0.1 * total_interference) +
+ np.power(10, 0.1 * ext_interference),
+ )
+
+ npt.assert_allclose(
+ self.simulation.ue.sinr_ext,
+ rx_power - ext_interference_total,
+ atol=1e-2,
+ )
+
+ npt.assert_allclose(
+ self.simulation.ue.inr,
+ ext_interference - thermal_noise,
+ atol=1e-2,
+ )
# what if IMT is interferer?
self.simulation.calculate_external_interference()
# check coupling loss from IMT_BS to FSS_ES
- coupling_loss_imt_system = np.array([124.47-50-1, 124.47-50-1, 125.29-50-2, 125.29-50-2])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ coupling_loss_imt_system = np.array(
+ [124.47 - 50 - 1, 124.47 - 50 - 1, 125.29 - 50 - 2, 125.29 - 50 - 2],
+ ).reshape((-1, 1))
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
interference = tx_power - coupling_loss_imt_system
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# check FSS Earth station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at FSS Earth station
- self.assertAlmostEqual(self.simulation.system.inr,
- np.array([ rx_interference - thermal_noise ]),
- delta=.01)
-
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ np.array([rx_interference - thermal_noise]),
+ delta=.01,
+ )
def test_simulation_2bs_4ue_ras(self):
self.param.general.system = "RAS"
@@ -446,192 +589,391 @@ def test_simulation_2bs_4ue_ras(self):
self.simulation = SimulationDownlink(self.param, "")
self.simulation.initialize()
-
self.simulation.bs_power_gain = 0
self.simulation.ue_power_gain = 0
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.ras.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.ras.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
self.simulation.scheduler()
self.simulation.power_control()
self.simulation.calculate_sinr()
# check UE thermal noise
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
- npt.assert_allclose(self.simulation.ue.thermal_noise,
- thermal_noise,
- atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9
+ npt.assert_allclose(
+ self.simulation.ue.thermal_noise,
+ thermal_noise,
+ atol=1e-2,
+ )
# check SINR
- npt.assert_allclose(self.simulation.ue.sinr,
- np.array([-70.48 - (-85.49), -80.36 - (-83.19), -70.54 - (-73.15), -60.00 - (-75.82)]),
- atol=1e-2)
-
- self.simulation.system = StationFactory.generate_ras_station(self.param.ras)
+ npt.assert_allclose(
+ self.simulation.ue.sinr,
+ np.array(
+ [-70.70 - (-85.49), -80.37 - (-83.19), -70.55 - (-73.15), -60.10 - (-75.82)],
+ ),
+ atol=1e-2,
+ )
+
+ self.simulation.system = StationFactory.generate_ras_station(
+ self.param.ras, random_number_gen, topology=None,
+ )
self.simulation.system.x = np.array([-2000])
self.simulation.system.y = np.array([0])
- self.simulation.system.height = np.array([self.param.ras.height])
+ self.simulation.system.height = np.array([self.param.ras.geometry.height])
self.simulation.system.antenna[0].effective_area = 54.9779
# Test gain calculation
- gains = self.simulation.calculate_gains(self.simulation.system,self.simulation.bs)
- npt.assert_equal(gains,np.array([[50, 50]]))
+ gains = self.simulation.calculate_gains(
+ self.simulation.system, self.simulation.bs,
+ )
+ npt.assert_equal(gains, np.array([[50, 50]]))
self.simulation.calculate_external_interference()
polarization_loss = 3
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- np.array([118.47-50-1, 118.47-50-1, 119.29-50-2, 119.29-50-2]) + polarization_loss,
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ np.array([
+ 118.47 - 50 - 1, 118.47 - 50 - 1, 119.29 -
+ 50 - 2, 119.29 - 50 - 2,
+ ]).reshape((-1, 1)) + polarization_loss,
+ atol=1e-2,
+ )
# Test RAS interference
- interference = self.param.imt.bs_conducted_power - 10*np.log10(self.param.imt.ue_k) \
- - np.array([118.47-50-1, 118.47-50-1, 119.29-50-2, 119.29-50-2]) - polarization_loss
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ interference = self.param.imt.bs.conducted_power - 10 * np.log10(self.param.imt.ue.k) \
+ - np.array([
+ 118.47 - 50 - 1,
+ 118.47 - 50 - 1,
+ 119.29 - 50 - 2,
+ 119.29 - 50 - 2,
+ ]) - polarization_loss
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# Test RAS PFD
- pfd = 10*np.log10(10**(rx_interference/10)/54.9779)
- self.assertAlmostEqual(self.simulation.system.pfd,
- pfd,
- delta=.01)
+ pfd = 10 * np.log10(10**(rx_interference / 10) / 54.9779)
+ self.assertAlmostEqual(
+ self.simulation.system.pfd,
+ pfd,
+ delta=.01,
+ )
# check RAS station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at RAS station
- self.assertAlmostEqual(self.simulation.system.inr,
- np.array([ rx_interference - (-98.599) ]),
- delta=.01)
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ np.array([rx_interference - (-98.599)]),
+ delta=.01,
+ )
def test_calculate_bw_weights(self):
self.param.general.system = "FSS_ES"
self.simulation = SimulationDownlink(self.param, "")
+ ############################################################################
+ # Calculating bw co-channel weights for when system is at start of band
bw_imt = 200
bw_sys = 33.33
ue_k = 3
- ref_weights = np.array([ 0.5, 0, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([0.5, 0, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 100
bw_sys = 25
ue_k = 3
- ref_weights = np.array([ 0.75, 0, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([0.75, 0, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 66.67
ue_k = 3
- ref_weights = np.array([ 1, 0, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 0, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 400
bw_sys = 200
ue_k = 3
- ref_weights = np.array([ 1, 0.49, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 0.49, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 133.33
ue_k = 3
- ref_weights = np.array([ 1, 1, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 1, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 150
ue_k = 3
- ref_weights = np.array([ 1, 1, 0.25])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 1, 0.25])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 150
bw_sys = 150
ue_k = 3
- ref_weights = np.array([ 1, 1, 1])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 1, 1])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 300
ue_k = 3
- ref_weights = np.array([ 1, 1, 1])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 1, 1])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 50
ue_k = 2
- ref_weights = np.array([ 0.5, 0])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([0.5, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 100
bw_sys = 60
ue_k = 2
- ref_weights = np.array([ 1, 0.2])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 0.2])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 300
bw_sys = 300
ue_k = 2
- ref_weights = np.array([ 1, 1])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([1, 1])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 100
bw_sys = 50
ue_k = 1
- ref_weights = np.array([ 0.5 ])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([0.5])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
bw_imt = 200
bw_sys = 180
ue_k = 1
- ref_weights = np.array([ 0.9])
- weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k)
+ ref_weights = np.array([0.9])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2 + bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
+ npt.assert_allclose(ref_weights, weights, atol=1e-2)
+
+ ############################################################################
+ # Calculating weigths for overlap at the middle of band:
+ bw_imt = 200
+ bw_sys = 33.33
+ ue_k = 3
+ ref_weights = np.array([0, 0.5, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ # at middle of imt band
+ fc_sys = fc_imt - bw_sys / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
npt.assert_allclose(ref_weights, weights, atol=1e-2)
+ bw_imt = 200
+ bw_sys = 50
+ ue_k = 4
+ ref_weights = np.array([0, 0.5, 0.5, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ # at middle of imt band
+ fc_sys = fc_imt
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
+ npt.assert_allclose(ref_weights, weights, atol=1e-2)
+
+ bw_imt = 200
+ bw_sys = 50
+ ue_k = 4
+ ref_weights = np.array([0, 0.7, 0.3, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ # at middle - 10 of imt band
+ fc_sys = fc_imt - 10
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
+ npt.assert_allclose(ref_weights, weights, atol=1e-2)
+
+ ############################################################################
+ # Calculating co-channel weights for partial overlap
+
+ bw_imt = 200
+ bw_sys = 180
+ ue_k = 1
+ ref_weights = np.array([0.45])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ # half inside, half outside
+ fc_sys = fc_imt - bw_imt / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
+ npt.assert_allclose(ref_weights, weights, atol=1e-2)
+
+ bw_imt = 200
+ bw_sys = 150
+ ue_k = 3
+ ref_weights = np.array([1, 0.125, 0])
+
+ bw_ue = np.repeat(bw_imt / ue_k, ue_k)
+ fc_imt = 2200
+ fc_ue = np.linspace(fc_imt - bw_imt / 2 + bw_ue[0] / 2, fc_imt + bw_imt / 2 - bw_ue[0] / 2, ue_k)
+ fc_sys = fc_imt - bw_imt / 2
+
+ weights = self.simulation.calculate_bw_weights(bw_ue, fc_ue, bw_sys, fc_sys)
+ npt.assert_allclose(ref_weights, weights, atol=1e-2)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_simulation_downlink_haps.py b/tests/test_simulation_downlink_haps.py
index ae3cf381d..1bb566f32 100644
--- a/tests/test_simulation_downlink_haps.py
+++ b/tests/test_simulation_downlink_haps.py
@@ -15,6 +15,9 @@
from sharc.antenna.antenna_omni import AntennaOmni
from sharc.station_factory import StationFactory
from sharc.propagation.propagation_factory import PropagationFactory
+from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology
+from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS
+
class SimulationDownlinkHapsTest(unittest.TestCase):
@@ -27,88 +30,94 @@ def setUp(self):
self.param.general.enable_adjacent_channel = False
self.param.general.overwrite_output = True
- self.param.imt.topology = "SINGLE_BS"
- self.param.imt.wrap_around = False
- self.param.imt.num_clusters = 2
- self.param.imt.intersite_distance = 150
+ self.param.imt.topology = ParametersImtTopology(
+ type="SINGLE_BS",
+ single_bs=ParametersSingleBS(
+ num_clusters=2,
+ intersite_distance=150,
+ cell_radius=2 * 150 / 3,
+ ),
+ )
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
- self.param.imt.frequency = 10000
+ self.param.imt.frequency = 10000.0
self.param.imt.bandwidth = 100
self.param.imt.rb_bandwidth = 0.180
self.param.imt.spectral_mask = "IMT-2020"
self.param.imt.spurious_emissions = -13
self.param.imt.guard_band_ratio = 0.1
self.param.imt.ho_margin = 3
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 10
- self.param.imt.bs_height = 6
- self.param.imt.bs_acs = 30
- self.param.imt.bs_noise_figure = 7
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 2
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 0
- self.param.imt.ue_distribution_distance = "RAYLEIGH"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 0.8
- self.param.imt.ue_p_cmax = 20
- self.param.imt.ue_conducted_power = 10
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_aclr = 20
- self.param.imt.ue_acs = 25
- self.param.imt.ue_noise_figure = 9
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 10
+ self.param.imt.bs.height = 6
+ self.param.imt.bs.acs = 30
+ self.param.imt.bs.noise_figure = 7
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 2
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 0
+ self.param.imt.ue.distribution_distance = "RAYLEIGH"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 0.8
+ self.param.imt.ue.p_cmax = 20
+ self.param.imt.ue.conducted_power = 10
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.aclr = 20
+ self.param.imt.ue.acs = 25
+ self.param.imt.ue.noise_figure = 9
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
- self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL)
+ # probability of line-of-sight (not for FSPL)
+ self.param.imt.line_of_sight_prob = 0.75
self.param.imt.shadowing = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.bs_normalization_file = None
- self.param.antenna_imt.bs_element_pattern = "M2101"
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_element_max_g = 10
- self.param.antenna_imt.bs_element_phi_3db = 80
- self.param.antenna_imt.bs_element_theta_3db = 80
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 16
- self.param.antenna_imt.bs_n_columns = 16
- self.param.antenna_imt.bs_element_horiz_spacing = 1
- self.param.antenna_imt.bs_element_vert_spacing = 1
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 10
-
- self.param.antenna_imt.ue_normalization_file = None
- self.param.antenna_imt.ue_normalization = False
- self.param.antenna_imt.ue_element_pattern = "M2101"
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_element_max_g = 5
- self.param.antenna_imt.ue_element_phi_3db = 65
- self.param.antenna_imt.ue_element_theta_3db = 65
- self.param.antenna_imt.ue_element_am = 30
- self.param.antenna_imt.ue_element_sla_v = 30
- self.param.antenna_imt.ue_n_rows = 2
- self.param.antenna_imt.ue_n_columns = 1
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
-
+
+ self.param.imt.bs.antenna.type = "ARRAY"
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.bs.antenna.array.normalization_file = None
+ self.param.imt.bs.antenna.array.element_pattern = "M2101"
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.element_max_g = 10
+ self.param.imt.bs.antenna.array.element_phi_3db = 80
+ self.param.imt.bs.antenna.array.element_theta_3db = 80
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 16
+ self.param.imt.bs.antenna.array.n_columns = 16
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 1
+ self.param.imt.bs.antenna.array.element_vert_spacing = 1
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 10
+
+ self.param.imt.ue.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.array.normalization_file = None
+ self.param.imt.ue.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.element_pattern = "M2101"
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.element_max_g = 5
+ self.param.imt.ue.antenna.array.element_phi_3db = 65
+ self.param.imt.ue.antenna.array.element_theta_3db = 65
+ self.param.imt.ue.antenna.array.element_am = 30
+ self.param.imt.ue.antenna.array.element_sla_v = 30
+ self.param.imt.ue.antenna.array.n_rows = 2
+ self.param.imt.ue.antenna.array.n_columns = 1
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
+
self.param.haps.frequency = 10000
self.param.haps.bandwidth = 200
self.param.haps.altitude = 20000
@@ -117,7 +126,8 @@ def setUp(self):
self.param.haps.azimuth = 0
self.param.haps.eirp_density = 4.4
self.param.haps.antenna_gain = 28
- self.param.haps.tx_power_density = self.param.haps.eirp_density - self.param.haps.antenna_gain - 60
+ self.param.haps.tx_power_density = self.param.haps.eirp_density - \
+ self.param.haps.antenna_gain - 60
self.param.haps.antenna_pattern = "OMNI"
self.param.haps.imt_altitude = 0
self.param.haps.imt_lat_deg = 0
@@ -125,10 +135,6 @@ def setUp(self):
self.param.haps.season = "SUMMER"
self.param.haps.channel_model = "FSPL"
self.param.haps.antenna_l_n = -25
- self.param.haps.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.haps.EARTH_RADIUS = 6371000
-
-
def test_simulation_2bs_4ue_1haps(self):
"""
@@ -145,90 +151,155 @@ def test_simulation_2bs_4ue_1haps(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.haps.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.haps.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+ self.simulation.coupling_loss_imt = \
+ self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
self.simulation.scheduler()
self.simulation.power_control()
self.simulation.calculate_sinr()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
-
- tx_power = 10 - 10*math.log10(2)
- npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2)
- npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9
+
+ tx_power = 10 - 10 * math.log10(2)
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[0], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[1], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
# check UE received power
- rx_power = np.array([tx_power-3-(78.47-1-10)-4-3, tx_power-3-(89.35-1-11)-4-3, tx_power-3-(91.53-2-22)-4-3, tx_power-3-(81.99-2-23)-4-3])
+ rx_power = np.array([
+ tx_power - 3 - (78.68 - 1 - 10) - 4 - 3,
+ tx_power - 3 - (89.37 - 1 - 11) - 4 - 3,
+ tx_power - 3 - (91.54 - 2 - 22) - 4 - 3,
+ tx_power - 3 - (82.09 - 2 - 23) - 4 - 3,
+ ])
npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2)
# check UE received interference
- rx_interference = np.array([tx_power-3-(97.55-2-10)-4-3, tx_power-3-(94.72-2-11)-4-3, tx_power-3-(93.27-1-22)-4-3, tx_power-3-(97.05-1-23)-4-3])
- npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2)
+ rx_interference = np.array([
+ tx_power - 3 - (97.55 - 2 - 10) - 4 - 3,
+ tx_power - 3 - (94.73 - 2 - 11) - 4 - 3,
+ tx_power - 3 - (93.28 - 1 - 22) - 4 - 3,
+ tx_power - 3 - (97.07 - 1 - 23) - 4 - 3,
+ ])
+ npt.assert_allclose(
+ self.simulation.ue.rx_interference,
+ rx_interference,
+ atol=1e-2,
+ )
# check UE thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
- npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2)
+ ue_noise_fig = 9
+ self.simulation.ue.noise_figure = np.ones(self.simulation.ue.num_stations) * ue_noise_fig
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + ue_noise_fig
+ # the simulator adds noise figure to total noise
+ npt.assert_allclose(
+ self.simulation.ue.thermal_noise,
+ thermal_noise,
+ atol=1e-2,
+ )
# check UE thermal noise + interference
- total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise))
- npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2)
-
- self.simulation.system = StationFactory.generate_haps(self.param.haps, 0, random_number_gen)
+ total_interference = 10 * \
+ np.log10(np.power(10, 0.1 * rx_interference) + np.power(10, 0.1 * thermal_noise))
+ npt.assert_allclose(
+ self.simulation.ue.total_interference,
+ total_interference,
+ atol=1e-2,
+ )
+
+ self.simulation.system = StationFactory.generate_haps(
+ self.param.haps, 0, random_number_gen,
+ )
# now we evaluate interference from HAPS to IMT UE
self.simulation.calculate_sinr_ext()
# check coupling loss between FSS_ES and IMT_UE
- coupling_loss_imt_system = np.array([148.47-28-10, 148.47-28-11, 148.47-28-22, 148.47-28-23])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
-
- system_tx_power = (4.4 - 28 - 60) + 10*math.log10(bandwidth_per_ue*1e6) + 30
-
- ext_interference = system_tx_power - coupling_loss_imt_system
- npt.assert_allclose(self.simulation.ue.ext_interference,
- ext_interference,
- atol=1e-2)
-
- ext_interference_total = 10*np.log10(np.power(10, 0.1*total_interference) \
- + np.power(10, 0.1*ext_interference))
-
- npt.assert_allclose(self.simulation.ue.sinr_ext,
- rx_power - ext_interference_total,
- atol=1e-2)
-
- npt.assert_allclose(self.simulation.ue.inr,
- ext_interference - thermal_noise,
- atol=1e-2)
-
-
-
+ coupling_loss_imt_system = np.array(
+ [148.47 - 28 - 10, 148.47 - 28 - 11, 148.47 - 28 - 22, 148.47 - 28 - 23],
+ ).reshape((-1, 1))
+
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
+
+ system_tx_power = (4.4 - 28 - 60) + 10 * \
+ np.log10(bandwidth_per_ue * 1e6) + 30
+
+ ext_interference = (system_tx_power - coupling_loss_imt_system).flatten()
+ npt.assert_allclose(
+ self.simulation.ue.ext_interference,
+ ext_interference,
+ atol=1e-2,
+ )
+
+ ext_interference_total = 10 * np.log10(
+ np.power(10, 0.1 * total_interference) +
+ np.power(10, 0.1 * ext_interference),
+ )
+
+ npt.assert_allclose(
+ self.simulation.ue.sinr_ext,
+ rx_power - ext_interference_total,
+ atol=1e-2,
+ )
+
+ npt.assert_allclose(
+ self.simulation.ue.inr,
+ ext_interference - thermal_noise,
+ atol=1e-2,
+ )
if __name__ == '__main__':
diff --git a/tests/test_simulation_downlink_tvro.py b/tests/test_simulation_downlink_tvro.py
index 6b0485174..a9cc342ae 100644
--- a/tests/test_simulation_downlink_tvro.py
+++ b/tests/test_simulation_downlink_tvro.py
@@ -15,6 +15,7 @@
from sharc.station_factory import StationFactory
from sharc.propagation.propagation_factory import PropagationFactory
+
class SimulationDownlinkTvroTest(unittest.TestCase):
def setUp(self):
@@ -27,91 +28,93 @@ def setUp(self):
self.param.general.seed = 101
self.param.general.overwrite_output = True
- self.param.imt.topology = "SINGLE_BS"
- self.param.imt.wrap_around = False
- self.param.imt.num_clusters = 2
- self.param.imt.intersite_distance = 150
+ self.param.imt.topology.type = "SINGLE_BS"
+ self.param.imt.topology.single_bs.num_clusters = 2
+ self.param.imt.topology.single_bs.intersite_distance = 150
+ self.param.imt.topology.single_bs.cell_radius = 100
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
- self.param.imt.frequency = 3590
+ self.param.imt.frequency = 3590.0
self.param.imt.bandwidth = 20
self.param.imt.rb_bandwidth = 0.180
self.param.imt.spectral_mask = "3GPP E-UTRA"
self.param.imt.spurious_emissions = -13
self.param.imt.guard_band_ratio = 0.1
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 46
- self.param.imt.bs_height = 20
- self.param.imt.bs_noise_figure = 5
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 2
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 0
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_distribution_distance = "UNIFORM"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 1
- self.param.imt.ue_p_cmax = 23
- self.param.imt.ue_power_dynamic_range = 63
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_acs = 25
- self.param.imt.ue_noise_figure = 9
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 46
+ self.param.imt.bs.height = 20
+ self.param.imt.bs.noise_figure = 5
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 2
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 0
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.distribution_distance = "UNIFORM"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 1
+ self.param.imt.ue.p_cmax = 23
+ self.param.imt.ue.power_dynamic_range = 63
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.acs = 25
+ self.param.imt.ue.noise_figure = 9
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
self.param.imt.los_adjustment_factor = 29
- self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL)
+ # probability of line-of-sight (not for FSPL)
+ self.param.imt.line_of_sight_prob = 0.75
self.param.imt.shadowing = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "BEAMFORMING"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.bs_element_pattern = "F1336"
- self.param.antenna_imt.bs_normalization_file = None
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_element_max_g = 18
- self.param.antenna_imt.bs_element_phi_3db = 65
- self.param.antenna_imt.bs_element_theta_3db = 0
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 1
- self.param.antenna_imt.bs_n_columns = 1
- self.param.antenna_imt.bs_element_horiz_spacing = 1
- self.param.antenna_imt.bs_element_vert_spacing = 1
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 10
-
- self.param.antenna_imt.ue_element_pattern = "FIXED"
- self.param.antenna_imt.ue_normalization = False
- self.param.antenna_imt.ue_normalization_file = None
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_element_max_g = -4
- self.param.antenna_imt.ue_element_phi_3db = 0
- self.param.antenna_imt.ue_element_theta_3db = 0
- self.param.antenna_imt.ue_element_am = 0
- self.param.antenna_imt.ue_element_sla_v = 0
- self.param.antenna_imt.ue_n_rows = 1
- self.param.antenna_imt.ue_n_columns = 1
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
+
+ self.param.imt.bs.antenna.type = "ARRAY"
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "BEAMFORMING"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "BEAMFORMING"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.bs.antenna.array.element_pattern = "F1336"
+ self.param.imt.bs.antenna.array.normalization_file = None
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.element_max_g = 18
+ self.param.imt.bs.antenna.array.element_phi_3db = 65
+ self.param.imt.bs.antenna.array.element_theta_3db = 0
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 1
+ self.param.imt.bs.antenna.array.n_columns = 1
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 1
+ self.param.imt.bs.antenna.array.element_vert_spacing = 1
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 10
+
+ self.param.imt.ue.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.array.element_pattern = "FIXED"
+ self.param.imt.ue.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.normalization_file = None
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.element_max_g = -4
+ self.param.imt.ue.antenna.array.element_phi_3db = 0
+ self.param.imt.ue.antenna.array.element_theta_3db = 0
+ self.param.imt.ue.antenna.array.element_am = 0
+ self.param.imt.ue.antenna.array.element_sla_v = 0
+ self.param.imt.ue.antenna.array.n_rows = 1
+ self.param.imt.ue.antenna.array.n_columns = 1
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
self.param.fss_es.location = "FIXED"
self.param.fss_es.x = 100
self.param.fss_es.y = 0
self.param.fss_es.min_dist_to_bs = 10
- self.param.fss_es.max_dist_to_bs = 600
+ self.param.fss_es.max_dist_to_bs = 600
self.param.fss_es.height = 6
self.param.fss_es.elevation_min = 49.8
self.param.fss_es.elevation_max = 49.8
@@ -127,9 +130,6 @@ def setUp(self):
self.param.fss_es.antenna_envelope_gain = 0
self.param.fss_es.channel_model = "FSPL"
self.param.fss_es.line_of_sight_prob = 1
- self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_es.EARTH_RADIUS = 6371000
-
def test_simulation_1bs_1ue_tvro(self):
self.param.general.system = "FSS_ES"
@@ -137,120 +137,181 @@ def test_simulation_1bs_1ue_tvro(self):
self.simulation = SimulationDownlink(self.param, "")
self.simulation.initialize()
random_number_gen = np.random.RandomState(self.param.general.seed)
-
+
self.assertTrue(self.simulation.co_channel)
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.x = np.array([0, -200])
self.simulation.bs.y = np.array([0, 0])
self.simulation.bs.azimuth = np.array([0, 180])
self.simulation.bs.elevation = np.array([-10, -10])
-
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([30, 60, -220, -300])
self.simulation.ue.y = np.array([0, 0, 0, 0])
# test connection method
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1], 1:[2,3]}
- self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]})
-
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_es.channel_model,
- self.param, random_number_gen)
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
+
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_es.channel_model,
+ self.simulation.param_system,
+ self.param, random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
- path_loss_imt = np.array([[73.09, 79.11, 90.40, 93.09],
- [90.78, 91.85, 69.57, 83.55]])
- bs_antenna_gains = np.array([[ 3.04, 7.30, -6.45, -6.45],
- [ -6.45, -6.45, 1.63, 17.95]])
- ue_antenna_gains = np.array([[ -4, -4, -4, -4], [ -4, -4, -4, -4]])
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
+ path_loss_imt = np.array([
+ [74.49, 79.50, 90.43, 93.11],
+ [90.81, 91.87, 72.25, 83.69],
+ ])
+ bs_antenna_gains = np.array([
+ [3.04, 7.30, -6.45, -6.45],
+ [-6.45, -6.45, 1.63, 17.95],
+ ])
+ ue_antenna_gains = np.array([[-4, -4, -4, -4], [-4, -4, -4, -4]])
coupling_loss_imt = path_loss_imt - bs_antenna_gains - ue_antenna_gains \
- + self.param.imt.bs_ohmic_loss \
- + self.param.imt.ue_ohmic_loss \
- + self.param.imt.ue_body_loss
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- coupling_loss_imt,
- atol=1e-1)
+ + self.param.imt.bs.ohmic_loss \
+ + self.param.imt.ue.ohmic_loss \
+ + self.param.imt.ue.body_loss
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ coupling_loss_imt,
+ atol=1e-1,
+ )
# test scheduler and bandwidth allocation
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*20/2)
- npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2)
-
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 20 / 2)
+ npt.assert_allclose(
+ self.simulation.ue.bandwidth,
+ bandwidth_per_ue * np.ones(4), atol=1e-2,
+ )
+
# there is no power control, so BS's will transmit at maximum power
self.simulation.power_control()
- tx_power = 46 - 10*np.log10(2)
- npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2)
- npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2)
+ tx_power = 46 - 10 * np.log10(2)
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[0], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.tx_power[1], np.array(
+ [tx_power, tx_power],
+ ), atol=1e-2,
+ )
# test method that calculates SINR
self.simulation.calculate_sinr()
-
+
# check UE received power
- rx_power = tx_power - np.concatenate((coupling_loss_imt[0][:2], coupling_loss_imt[1][2:]))
+ rx_power = tx_power - \
+ np.concatenate(
+ (coupling_loss_imt[0][:2], coupling_loss_imt[1][2:]),
+ )
npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-1)
# check UE received interference
- rx_interference = tx_power - np.concatenate((coupling_loss_imt[1][:2], coupling_loss_imt[0][2:]))
- npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-1)
+ rx_interference = tx_power - \
+ np.concatenate(
+ (coupling_loss_imt[1][:2], coupling_loss_imt[0][2:]),
+ )
+ npt.assert_allclose(
+ self.simulation.ue.rx_interference,
+ rx_interference, atol=1e-1,
+ )
# check UE thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9
- npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-1)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9
+ npt.assert_allclose(
+ self.simulation.ue.thermal_noise,
+ thermal_noise, atol=1e-1,
+ )
# check UE thermal noise + interference
- total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise))
- npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-1)
+ total_interference = 10 * \
+ np.log10(
+ np.power(10, 0.1 * rx_interference) +
+ np.power(10, 0.1 * thermal_noise),
+ )
+ npt.assert_allclose(
+ self.simulation.ue.total_interference, total_interference, atol=1e-1,
+ )
# check SNR
- npt.assert_allclose(self.simulation.ue.snr, rx_power - thermal_noise, atol=1e-1)
+ npt.assert_allclose(
+ self.simulation.ue.snr,
+ rx_power - thermal_noise, atol=1e-1,
+ )
# check SINR
- npt.assert_allclose(self.simulation.ue.sinr, rx_power - total_interference, atol=1e-1)
+ npt.assert_allclose(
+ self.simulation.ue.sinr,
+ rx_power - total_interference, atol=1e-1,
+ )
#######################################################################
- self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen)
+ self.simulation.system = StationFactory.generate_fss_earth_station(
+ self.param.fss_es, random_number_gen,
+ )
self.simulation.system.x = np.array([600])
self.simulation.system.y = np.array([0])
-
- # test the method that calculates interference from IMT UE to FSS space station
- self.simulation.calculate_external_interference()
-
+
+ # test the method that calculates interference from IMT UE to FSS space
+ # station
+ self.simulation.calculate_external_interference()
+
# check coupling loss from IMT_BS to FSS_ES
- # 4 values because we have 2 BS * 2 beams for each base station.
- path_loss_imt_system = np.array([99.11, 99.11, 101.61, 101.61])
+ # 4 values because we have 2 BS * 2 beams for each base station.
+ path_loss_imt_system = np.array([99.11, 99.11, 101.61, 101.61])
polarization_loss = 3
- tvro_antenna_gain = np.array([ 0, 0, 0, 0])
- bs_antenna_gain = np.array([6.47, 6.47, -6.45, -6.45])
+ tvro_antenna_gain = np.array([0, 0, 0, 0])
+ bs_antenna_gain = np.array([6.47, 6.47, -6.45, -6.45])
coupling_loss_imt_system = path_loss_imt_system - tvro_antenna_gain \
- - bs_antenna_gain \
- + polarization_loss \
- + self.param.imt.bs_ohmic_loss
-
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-1)
-
+ - bs_antenna_gain \
+ + polarization_loss \
+ + self.param.imt.bs.ohmic_loss
+
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system.reshape(-1, 1),
+ atol=1e-1,
+ )
+
# check blocking signal
interference = tx_power - coupling_loss_imt_system
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
-
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_simulation_indoor.py b/tests/test_simulation_indoor.py
index fc38f16da..cc71b3fd6 100644
--- a/tests/test_simulation_indoor.py
+++ b/tests/test_simulation_indoor.py
@@ -15,7 +15,9 @@
from sharc.parameters.parameters import Parameters
from sharc.antenna.antenna_omni import AntennaOmni
from sharc.station_factory import StationFactory
-from sharc.propagation.propagation_factory import PropagationFactory
+from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology
+from sharc.parameters.imt.parameters_indoor import ParametersIndoor
+
class SimulationIndoorTest(unittest.TestCase):
@@ -28,9 +30,7 @@ def setUp(self):
self.param.general.enable_adjacent_channel = False
self.param.general.overwrite_output = True
- self.param.imt.topology = "INDOOR"
- self.param.imt.num_clusters = 1
- self.param.imt.intersite_distance = 339
+ self.param.imt.topology.type = "INDOOR"
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
self.param.imt.frequency = 40000
@@ -39,81 +39,85 @@ def setUp(self):
self.param.imt.spectral_mask = "IMT-2020"
self.param.imt.spurious_emissions = -13
self.param.imt.guard_band_ratio = 0.1
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 2
- self.param.imt.bs_height = 3
- self.param.imt.bs_noise_figure = 12
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 1
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 95
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_distribution_distance = "RAYLEIGH"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 1
- self.param.imt.ue_p_cmax = 22
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_noise_figure = 12
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 2
+ self.param.imt.bs.height = 3
+ self.param.imt.bs.noise_figure = 12
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 1
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 95
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.distribution_distance = "RAYLEIGH"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 1
+ self.param.imt.ue.p_cmax = 22
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.noise_figure = 12
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
self.param.imt.shadowing = False
- self.param.imt.wrap_around = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.bs_normalization_file = path.join('..','sharc','antenna','beamforming_normalization','bs_indoor_norm.npz')
- self.param.antenna_imt.ue_normalization_file = path.join('..','sharc','antenna','beamforming_normalization','ue_norm.npz')
- self.param.antenna_imt.bs_element_pattern = "M2101"
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_element_max_g = 5
- self.param.antenna_imt.bs_element_phi_3db = 90
- self.param.antenna_imt.bs_element_theta_3db = 90
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 8
- self.param.antenna_imt.bs_n_columns = 16
- self.param.antenna_imt.bs_element_horiz_spacing = 0.5
- self.param.antenna_imt.bs_element_vert_spacing = 0.5
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 90
-
- self.param.antenna_imt.ue_element_pattern = "M2101"
- self.param.antenna_imt.ue_normalization = False
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_element_max_g = 5
- self.param.antenna_imt.ue_element_phi_3db = 90
- self.param.antenna_imt.ue_element_theta_3db = 90
- self.param.antenna_imt.ue_element_am = 25
- self.param.antenna_imt.ue_element_sla_v = 25
- self.param.antenna_imt.ue_n_rows = 4
- self.param.antenna_imt.ue_n_columns = 4
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
-
- self.param.indoor.basic_path_loss = "FSPL"
- self.param.indoor.n_rows = 1
- self.param.indoor.n_colums = 1
- self.param.indoor.num_imt_buildings = 'ALL'
- self.param.indoor.street_width = 30
- self.param.indoor.ue_indoor_percent = 0.95
- self.param.indoor.building_class = "TRADITIONAL"
- self.param.indoor.intersite_distance = 30
- self.param.indoor.num_cells = 4
- self.param.indoor.num_floors = 1
+
+ self.param.imt.bs.antenna.type = "ARRAY"
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.bs.antenna.array.normalization_file = path.join(
+ '..', 'sharc', 'antenna', 'beamforming_normalization', 'bs_indoor_norm.npz',
+ )
+ self.param.imt.ue.antenna.array.normalization_file = path.join(
+ '..', 'sharc', 'antenna', 'beamforming_normalization', 'ue_norm.npz',
+ )
+ self.param.imt.bs.antenna.array.element_pattern = "M2101"
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.element_max_g = 5
+ self.param.imt.bs.antenna.array.element_phi_3db = 90
+ self.param.imt.bs.antenna.array.element_theta_3db = 90
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 8
+ self.param.imt.bs.antenna.array.n_columns = 16
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.bs.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 90
+
+ self.param.imt.ue.antenna.type = "ARRAY"
+ self.param.imt.ue.antenna.array.element_pattern = "M2101"
+ self.param.imt.ue.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.element_max_g = 5
+ self.param.imt.ue.antenna.array.element_phi_3db = 90
+ self.param.imt.ue.antenna.array.element_theta_3db = 90
+ self.param.imt.ue.antenna.array.element_am = 25
+ self.param.imt.ue.antenna.array.element_sla_v = 25
+ self.param.imt.ue.antenna.array.n_rows = 4
+ self.param.imt.ue.antenna.array.n_columns = 4
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
+
+ self.param.imt.topology.indoor.basic_path_loss = "FSPL"
+ self.param.imt.topology.indoor.n_rows = 1
+ self.param.imt.topology.indoor.n_colums = 1
+ self.param.imt.topology.indoor.num_imt_buildings = 'ALL'
+ self.param.imt.topology.indoor.street_width = 30
+ self.param.imt.topology.indoor.ue_indoor_percent = 0.95
+ self.param.imt.topology.indoor.building_class = "TRADITIONAL"
+ self.param.imt.topology.indoor.intersite_distance = 30
+ self.param.imt.topology.indoor.num_cells = 4
+ self.param.imt.topology.indoor.num_floors = 1
self.param.fss_es.x = 135
self.param.fss_es.y = 65
@@ -132,9 +136,6 @@ def setUp(self):
self.param.fss_es.line_of_sight_prob = 1
self.param.fss_es.adjacent_ch_selectivity = 0
self.param.fss_es.diameter = 0.74
- self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_es.EARTH_RADIUS = 6371000
-
def test_simulation_fss_es(self):
# Initialize stations
@@ -145,95 +146,143 @@ def test_simulation_fss_es(self):
random_number_gen = np.random.RandomState(101)
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.assertTrue(np.all(self.simulation.bs.active))
-
- self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es,
- random_number_gen)
-
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
-
+
+ self.simulation.system = StationFactory.generate_fss_earth_station(
+ self.param.fss_es,
+ random_number_gen,
+ )
+
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
+
# print("Random position:")
# self.simulation.plot_scenario()
- self.simulation.ue.x = np.array([0.0, 45.0, 75.0,120.0])
- self.simulation.ue.y = np.array([0.0, 50.0, 0.0, 50.0])
+ self.simulation.ue.x = np.array([0.0, 45.0, 75.0, 120.0])
+ self.simulation.ue.y = np.array([0.0, 50.0, 0.0, 50.0])
+ self.simulation.ue.z = np.ones_like(self.simulation.ue.x) * self.param.imt.ue.height
# print("Forced position:")
# self.simulation.plot_scenario()
-
+
# Connect and select UEs
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
self.assertTrue(np.all(self.simulation.ue.active))
- self.assertDictEqual(self.simulation.link,{0:[0],1:[1],2:[2],3:[3]})
-
+ self.assertDictEqual(
+ self.simulation.link, {
+ 0: [0], 1: [1], 2: [2], 3: [3],
+ },
+ )
+
# Test BS-to-UE angles in the IMT coord system
- expected_azi = np.array([[-120.96, 39.80, -22.62, 13.39],
- [-150.95, 90.00, -39.81, 18.43],
- [-161.57, 140.19, -90.00, 29.06],
- [-166.61, 157.38,-140.19, 59.03]])
- npt.assert_allclose(self.simulation.bs_to_ue_phi,
- expected_azi,
- atol=1e-2)
- expected_ele = np.array([[92.95, 92.20, 91.32, 90.79],
- [91.67, 93.43, 92.20, 91.09],
- [91.09, 92.20, 93.43, 91.67],
- [90.79, 91.32, 92.20, 92.95]])
- npt.assert_allclose(self.simulation.bs_to_ue_theta,
- expected_ele,
- atol=1e-2)
-
+ expected_azi = np.array([
+ [-120.96, 39.80, -22.62, 13.39],
+ [-150.95, 90.00, -39.81, 18.43],
+ [-161.57, 140.19, -90.00, 29.06],
+ [-166.61, 157.38, -140.19, 59.03],
+ ])
+ npt.assert_allclose(
+ self.simulation.bs_to_ue_phi,
+ expected_azi,
+ atol=1e-2,
+ )
+ expected_ele = np.array([
+ [92.95, 92.20, 91.32, 90.79],
+ [91.67, 93.43, 92.20, 91.09],
+ [91.09, 92.20, 93.43, 91.67],
+ [90.79, 91.32, 92.20, 92.95],
+ ])
+ npt.assert_allclose(
+ self.simulation.bs_to_ue_theta,
+ expected_ele,
+ atol=1e-2,
+ )
+
# Test BS-to-UE angles in the local coord system
- expected_loc = [(np.array([-86.57]),np.array([120.92])),
- (np.array([ 86.57]),np.array([ 90.00])),
- (np.array([-86.57]),np.array([ 90.00])),
- (np.array([ 86.57]),np.array([ 59.08]))]
- expected_beam = [(-86.57,30.92),
- ( 86.57, 0.00),
- (-86.57, 0.00),
- ( 86.57,-30.92)]
+ expected_loc = [
+ (np.array([-86.57]), np.array([120.92])),
+ (np.array([86.57]), np.array([90.00])),
+ (np.array([-86.57]), np.array([90.00])),
+ (np.array([86.57]), np.array([59.08])),
+ ]
+ expected_beam = [
+ (-86.57, 30.92),
+ (86.57, 0.00),
+ (-86.57, 0.00),
+ (86.57, -30.92),
+ ]
for k in range(self.simulation.bs.num_stations):
-
- self.assertEqual(self.simulation.bs.antenna[k].azimuth,0.0)
- self.assertEqual(self.simulation.bs.antenna[k].elevation,-90.0)
-
- lo_angles = self.simulation.bs.antenna[k].to_local_coord(expected_azi[k,k],
- expected_ele[k,k])
- npt.assert_array_almost_equal(lo_angles,expected_loc[k],decimal=2)
- npt.assert_array_almost_equal(self.simulation.bs.antenna[k].beams_list[0],
- expected_beam[k],decimal=2)
-
+
+ self.assertEqual(self.simulation.bs.antenna[k].azimuth, 0.0)
+ self.assertEqual(self.simulation.bs.antenna[k].elevation, -90.0)
+
+ lo_angles = self.simulation.bs.antenna[k].to_local_coord(
+ expected_azi[k, k],
+ expected_ele[k, k],
+ )
+ npt.assert_array_almost_equal(
+ lo_angles, expected_loc[k], decimal=2,
+ )
+ npt.assert_array_almost_equal(
+ self.simulation.bs.antenna[k].beams_list[0],
+ expected_beam[k], decimal=2,
+ )
+
# Test angle to ES in the IMT coord system
- phi_es, theta_es = self.simulation.bs.get_pointing_vector_to(self.simulation.system)
- expected_phi_es = np.array([[18.44],[23.96],[33.69],[53.13]])
- npt.assert_array_almost_equal(phi_es,expected_phi_es,decimal=2)
- expected_theta_es = np.array([[86.83],[85.94],[84.46],[82.03]])
- npt.assert_array_almost_equal(theta_es,expected_theta_es,decimal=2)
-
+ phi_es, theta_es = self.simulation.bs.get_pointing_vector_to(
+ self.simulation.system,
+ )
+ expected_phi_es = np.array([[18.44], [23.96], [33.69], [53.13]])
+ npt.assert_array_almost_equal(phi_es, expected_phi_es, decimal=2)
+ expected_theta_es = np.array([[86.83], [85.94], [84.46], [82.03]])
+ npt.assert_array_almost_equal(theta_es, expected_theta_es, decimal=2)
+
# Test angle to ES in the local coord system
- expected_es_loc = [(np.array([99.92]),np.array([18.70])),
- (np.array([99.92]),np.array([24.28])),
- (np.array([99.92]),np.array([34.09])),
- (np.array([99.92]),np.array([53.54]))]
+ expected_es_loc = [
+ (np.array([99.92]), np.array([18.70])),
+ (np.array([99.92]), np.array([24.28])),
+ (np.array([99.92]), np.array([34.09])),
+ (np.array([99.92]), np.array([53.54])),
+ ]
for k in range(self.simulation.bs.num_stations):
- lo_angles = self.simulation.bs.antenna[k].to_local_coord(expected_phi_es[k],
- expected_theta_es[k])
- npt.assert_array_almost_equal(lo_angles,expected_es_loc[k],decimal=2)
-
+ lo_angles = self.simulation.bs.antenna[k].to_local_coord(
+ expected_phi_es[k],
+ expected_theta_es[k],
+ )
+ npt.assert_array_almost_equal(
+ lo_angles, expected_es_loc[k], decimal=2,
+ )
+
# Test gain to ES
- calc_gain = self.simulation.calculate_gains(self.simulation.bs,
- self.simulation.system)
+ calc_gain = self.simulation.calculate_gains(
+ self.simulation.bs,
+ self.simulation.system,
+ )
for k in range(self.simulation.bs.num_stations):
beam = 0
- exp_gain = self.simulation.bs.antenna[k]._beam_gain(expected_es_loc[k][0],
- expected_es_loc[k][1],
- beam)
- self.assertAlmostEqual(np.asscalar(calc_gain[k]),np.asscalar(exp_gain),places=1)
-
+ exp_gain = self.simulation.bs.antenna[k]._beam_gain(
+ expected_es_loc[k][0],
+ expected_es_loc[k][1],
+ beam,
+ )
+ self.assertAlmostEqual(
+ np.ndarray.item(
+ calc_gain[k],
+ ),
+ np.ndarray.item(exp_gain),
+ places=1,
+ )
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_simulation_uplink.py b/tests/test_simulation_uplink.py
index 2a38a0861..3c4da26d2 100644
--- a/tests/test_simulation_uplink.py
+++ b/tests/test_simulation_uplink.py
@@ -17,6 +17,7 @@
from sharc.propagation.propagation_factory import PropagationFactory
from sharc.support.enumerations import StationType
+
class SimulationUplinkTest(unittest.TestCase):
def setUp(self):
@@ -28,87 +29,87 @@ def setUp(self):
self.param.general.enable_adjacent_channel = False
self.param.general.overwrite_output = True
- self.param.imt.topology = "SINGLE_BS"
- self.param.imt.wrap_around = True
- self.param.imt.num_clusters = 2
- self.param.imt.intersite_distance = 150
+ self.param.imt.topology.type = "SINGLE_BS"
+ self.param.imt.topology.single_bs.num_clusters = 2
+ self.param.imt.topology.single_bs.intersite_distance = 150
+ self.param.imt.topology.single_bs.cell_radius = 100
self.param.imt.minimum_separation_distance_bs_ue = 10
self.param.imt.interfered_with = False
- self.param.imt.frequency = 10000
- self.param.imt.bandwidth = 100
+ self.param.imt.frequency = 10000.0
+ self.param.imt.bandwidth = 100.0
self.param.imt.rb_bandwidth = 0.180
self.param.imt.spectral_mask = "IMT-2020"
self.param.imt.spurious_emissions = -13
self.param.imt.guard_band_ratio = 0.1
self.param.imt.ho_margin = 3
- self.param.imt.bs_load_probability = 1
- self.param.imt.num_resource_blocks = 10
- self.param.imt.bs_conducted_power = 10
- self.param.imt.bs_height = 6
- self.param.imt.bs_acs = 30
- self.param.imt.bs_noise_figure = 7
- self.param.imt.bs_noise_temperature = 290
- self.param.imt.bs_ohmic_loss = 3
- self.param.imt.ul_attenuation_factor = 0.4
- self.param.imt.ul_sinr_min = -10
- self.param.imt.ul_sinr_max = 22
- self.param.imt.ue_k = 2
- self.param.imt.ue_k_m = 1
- self.param.imt.ue_indoor_percent = 0
- self.param.imt.ue_distribution_distance = "RAYLEIGH"
- self.param.imt.ue_distribution_azimuth = "UNIFORM"
- self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE"
- self.param.imt.ue_tx_power_control = "OFF"
- self.param.imt.ue_p_o_pusch = -95
- self.param.imt.ue_alpha = 0.8
- self.param.imt.ue_p_cmax = 20
- self.param.imt.ue_conducted_power = 10
- self.param.imt.ue_height = 1.5
- self.param.imt.ue_aclr = 20
- self.param.imt.ue_acs = 25
- self.param.imt.ue_noise_figure = 9
- self.param.imt.ue_ohmic_loss = 3
- self.param.imt.ue_body_loss = 4
- self.param.imt.dl_attenuation_factor = 0.6
- self.param.imt.dl_sinr_min = -10
- self.param.imt.dl_sinr_max = 30
+ self.param.imt.bs.load_probability = 1
+
+ self.param.imt.bs.conducted_power = 10
+ self.param.imt.bs.height = 6
+ self.param.imt.bs.acs = 30
+ self.param.imt.bs.noise_figure = 7
+ self.param.imt.bs.ohmic_loss = 3
+ self.param.imt.uplink.attenuation_factor = 0.4
+ self.param.imt.uplink.sinr_min = -10
+ self.param.imt.uplink.sinr_max = 22
+ self.param.imt.ue.k = 2
+ self.param.imt.ue.k_m = 1
+ self.param.imt.ue.indoor_percent = 0
+ self.param.imt.ue.distribution_distance = "RAYLEIGH"
+ self.param.imt.ue.distribution_azimuth = "UNIFORM"
+ self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ self.param.imt.ue.tx_power_control = "OFF"
+ self.param.imt.ue.p_o_pusch = -95
+ self.param.imt.ue.alpha = 0.8
+ self.param.imt.ue.p_cmax = 20
+ self.param.imt.ue.conducted_power = 10
+ self.param.imt.ue.height = 1.5
+ self.param.imt.ue.aclr = 20
+ self.param.imt.ue.acs = 25
+ self.param.imt.ue.noise_figure = 9
+ self.param.imt.ue.ohmic_loss = 3
+ self.param.imt.ue.body_loss = 4
+ self.param.imt.downlink.attenuation_factor = 0.6
+ self.param.imt.downlink.sinr_min = -10
+ self.param.imt.downlink.sinr_max = 30
self.param.imt.channel_model = "FSPL"
- self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL)
+ # probability of line-of-sight (not for FSPL)
+ self.param.imt.line_of_sight_prob = 0.75
self.param.imt.shadowing = False
self.param.imt.noise_temperature = 290
- self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23
-
- self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.antenna_imt.bs_normalization = False
- self.param.antenna_imt.bs_element_pattern = "M2101"
- self.param.antenna_imt.bs_minimum_array_gain = -200
- self.param.antenna_imt.bs_normalization_file = None
- self.param.antenna_imt.bs_element_max_g = 10
- self.param.antenna_imt.bs_element_phi_3db = 80
- self.param.antenna_imt.bs_element_theta_3db = 80
- self.param.antenna_imt.bs_element_am = 25
- self.param.antenna_imt.bs_element_sla_v = 25
- self.param.antenna_imt.bs_n_rows = 16
- self.param.antenna_imt.bs_n_columns = 16
- self.param.antenna_imt.bs_element_horiz_spacing = 1
- self.param.antenna_imt.bs_element_vert_spacing = 1
- self.param.antenna_imt.bs_multiplication_factor = 12
- self.param.antenna_imt.bs_downtilt = 10
-
- self.param.antenna_imt.ue_element_pattern = "M2101"
- self.param.antenna_imt.ue_normalization = False
- self.param.antenna_imt.ue_minimum_array_gain = -200
- self.param.antenna_imt.ue_normalization_file = None
- self.param.antenna_imt.ue_element_max_g = 5
- self.param.antenna_imt.ue_element_phi_3db = 65
- self.param.antenna_imt.ue_element_theta_3db = 65
- self.param.antenna_imt.ue_element_am = 30
- self.param.antenna_imt.ue_element_sla_v = 30
- self.param.antenna_imt.ue_n_rows = 2
- self.param.antenna_imt.ue_n_columns = 1
- self.param.antenna_imt.ue_element_horiz_spacing = 0.5
- self.param.antenna_imt.ue_element_vert_spacing = 0.5
- self.param.antenna_imt.ue_multiplication_factor = 12
+
+ self.param.imt.bs.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.ue.antenna.array.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.param.imt.bs.antenna.array.normalization = False
+ self.param.imt.bs.antenna.array.element_pattern = "M2101"
+ self.param.imt.bs.antenna.array.minimum_array_gain = -200
+ self.param.imt.bs.antenna.array.normalization_file = None
+ self.param.imt.bs.antenna.array.element_max_g = 10
+ self.param.imt.bs.antenna.array.element_phi_3db = 80
+ self.param.imt.bs.antenna.array.element_theta_3db = 80
+ self.param.imt.bs.antenna.array.element_am = 25
+ self.param.imt.bs.antenna.array.element_sla_v = 25
+ self.param.imt.bs.antenna.array.n_rows = 16
+ self.param.imt.bs.antenna.array.n_columns = 16
+ self.param.imt.bs.antenna.array.element_horiz_spacing = 1
+ self.param.imt.bs.antenna.array.element_vert_spacing = 1
+ self.param.imt.bs.antenna.array.multiplication_factor = 12
+ self.param.imt.bs.antenna.array.downtilt = 10
+
+ self.param.imt.ue.antenna.array.element_pattern = "M2101"
+ self.param.imt.ue.antenna.array.normalization = False
+ self.param.imt.ue.antenna.array.minimum_array_gain = -200
+ self.param.imt.ue.antenna.array.normalization_file = None
+ self.param.imt.ue.antenna.array.element_max_g = 5
+ self.param.imt.ue.antenna.array.element_phi_3db = 65
+ self.param.imt.ue.antenna.array.element_theta_3db = 65
+ self.param.imt.ue.antenna.array.element_am = 30
+ self.param.imt.ue.antenna.array.element_sla_v = 30
+ self.param.imt.ue.antenna.array.n_rows = 2
+ self.param.imt.ue.antenna.array.n_columns = 1
+ self.param.imt.ue.antenna.array.element_horiz_spacing = 0.5
+ self.param.imt.ue.antenna.array.element_vert_spacing = 0.5
+ self.param.imt.ue.antenna.array.multiplication_factor = 12
self.param.fss_ss.frequency = 10000
self.param.fss_ss.bandwidth = 100
@@ -122,7 +123,7 @@ def setUp(self):
self.param.fss_ss.antenna_pattern = "OMNI"
self.param.fss_ss.imt_altitude = 1000
self.param.fss_ss.imt_lat_deg = -23.5629739
- self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75)
+ self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75)
self.param.fss_ss.channel_model = "FSPL"
self.param.fss_ss.line_of_sight_prob = 0.01
self.param.fss_ss.surf_water_vapour_density = 7.5
@@ -130,8 +131,6 @@ def setUp(self):
self.param.fss_ss.time_ratio = 0.5
self.param.fss_ss.antenna_l_s = -20
self.param.fss_ss.acs = 0
- self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_ss.EARTH_RADIUS = 6371000
self.param.fss_es.x = -5000
self.param.fss_es.y = 0
@@ -149,30 +148,29 @@ def setUp(self):
self.param.fss_es.channel_model = "FSPL"
self.param.fss_es.line_of_sight_prob = 1
self.param.fss_es.acs = 0
- self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23
- self.param.fss_es.EARTH_RADIUS = 6371000
-
- self.param.ras.x = -5000
- self.param.ras.y = 0
- self.param.ras.height = 10
- self.param.ras.elevation = 20
- self.param.ras.azimuth = 0
- self.param.ras.frequency = 10000
+
+ self.param.ras.geometry.location.type = "FIXED"
+ self.param.ras.geometry.location.fixed.x = -5000
+ self.param.ras.geometry.location.fixed.y = 0
+ self.param.ras.geometry.height = 10
+ self.param.ras.geometry.elevation.fixed = 20
+ self.param.ras.geometry.azimuth.fixed = 0
+ self.param.ras.geometry.elevation.type = "FIXED"
+ self.param.ras.geometry.azimuth.type = "FIXED"
+ self.param.ras.frequency = 1000
self.param.ras.bandwidth = 100
- self.param.ras.antenna_noise_temperature = 50
- self.param.ras.receiver_noise_temperature = 50
- self.param.ras.antenna_gain = 50
+ self.param.ras.noise_temperature = 100
+ self.param.ras.antenna.gain = 50
self.param.ras.antenna_efficiency = 0.7
- self.param.ras.diameter = 10
- self.param.ras.acs = 0
- self.param.ras.antenna_pattern = "OMNI"
+ self.param.ras.adjacent_ch_selectivity = 0
+ self.param.ras.tx_power_density = -500
+ self.param.ras.antenna.pattern = "OMNI"
self.param.ras.channel_model = "FSPL"
self.param.ras.line_of_sight_prob = 1
self.param.ras.BOLTZMANN_CONSTANT = 1.38064852e-23
self.param.ras.EARTH_RADIUS = 6371000
self.param.ras.SPEED_OF_LIGHT = 299792458
-
def test_simulation_2bs_4ue_ss(self):
self.param.general.system = "FSS_SS"
@@ -186,143 +184,208 @@ def test_simulation_2bs_4ue_ss(self):
self.assertTrue(self.simulation.co_channel)
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
# test connection method
self.simulation.connect_ue_to_bs()
- self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]})
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
# We do not test the selection method here because in this specific
# scenario we do not want to change the order of the UE's
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
- coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23],
- [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]])
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- coupling_loss_imt,
- atol=1e-2)
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
+ coupling_loss_imt = np.array([
+ [88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23],
+ [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23],
+ ])
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ coupling_loss_imt,
+ atol=1e-2,
+ )
# test scheduler and bandwidth allocation
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
- npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
+ npt.assert_allclose(
+ self.simulation.ue.bandwidth,
+ bandwidth_per_ue * np.ones(4), atol=1e-2,
+ )
# there is no power control, so UE's will transmit at maximum power
self.simulation.power_control()
tx_power = 20
- npt.assert_allclose(self.simulation.ue.tx_power, tx_power*np.ones(4))
+ npt.assert_allclose(self.simulation.ue.tx_power, tx_power * np.ones(4))
# test method that calculates SINR
self.simulation.calculate_sinr()
# check BS received power
- rx_power = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,0:2]),
- 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,2:4])}
- npt.assert_allclose(self.simulation.bs.rx_power[0],
- rx_power[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.rx_power[1],
- rx_power[1],
- atol=1e-2)
+ rx_power = {
+ 0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 0:2]),
+ 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 2:4]),
+ }
+ npt.assert_allclose(
+ self.simulation.bs.rx_power[0],
+ rx_power[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.rx_power[1],
+ rx_power[1],
+ atol=1e-2,
+ )
# check BS received interference
- rx_interference = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,2:4]),
- 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,0:2])}
-
- npt.assert_allclose(self.simulation.bs.rx_interference[0],
- rx_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.rx_interference[1],
- rx_interference[1],
- atol=1e-2)
+ rx_interference = {
+ 0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 2:4]),
+ 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 0:2]),
+ }
+
+ npt.assert_allclose(
+ self.simulation.bs.rx_interference[0],
+ rx_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.rx_interference[1],
+ rx_interference[1],
+ atol=1e-2,
+ )
# check BS thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7
- npt.assert_allclose(self.simulation.bs.thermal_noise,
- thermal_noise,
- atol=1e-2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7
+ npt.assert_allclose(
+ self.simulation.bs.thermal_noise,
+ thermal_noise,
+ atol=1e-2,
+ )
# check BS thermal noise + interference
- total_interference = { 0: 10*np.log10(np.power(10, 0.1*rx_interference[0]) + np.power(10, 0.1*thermal_noise)),
- 1: 10*np.log10(np.power(10, 0.1*rx_interference[1]) + np.power(10, 0.1*thermal_noise))}
- npt.assert_allclose(self.simulation.bs.total_interference[0],
- total_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.total_interference[1],
- total_interference[1],
- atol=1e-2)
+ total_interference = {
+ 0: 10 * np.log10(np.power(10, 0.1 * rx_interference[0]) + np.power(10, 0.1 * thermal_noise)),
+ 1: 10 * np.log10(np.power(10, 0.1 * rx_interference[1]) + np.power(10, 0.1 * thermal_noise)),
+ }
+ npt.assert_allclose(
+ self.simulation.bs.total_interference[0],
+ total_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.total_interference[1],
+ total_interference[1],
+ atol=1e-2,
+ )
# check SNR
- npt.assert_allclose(self.simulation.bs.snr[0],
- rx_power[0] - thermal_noise,
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.snr[1],
- rx_power[1] - thermal_noise,
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.bs.snr[0],
+ rx_power[0] - thermal_noise,
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.snr[1],
+ rx_power[1] - thermal_noise,
+ atol=1e-2,
+ )
# check SINR
- npt.assert_allclose(self.simulation.bs.sinr[0],
- rx_power[0] - total_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.sinr[1],
- rx_power[1] - total_interference[1],
- atol=1e-2)
-
- self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss)
+ npt.assert_allclose(
+ self.simulation.bs.sinr[0],
+ rx_power[0] - total_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.sinr[1],
+ rx_power[1] - total_interference[1],
+ atol=1e-2,
+ )
+
+ self.simulation.system = StationFactory.generate_fss_space_station(
+ self.param.fss_ss,
+ )
self.simulation.system.x = np.array([0])
self.simulation.system.y = np.array([0])
+ self.simulation.system.z = np.array([self.param.fss_ss.altitude])
self.simulation.system.height = np.array([self.param.fss_ss.altitude])
- # test the method that calculates interference from IMT UE to FSS space station
+ # test the method that calculates interference from IMT UE to FSS space
+ # station
self.simulation.calculate_external_interference()
# check coupling loss
- coupling_loss_imt_system = np.array([213.52-51-10, 213.52-51-11, 213.52-51-22, 213.52-51-23])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ coupling_loss_imt_system = np.array(
+ [213.52 - 51 - 10, 213.52 - 51 - 11, 213.52 - 51 - 22, 213.52 - 51 - 23],
+ ).reshape(-1, 1)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
# check interference generated by UE to FSS space station
interference_ue = tx_power - coupling_loss_imt_system
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference_ue)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference_ue)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# check FSS space station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*950*100*1e3*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 950 * 100 * 1e3 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at FSS space station
- self.assertAlmostEqual(self.simulation.system.inr,
- rx_interference - thermal_noise,
- delta=.01)
-
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ rx_interference - thermal_noise,
+ delta=.01,
+ )
def test_simulation_2bs_4ue_es(self):
self.param.general.system = "FSS_ES"
@@ -335,40 +398,54 @@ def test_simulation_2bs_4ue_es(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
# We do not test the selection method here because in this specific
# scenario we do not want to change the order of the UE's
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param, self.simulation.param_system,
+ random_number_gen,
+ )
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
self.simulation.power_control()
self.simulation.calculate_sinr()
@@ -376,137 +453,206 @@ def test_simulation_2bs_4ue_es(self):
tx_power = 20
# check coupling loss IMT
- coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23],
- [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]])
- npt.assert_allclose(self.simulation.coupling_loss_imt,
- coupling_loss_imt,
- atol=1e-2)
+ coupling_loss_imt = np.array([
+ [88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23],
+ [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23],
+ ])
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt,
+ coupling_loss_imt,
+ atol=1e-2,
+ )
# check BS received power
- rx_power = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,0:2]),
- 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,2:4])}
- npt.assert_allclose(self.simulation.bs.rx_power[0],
- rx_power[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.rx_power[1],
- rx_power[1],
- atol=1e-2)
+ rx_power = {
+ 0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 0:2]),
+ 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 2:4]),
+ }
+ npt.assert_allclose(
+ self.simulation.bs.rx_power[0],
+ rx_power[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.rx_power[1],
+ rx_power[1],
+ atol=1e-2,
+ )
# check BS received interference
- rx_interference = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,2:4]),
- 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,0:2])}
-
- npt.assert_allclose(self.simulation.bs.rx_interference[0],
- rx_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.rx_interference[1],
- rx_interference[1],
- atol=1e-2)
+ rx_interference = {
+ 0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 2:4]),
+ 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 0:2]),
+ }
+
+ npt.assert_allclose(
+ self.simulation.bs.rx_interference[0],
+ rx_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.rx_interference[1],
+ rx_interference[1],
+ atol=1e-2,
+ )
# check BS thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7
- npt.assert_allclose(self.simulation.bs.thermal_noise,
- thermal_noise,
- atol=1e-2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7
+ npt.assert_allclose(
+ self.simulation.bs.thermal_noise,
+ thermal_noise,
+ atol=1e-2,
+ )
# check BS thermal noise + interference
- total_interference = { 0: 10*np.log10(np.power(10, 0.1*rx_interference[0]) + np.power(10, 0.1*thermal_noise)),
- 1: 10*np.log10(np.power(10, 0.1*rx_interference[1]) + np.power(10, 0.1*thermal_noise))}
- npt.assert_allclose(self.simulation.bs.total_interference[0],
- total_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.total_interference[1],
- total_interference[1],
- atol=1e-2)
+ total_interference = {
+ 0: 10 * np.log10(np.power(10, 0.1 * rx_interference[0]) + np.power(10, 0.1 * thermal_noise)),
+ 1: 10 * np.log10(np.power(10, 0.1 * rx_interference[1]) + np.power(10, 0.1 * thermal_noise)),
+ }
+ npt.assert_allclose(
+ self.simulation.bs.total_interference[0],
+ total_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.total_interference[1],
+ total_interference[1],
+ atol=1e-2,
+ )
# check SNR
- npt.assert_allclose(self.simulation.bs.snr[0],
- rx_power[0] - thermal_noise,
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.snr[1],
- rx_power[1] - thermal_noise,
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.bs.snr[0],
+ rx_power[0] - thermal_noise,
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.snr[1],
+ rx_power[1] - thermal_noise,
+ atol=1e-2,
+ )
# check SINR
- npt.assert_allclose(self.simulation.bs.sinr[0],
- rx_power[0] - total_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.sinr[1],
- rx_power[1] - total_interference[1],
- atol=1e-2)
-
- self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen)
- self.simulation.system.x = np.array([-2000])
- self.simulation.system.y = np.array([0])
- self.simulation.system.height = np.array([self.param.fss_es.height])
+ npt.assert_allclose(
+ self.simulation.bs.sinr[0],
+ rx_power[0] - total_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.sinr[1],
+ rx_power[1] - total_interference[1],
+ atol=1e-2,
+ )
+
+ self.param.fss_es.x = -2000
+ self.param.fss_es.y = 0
+ self.simulation.system = StationFactory.generate_fss_earth_station(
+ self.param.fss_es, random_number_gen,
+ )
# what if FSS ES is interferer???
self.simulation.calculate_sinr_ext()
# coupling loss FSS_ES <-> IMT BS
- coupling_loss_imt_system = np.array([124.47-50-1, 124.47-50-1, 125.29-50-2, 125.29-50-2])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ coupling_loss_imt_system = np.array(
+ [124.47 - 50 - 1, 124.47 - 50 - 1, 125.29 - 50 - 2, 125.29 - 50 - 2],
+ ).reshape(-1, 1)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
# external interference
- system_tx_power = -60 + 10*math.log10(bandwidth_per_ue*1e6) + 30
- ext_interference = { 0: system_tx_power - coupling_loss_imt_system[0:2],
- 1: system_tx_power - coupling_loss_imt_system[2:4]}
- npt.assert_allclose(self.simulation.bs.ext_interference[0],
- ext_interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.ext_interference[1],
- ext_interference[1],
- atol=1e-2)
+ system_tx_power = -60 + 10 * math.log10(self.simulation.overlapping_bandwidth * 1e6) + 30
+ ext_interference = {
+ 0: system_tx_power - coupling_loss_imt_system[0:2, 0],
+ 1: system_tx_power - coupling_loss_imt_system[2:4, 0],
+ }
+ npt.assert_allclose(
+ self.simulation.bs.ext_interference[0],
+ ext_interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.ext_interference[1],
+ ext_interference[1],
+ atol=1e-2,
+ )
# SINR with external interference
- interference = { 0: 10*np.log10(np.power(10, 0.1*total_interference[0]) \
- + np.power(10, 0.1*ext_interference[0])),
- 1: 10*np.log10(np.power(10, 0.1*total_interference[1]) \
- + np.power(10, 0.1*ext_interference[1]))}
-
- npt.assert_allclose(self.simulation.bs.sinr_ext[0],
- rx_power[0] - interference[0],
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.sinr_ext[1],
- rx_power[1] - interference[1],
- atol=1e-2)
+ interference = {
+ 0: 10 * np.log10(
+ np.power(10, 0.1 * total_interference[0]) +
+ np.power(10, 0.1 * ext_interference[0]),
+ ),
+ 1: 10 * np.log10(
+ np.power(10, 0.1 * total_interference[1]) +
+ np.power(10, 0.1 * ext_interference[1]),
+ ),
+ }
+
+ npt.assert_allclose(
+ self.simulation.bs.sinr_ext[0],
+ rx_power[0] - interference[0],
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.sinr_ext[1],
+ rx_power[1] - interference[1],
+ atol=1e-2,
+ )
# INR
- npt.assert_allclose(self.simulation.bs.inr[0],
- interference[0] - thermal_noise,
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.inr[1],
- interference[1] - thermal_noise,
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.bs.inr[0],
+ interference[0] - thermal_noise,
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.inr[1],
+ interference[1] - thermal_noise,
+ atol=1e-2,
+ )
# what if IMT is interferer?
self.simulation.calculate_external_interference()
# coupling loss
- coupling_loss_imt_system = np.array([128.55-50-10, 128.76-50-11, 128.93-50-22, 129.17-50-23])
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- coupling_loss_imt_system,
- atol=1e-2)
+ coupling_loss_imt_system = np.array(
+ [128.55 - 50 - 10, 128.76 - 50 - 11, 128.93 - 50 - 22, 129.17 - 50 - 23],
+ ).reshape(-1, 1)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ coupling_loss_imt_system,
+ atol=1e-2,
+ )
# interference
interference = tx_power - coupling_loss_imt_system
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# check FSS Earth station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at FSS Earth station
- self.assertAlmostEqual(self.simulation.system.inr,
- np.array([ rx_interference - thermal_noise ]),
- delta=.01)
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ np.array([rx_interference - thermal_noise]),
+ delta=.01,
+ )
def test_simulation_2bs_4ue_ras(self):
self.param.general.system = "RAS"
@@ -519,94 +665,139 @@ def test_simulation_2bs_4ue_ras(self):
random_number_gen = np.random.RandomState()
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)])
self.simulation.bs.active = np.ones(2, dtype=bool)
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([20, 70, 110, 170])
- self.simulation.ue.y = np.array([ 0, 0, 0, 0])
- self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)])
+ self.simulation.ue.y = np.array([0, 0, 0, 0])
+ self.simulation.ue.antenna = np.array(
+ [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)],
+ )
self.simulation.ue.active = np.ones(4, dtype=bool)
self.simulation.connect_ue_to_bs()
self.simulation.select_ue(random_number_gen)
- self.simulation.link = {0:[0,1],1:[2,3]}
-
- self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model,
- self.param, random_number_gen)
- self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model,
- self.param, random_number_gen)
+ self.simulation.link = {0: [0, 1], 1: [2, 3]}
+
+ self.simulation.propagation_imt = PropagationFactory.create_propagation(
+ self.param.imt.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
+
+ self.simulation.propagation_system = PropagationFactory.create_propagation(
+ self.param.fss_ss.channel_model,
+ self.param,
+ self.simulation.param_system,
+ random_number_gen,
+ )
# test coupling loss method
- self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs,
- self.simulation.ue,
- self.simulation.propagation_imt)
+ self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(
+ self.simulation.ue,
+ self.simulation.bs,
+ )
self.simulation.scheduler()
- bandwidth_per_ue = math.trunc((1 - 0.1)*100/2)
+ bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2)
self.simulation.power_control()
self.simulation.calculate_sinr()
# check BS thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7
- npt.assert_allclose(self.simulation.bs.thermal_noise,
- thermal_noise,
- atol=1e-2)
+ thermal_noise = 10 * \
+ np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7
+ npt.assert_allclose(
+ self.simulation.bs.thermal_noise,
+ thermal_noise,
+ atol=1e-2,
+ )
# check SINR
- npt.assert_allclose(self.simulation.bs.sinr[0],
- np.array([-57.47 - (-60.27), -67.35 - (-63.05)]),
- atol=1e-2)
- npt.assert_allclose(self.simulation.bs.sinr[1],
- np.array([-57.53 - (-75.41), -46.99 - (-71.67)]),
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.bs.sinr[0],
+ np.array([-57.47 - (-60.06), -67.35 - (-63.04)]),
+ atol=1e-2,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.sinr[1],
+ np.array([-57.53 - (-75.40), -46.99 - (-71.57)]),
+ atol=1e-2,
+ )
# Create system
- self.simulation.system = StationFactory.generate_ras_station(self.param.ras)
+ self.simulation.system = StationFactory.generate_ras_station(
+ self.param.ras, random_number_gen, None,
+ )
self.simulation.system.x = np.array([-2000])
self.simulation.system.y = np.array([0])
- self.simulation.system.height = np.array([self.param.ras.height])
+ self.simulation.system.height = np.array([self.param.ras.geometry.height])
self.simulation.system.antenna[0].effective_area = 54.9779
# Test gain calculation
- gains = self.simulation.calculate_gains(self.simulation.system,self.simulation.ue)
- npt.assert_equal(gains,np.array([[50, 50, 50, 50]]))
+ gains = self.simulation.calculate_gains(
+ self.simulation.system, self.simulation.ue,
+ )
+ npt.assert_equal(gains, np.array([[50, 50, 50, 50]]))
# Test external interference
self.simulation.calculate_external_interference()
- npt.assert_allclose(self.simulation.coupling_loss_imt_system,
- np.array([125.55-50-10, 125.76-50-11, 125.93-50-22, 126.17-50-23]),
- atol=1e-2)
+ npt.assert_allclose(
+ self.simulation.coupling_loss_imt_system,
+ np.array([
+ 125.55 - 50 - 10, 125.76 - 50 - 11,
+ 125.93 - 50 - 22, 126.17 - 50 - 23,
+ ]).reshape(-1, 1),
+ atol=1e-2,
+ )
# Test RAS PFD
- interference = 20 - np.array([125.55-50-10, 125.76-50-11, 125.93-50-22, 126.17-50-23])
- rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference)))
- self.assertAlmostEqual(self.simulation.system.rx_interference,
- rx_interference,
- delta=.01)
+ interference = 20 - \
+ np.array([
+ 125.55 - 50 - 10, 125.76 - 50 - 11,
+ 125.93 - 50 - 22, 126.17 - 50 - 23,
+ ])
+ rx_interference = 10 * \
+ math.log10(np.sum(np.power(10, 0.1 * interference)))
+ self.assertAlmostEqual(
+ self.simulation.system.rx_interference,
+ rx_interference,
+ delta=.01,
+ )
# Test RAS PFD
- pfd = 10*np.log10(10**(rx_interference/10)/54.9779)
- self.assertAlmostEqual(self.simulation.system.pfd,
- pfd,
- delta=.01)
+ pfd = 10 * np.log10(10**(rx_interference / 10) / 54.9779)
+ self.assertAlmostEqual(
+ self.simulation.system.pfd,
+ pfd,
+ delta=.01,
+ )
# check RAS station thermal noise
- thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6)
- self.assertAlmostEqual(self.simulation.system.thermal_noise,
- thermal_noise,
- delta=.01)
+ thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6)
+ self.assertAlmostEqual(
+ self.simulation.system.thermal_noise,
+ thermal_noise,
+ delta=.01,
+ )
# check INR at RAS station
- self.assertAlmostEqual(self.simulation.system.inr,
- np.array([ rx_interference - (-98.599) ]),
- delta=.01)
+ self.assertAlmostEqual(
+ self.simulation.system.inr,
+ np.array([rx_interference - (-98.599)]),
+ delta=.01,
+ )
def test_beamforming_gains(self):
self.param.general.system = "FSS_SS"
@@ -618,51 +809,67 @@ def test_beamforming_gains(self):
random_number_gen = np.random.RandomState(101)
# Set scenario
- self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
-
- self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt,
- self.param.antenna_imt,
- self.simulation.topology,
- random_number_gen)
+ self.simulation.bs = StationFactory.generate_imt_base_stations(
+ self.param.imt,
+ self.param.imt.bs.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
+
+ self.simulation.ue = StationFactory.generate_imt_ue(
+ self.param.imt,
+ self.param.imt.ue.antenna.array,
+ self.simulation.topology,
+ random_number_gen,
+ )
self.simulation.ue.x = np.array([50.000, 43.301, 150.000, 175.000])
- self.simulation.ue.y = np.array([ 0.000, 25.000, 0.000, 43.301])
+ self.simulation.ue.y = np.array([0.000, 25.000, 0.000, 43.301])
# Physical pointing angles
- self.assertEqual(self.simulation.bs.antenna[0].azimuth,0)
- self.assertEqual(self.simulation.bs.antenna[0].elevation,-10)
- self.assertEqual(self.simulation.bs.antenna[1].azimuth,180)
- self.assertEqual(self.simulation.bs.antenna[0].elevation,-10)
+ self.assertEqual(self.simulation.bs.antenna[0].azimuth, 0)
+ self.assertEqual(self.simulation.bs.antenna[0].elevation, -10)
+ self.assertEqual(self.simulation.bs.antenna[1].azimuth, 180)
+ self.assertEqual(self.simulation.bs.antenna[0].elevation, -10)
# Change UE pointing
self.simulation.ue.azimuth = np.array([180, -90, 90, -90])
self.simulation.ue.elevation = np.array([-30, -15, 15, 30])
- par = self.param.antenna_imt.get_antenna_parameters(StationType.IMT_UE)
+ par = self.param.imt.ue.antenna.array.get_antenna_parameters()
for i in range(self.simulation.ue.num_stations):
- self.simulation.ue.antenna[i] = AntennaBeamformingImt(par, self.simulation.ue.azimuth[i],
- self.simulation.ue.elevation[i])
- self.assertEqual(self.simulation.ue.antenna[0].azimuth,180)
- self.assertEqual(self.simulation.ue.antenna[0].elevation,-30)
- self.assertEqual(self.simulation.ue.antenna[1].azimuth,-90)
- self.assertEqual(self.simulation.ue.antenna[1].elevation,-15)
- self.assertEqual(self.simulation.ue.antenna[2].azimuth,90)
- self.assertEqual(self.simulation.ue.antenna[2].elevation,15)
- self.assertEqual(self.simulation.ue.antenna[3].azimuth,-90)
- self.assertEqual(self.simulation.ue.antenna[3].elevation,30)
+ self.simulation.ue.antenna[i] = AntennaBeamformingImt(
+ par, self.simulation.ue.azimuth[i],
+ self.simulation.ue.elevation[i],
+ )
+ self.assertEqual(self.simulation.ue.antenna[0].azimuth, 180)
+ self.assertEqual(self.simulation.ue.antenna[0].elevation, -30)
+ self.assertEqual(self.simulation.ue.antenna[1].azimuth, -90)
+ self.assertEqual(self.simulation.ue.antenna[1].elevation, -15)
+ self.assertEqual(self.simulation.ue.antenna[2].azimuth, 90)
+ self.assertEqual(self.simulation.ue.antenna[2].elevation, 15)
+ self.assertEqual(self.simulation.ue.antenna[3].azimuth, -90)
+ self.assertEqual(self.simulation.ue.antenna[3].elevation, 30)
# Simulate connection and selection
self.simulation.connect_ue_to_bs()
- self.assertEqual(self.simulation.link,{0:[0,1],1:[2,3]})
+ self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]})
# Test BS gains
# Test pointing vector
- phi, theta = self.simulation.bs.get_pointing_vector_to(self.simulation.ue)
- npt.assert_allclose(phi,np.array([[0.0, 30.0, 0.0, 13.898],
- [180.0, 170.935, 180.0, 120.0 ]]),atol=eps)
- npt.assert_allclose(theta,np.array([[95.143, 95.143, 91.718, 91.430],
- [91.718, 91.624, 95.143, 95.143]]),atol=eps)
+ phi, theta = self.simulation.bs.get_pointing_vector_to(
+ self.simulation.ue,
+ )
+ npt.assert_allclose(
+ phi, np.array([
+ [0.0, 30.0, 0.0, 13.898],
+ [180.0, 170.935, 180.0, 120.0],
+ ]), atol=eps,
+ )
+ npt.assert_allclose(
+ theta, np.array([
+ [95.143, 95.143, 91.718, 91.430],
+ [91.718, 91.624, 95.143, 95.143],
+ ]), atol=eps,
+ )
self.simulation.bs_to_ue_phi = phi
self.simulation.bs_to_ue_theta = theta
@@ -670,47 +877,79 @@ def test_beamforming_gains(self):
# method shufles the link dictionary, the order of the beams cannot be
# predicted. Thus, the beams need to be added outside of the function
self.simulation.ue.active = np.ones(4, dtype=bool)
- self.simulation.bs.antenna[0].add_beam(phi[0,0],theta[0,0])
- self.simulation.bs.antenna[0].add_beam(phi[0,1],theta[0,1])
- self.simulation.bs.antenna[1].add_beam(phi[1,2],theta[1,2])
- self.simulation.bs.antenna[1].add_beam(phi[1,3],theta[1,3])
- self.simulation.ue.antenna[0].add_beam(phi[0,0]-180,180-theta[0,0])
- self.simulation.ue.antenna[1].add_beam(phi[0,1]-180,180-theta[0,1])
- self.simulation.ue.antenna[2].add_beam(phi[1,2]-180,180-theta[1,2])
- self.simulation.ue.antenna[3].add_beam(phi[1,3]-180,180-theta[1,3])
- self.simulation.bs_to_ue_beam_rbs = np.array([0, 1, 0, 1],dtype=int)
+ self.simulation.bs.antenna[0].add_beam(phi[0, 0], theta[0, 0])
+ self.simulation.bs.antenna[0].add_beam(phi[0, 1], theta[0, 1])
+ self.simulation.bs.antenna[1].add_beam(phi[1, 2], theta[1, 2])
+ self.simulation.bs.antenna[1].add_beam(phi[1, 3], theta[1, 3])
+ self.simulation.ue.antenna[0].add_beam(
+ phi[0, 0] - 180, 180 - theta[0, 0],
+ )
+ self.simulation.ue.antenna[1].add_beam(
+ phi[0, 1] - 180, 180 - theta[0, 1],
+ )
+ self.simulation.ue.antenna[2].add_beam(
+ phi[1, 2] - 180, 180 - theta[1, 2],
+ )
+ self.simulation.ue.antenna[3].add_beam(
+ phi[1, 3] - 180, 180 - theta[1, 3],
+ )
+ self.simulation.bs_to_ue_beam_rbs = np.array([0, 1, 0, 1], dtype=int)
# Test beams pointing
- npt.assert_allclose(self.simulation.bs.antenna[0].beams_list[0],
- (0.0,-4.857),atol=eps)
- npt.assert_allclose(self.simulation.bs.antenna[0].beams_list[1],
- (29.92,-3.53),atol=eps)
- npt.assert_allclose(self.simulation.bs.antenna[1].beams_list[0],
- (0.0,-4.857),atol=eps)
- npt.assert_allclose(self.simulation.bs.antenna[1].beams_list[1],
- (-59.60,0.10),atol=eps)
- npt.assert_allclose(self.simulation.ue.antenna[0].beams_list[0],
- (0.0,-35.143),atol=eps)
- npt.assert_allclose(self.simulation.ue.antenna[1].beams_list[0],
- (-62.04,-12.44),atol=eps)
- npt.assert_allclose(self.simulation.ue.antenna[2].beams_list[0],
- (-88.66,-4.96),atol=eps)
- npt.assert_allclose(self.simulation.ue.antenna[3].beams_list[0],
- (32.16,20.71),atol=eps)
+ npt.assert_allclose(
+ self.simulation.bs.antenna[0].beams_list[0],
+ (0.0, -4.857), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.antenna[0].beams_list[1],
+ (29.92, -3.53), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.antenna[1].beams_list[0],
+ (0.0, -4.857), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.bs.antenna[1].beams_list[1],
+ (-59.60, 0.10), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.ue.antenna[0].beams_list[0],
+ (0.0, -35.143), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.ue.antenna[1].beams_list[0],
+ (-62.04, -12.44), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.ue.antenna[2].beams_list[0],
+ (-88.66, -4.96), atol=eps,
+ )
+ npt.assert_allclose(
+ self.simulation.ue.antenna[3].beams_list[0],
+ (32.16, 20.71), atol=eps,
+ )
# BS Gain matrix
- ref_gain = np.array([[ 34.03, 32.37, 8.41, -9.71],
- [ 8.41, -8.94, 34.03, 27.42]])
- gain = self.simulation.calculate_gains(self.simulation.bs,self.simulation.ue)
- npt.assert_allclose(gain,ref_gain,atol=eps)
+ ref_gain = np.array([
+ [34.03, 32.37, 8.41, -9.71],
+ [8.41, -8.94, 34.03, 27.42],
+ ])
+ gain = self.simulation.calculate_gains(
+ self.simulation.bs, self.simulation.ue,
+ )
+ npt.assert_allclose(gain, ref_gain, atol=eps)
# UE Gain matrix
- ref_gain = np.array([[ 4.503, -44.198],
- [ -3.362, -11.206],
- [-14.812, -14.389],
- [ -9.726, 3.853]])
- gain = self.simulation.calculate_gains(self.simulation.ue,self.simulation.bs)
- npt.assert_allclose(gain,ref_gain,atol=eps)
+ ref_gain = np.array([
+ [4.503, -44.198],
+ [-3.362, -11.206],
+ [-14.812, -14.389],
+ [-9.726, 3.853],
+ ])
+ gain = self.simulation.calculate_gains(
+ self.simulation.ue, self.simulation.bs,
+ )
+ npt.assert_allclose(gain, ref_gain, atol=eps)
def test_calculate_imt_ul_tput(self):
self.param.general.system = "FSS_SS"
@@ -722,13 +961,15 @@ def test_calculate_imt_ul_tput(self):
# Test 1
snir = np.array([0.0, 1.0, 15.0, -5.0, 100.00, 200.00])
- ref_tput = np.array([ 0.400, 0.470, 2.011, 0.159, 2.927, 2.927])
- tput = self.simulation.calculate_imt_tput(snir,
- self.param.imt.ul_sinr_min,
- self.param.imt.ul_sinr_max,
- self.param.imt.ul_attenuation_factor)
- npt.assert_allclose(tput,ref_tput,atol=eps)
+ ref_tput = np.array([0.400, 0.470, 2.011, 0.159, 2.927, 2.927])
+ tput = self.simulation.calculate_imt_tput(
+ snir,
+ self.param.imt.uplink.sinr_min,
+ self.param.imt.uplink.sinr_max,
+ self.param.imt.uplink.attenuation_factor,
+ )
+ npt.assert_allclose(tput, ref_tput, atol=eps)
+
if __name__ == '__main__':
unittest.main()
-
diff --git a/tests/test_spectral_mask_3gpp.py b/tests/test_spectral_mask_3gpp.py
index ecca99d1b..b2511b30d 100644
--- a/tests/test_spectral_mask_3gpp.py
+++ b/tests/test_spectral_mask_3gpp.py
@@ -11,8 +11,9 @@
from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp
from sharc.support.enumerations import StationType
+
class SpectalMask3GppTest(unittest.TestCase):
-
+
def setUp(self):
# Initialize variables for BS Cat-A mask (3.5 GHz)
sta_type = StationType.IMT_BS
@@ -20,28 +21,33 @@ def setUp(self):
frequency = 3490
bandwidth = 20
spurious = -13
- self.mask_bs_a = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious)
- self.mask_bs_a.set_mask(power = p_tx)
-
+ self.mask_bs_a = SpectralMask3Gpp(
+ sta_type, frequency, bandwidth, spurious,
+ )
+ self.mask_bs_a.set_mask(p_tx)
+
# Initialize variables for BS Cat-B mask (3.5 GHz)
sta_type = StationType.IMT_BS
p_tx = 46
frequency = 3490
bandwidth = 20
spurious = -30
- self.mask_bs_b = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious)
- self.mask_bs_b.set_mask(power = p_tx)
-
+ self.mask_bs_b = SpectralMask3Gpp(
+ sta_type, frequency, bandwidth, spurious,
+ )
+ self.mask_bs_b.set_mask(p_tx)
+
# Initialize variables for UE mask (3.5 GHz)
sta_type = StationType.IMT_UE
p_tx = 22
frequency = 3490
bandwidth = 20
spurious = -30
- self.mask_ue = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious)
- self.mask_ue.set_mask(power = p_tx)
-
-
+ self.mask_ue = SpectralMask3Gpp(
+ sta_type, frequency, bandwidth, spurious,
+ )
+ self.mask_ue.set_mask(p_tx)
+
def test_power_calc_bs_a(self):
#######################################################################
# BS Cat-A mask
@@ -49,132 +55,166 @@ def test_power_calc_bs_a(self):
fc = 3502.5
bandwidth = 5
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 7.01, delta = 1e-2)
+ self.assertAlmostEqual(poob, 7.01, delta=1e-2)
#######################################################################
fc = 3507.5
bandwidth = 5
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 2.98, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 2.98, delta=1e-2)
+
#######################################################################
fc = 3505
bandwidth = 10
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 8.46, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 8.46, delta=1e-2)
+
#######################################################################
fc = 3510
bandwidth = 10
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 3.50, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 3.50, delta=1e-2)
+
#######################################################################
fc = 3515
bandwidth = 10
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, -3, delta = 1e-2)
+ self.assertAlmostEqual(poob, -3, delta=1e-2)
#######################################################################
fc = 3520
bandwidth = 20
poob = self.mask_bs_a.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 0.01, delta = 1e-2)
-
- def test_power_calc_bs_b(self):
+ self.assertAlmostEqual(poob, 0.01, delta=1e-2)
+
+ def test_power_calc_bs_b(self):
#######################################################################
# BS Cat-B mask
#######################################################################
fc = 3502.5
bandwidth = 5
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 7.01, delta = 1e-2)
+ self.assertAlmostEqual(poob, 7.01, delta=1e-2)
#######################################################################
fc = 3507.5
bandwidth = 5
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 2.98, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 2.98, delta=1e-2)
+
#######################################################################
fc = 3505
bandwidth = 10
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 8.46, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 8.46, delta=1e-2)
+
#######################################################################
fc = 3510
bandwidth = 10
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, 3, delta = 1e-2)
-
+ self.assertAlmostEqual(poob, 3, delta=1e-2)
+
#######################################################################
fc = 3515
bandwidth = 10
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, -20, delta = 1e-2)
+ self.assertAlmostEqual(poob, -20, delta=1e-2)
#######################################################################
fc = 3520
bandwidth = 20
poob = self.mask_bs_b.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, -16.98, delta = 1e-2)
-
- def test_power_calc_ue(self):
+ self.assertAlmostEqual(poob, -16.98, delta=1e-2)
+
+ def test_power_calc_ue(self):
#######################################################################
# UE mask
#######################################################################
fc = 3500.5
bandwidth = 1
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, -21 + 10*np.log10(1/0.03), delta = 1e-2)
+ self.assertAlmostEqual(poob, -21 + 10 * np.log10(1 / 0.03), delta=1e-2)
#######################################################################
fc = 3503
bandwidth = 4
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob, -10 + 10*np.log10(4), delta = 1e-2)
-
+ self.assertAlmostEqual(poob, -10 + 10 * np.log10(4), delta=1e-2)
+
######################################################################
fc = 3502.5
bandwidth = 5
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob,
- 10*np.log10(np.power(10, 0.1*(-21 + 10*np.log10(1/0.03))) + np.power(10, 0.1*(-10 + 10*np.log10(4)))),
- delta = 1e-2)
-
+ self.assertAlmostEqual(
+ poob,
+ 10 * np.log10(
+ np.power(
+ 10,
+ 0.1 * (-21 + 10 * np.log10(1 / 0.03)),
+ ) + np.power(
+ 10,
+ 0.1 * (-10 + 10 * np.log10(4)),
+ ),
+ ),
+ delta=1e-2,
+ )
+
#######################################################################
fc = 3510
bandwidth = 10
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob,
- -13 + 10*np.log10(10),
- delta = 1e-2)
-
+ self.assertAlmostEqual(
+ poob,
+ -13 + 10 * np.log10(10),
+ delta=1e-2,
+ )
+
######################################################################
fc = 3520
bandwidth = 10
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob,
- 10*np.log10(np.power(10, 0.1*(-13 + 10*np.log10(5))) + np.power(10, 0.1*(-25 + 10*np.log10(5)))),
- delta = 1e-2)
+ self.assertAlmostEqual(
+ poob,
+ 10 * np.log10(
+ np.power(
+ 10,
+ 0.1 * (-13 + 10 * np.log10(5)),
+ ) + np.power(
+ 10,
+ 0.1 * (-25 + 10 * np.log10(5)),
+ ),
+ ),
+ delta=1e-2,
+ )
#######################################################################
fc = 3525
bandwidth = 10
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob,
- 10*np.log10(np.power(10, 0.1*(-25 + 10*np.log10(5))) + np.power(10, 0.1*(-30 + 10*np.log10(5)))),
- delta = 1e-2)
+ self.assertAlmostEqual(
+ poob,
+ 10 * np.log10(
+ np.power(
+ 10,
+ 0.1 * (-25 + 10 * np.log10(5)),
+ ) + np.power(
+ 10,
+ 0.1 * (-30 + 10 * np.log10(5)),
+ ),
+ ),
+ delta=1e-2,
+ )
#######################################################################
fc = 3600
bandwidth = 50
poob = self.mask_ue.power_calc(fc, bandwidth)
- self.assertAlmostEqual(poob,
- -30 + 10*np.log10(50),
- delta = 1e-2)
+ self.assertAlmostEqual(
+ poob,
+ -30 + 10 * np.log10(50),
+ delta=1e-2,
+ )
+
-
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_spectral_mask_imt.py b/tests/test_spectral_mask_imt.py
index bed233744..4b8521e4a 100644
--- a/tests/test_spectral_mask_imt.py
+++ b/tests/test_spectral_mask_imt.py
@@ -11,8 +11,9 @@
from sharc.mask.spectral_mask_imt import SpectralMaskImt
from sharc.support.enumerations import StationType
+
class SpectalMaskImtTest(unittest.TestCase):
-
+
def setUp(self):
# Initialize variables for 40 GHz
sta_type = StationType.IMT_BS
@@ -20,27 +21,48 @@ def setUp(self):
freq = 43000
band = 200
spurious = -13
-
+
# Create mask for 40 GHz
self.mask_bs_40GHz = SpectralMaskImt(sta_type, freq, band, spurious)
- self.mask_bs_40GHz.set_mask(power = p_tx)
-
+ self.mask_bs_40GHz.set_mask(p_tx)
+
# Initialize variables for 40 GHz
sta_type = StationType.IMT_BS
p_tx = 28.1
freq = 24350
band = 200
-
+
# Create mask for BS at 26 GHz
self.mask_bs_26GHz = SpectralMaskImt(sta_type, freq, band, spurious)
- self.mask_bs_26GHz.set_mask(power = p_tx)
+ self.mask_bs_26GHz.set_mask(p_tx)
# Create mask for UE at 26 GHz
sta_type = StationType.IMT_UE
self.mask_ue_26GHz = SpectralMaskImt(sta_type, freq, band, spurious)
- self.mask_ue_26GHz.set_mask(power = p_tx)
-
-
+ self.mask_ue_26GHz.set_mask(p_tx)
+
+ # Initialize variables for 9GHz -13dBm/MHz
+ sta_type = StationType.IMT_BS
+ p_tx = 28.1
+ freq = 9000
+ band = 200
+
+ # Create mask for BS at 9 GHz
+ self.mask_bs_9GHz = SpectralMaskImt(sta_type, freq, band, -13)
+ self.mask_bs_9GHz.set_mask(p_tx)
+
+ # Initialize variables for 9GHz -30dBm/MHz
+ sta_type = StationType.IMT_BS
+ p_tx = 28.1
+ freq = 9000
+ band = 200
+
+ # Create mask for BS at 9 GHz and spurious emission at -30dBm/MHz
+ self.mask_bs_9GHz_30_spurious = SpectralMaskImt(
+ sta_type, freq, band, -30,
+ )
+ self.mask_bs_9GHz_30_spurious.set_mask(p_tx)
+
def test_power_calc(self):
#######################################################################
# Testing mask for 40 GHz
@@ -52,29 +74,29 @@ def test_power_calc(self):
with self.assertWarns(RuntimeWarning):
poob = self.mask_bs_40GHz.power_calc(fc, band)
self.assertAlmostEqual(poob, -np.inf, delta=1e-2)
-
+
# Test 2
fc = 43300
band = 600
poob = self.mask_bs_40GHz.power_calc(fc, band)
self.assertAlmostEqual(poob, 11.8003, delta=1e-2)
-
+
# Test 3
fc = 43000
band = 1200
poob = self.mask_bs_40GHz.power_calc(fc, band)
self.assertAlmostEqual(poob, 14.8106, delta=1e-2)
-
+
# Test 4
fc = 45000
band = 1000
poob = self.mask_bs_40GHz.power_calc(fc, band)
- self.assertAlmostEqual(poob, 17, delta=1e-2)
-
+ self.assertAlmostEqual(poob, 17, delta=1e-2)
+
#######################################################################
# Testing mask for 26 GHz
#######################################################################
-
+
# Test 1 - BS
fc = 23800
band = 400
@@ -92,7 +114,7 @@ def test_power_calc(self):
band = 400
poob = self.mask_bs_26GHz.power_calc(fc, band)
self.assertAlmostEqual(poob, 13.02, delta=1e-2)
-
+
# Test 1 - UE
fc = 23800
band = 400
@@ -111,7 +133,54 @@ def test_power_calc(self):
poob = self.mask_ue_26GHz.power_calc(fc, band)
self.assertAlmostEqual(poob, 13.02, delta=1e-2)
+ #######################################################################
+ # Testing mask for 9 GHz and -13dBm/MHz spurious emissions (alternative mask, for BS only)
+ #######################################################################
+
+ # Test 1 - BS
+ fc = 9200
+ band = 200
+ poob = self.mask_bs_9GHz.power_calc(fc, band)
+ # for this test to pass, alternative mask sample size needed to be at
+ # least approx. 30 for OOB start
+ self.assertAlmostEqual(poob, 10.09, delta=1e-2)
+
+ # Test 2 - BS
+ fc = 8800
+ band = 200
+ poob = self.mask_bs_9GHz.power_calc(fc, band)
+ self.assertAlmostEqual(poob, 10.09, delta=1e-2)
+
+ # Test 3 - BS
+ fc = 9400
+ band = 400
+ poob = self.mask_bs_9GHz.power_calc(fc, band)
+ self.assertAlmostEqual(poob, 13.02, delta=1e-2)
+
+ #######################################################################
+ # Testing mask for 9 GHz and -30dBm/MHz spurious emissions (alternative mask, for BS only)
+ #######################################################################
+
+ # Test 1 - BS
+ fc = 9200
+ band = 200
+ poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band)
+ # for this test to pass, 'ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE' needed
+ # to be at least approx. 40
+ self.assertAlmostEqual(poob, 8.26, delta=1e-2)
+
+ # Test 2 - BS
+ fc = 8800
+ band = 200
+ poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band)
+ self.assertAlmostEqual(poob, 8.26, delta=1e-2)
+
+ # Test 3 - BS
+ fc = 9400
+ band = 400
+ poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band)
+ self.assertAlmostEqual(poob, 8.14, delta=1e-2)
+
-
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_spectral_mask_mss.py b/tests/test_spectral_mask_mss.py
new file mode 100644
index 000000000..03ad934c1
--- /dev/null
+++ b/tests/test_spectral_mask_mss.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Dec 5 11:56:10 2017
+
+@author: Calil
+"""
+
+import unittest
+import numpy as np
+import numpy.testing as npt
+
+from sharc.mask.spectral_mask_mss import SpectralMaskMSS
+
+
+class SpectalMaskMSSTest(unittest.TestCase):
+ def test_power_calc(self):
+ # Test 1
+ p_tx_density = -30 # dBW / Hz
+ freq = 2190
+ band = 1
+ p_tx = p_tx_density + 10 * np.log10(band * 1e6)
+
+ # reference bw is 4khz below 15GHz center freq
+ p_tx_over_4khz = p_tx_density + 10 * np.log10(4e3)
+
+ # dBm/MHz
+ spurious_emissions = -30
+
+ mask = SpectralMaskMSS(
+ freq, band, spurious_emissions,
+ )
+ mask.set_mask(p_tx)
+
+ N = len(mask.delta_f_lim)
+ # N = 2
+
+ should_eq = np.zeros(2 * N)
+ eq = np.zeros(2 * N)
+ spurious_start = 2 * band
+ for i in range(N):
+ f_offset = band / 2 + i * 4e-3
+
+ F = (f_offset - band / 2) / band * 100
+
+ should_eq[2 * i] = p_tx_over_4khz - 40 * np.log10(F / 50 + 1) + 30
+ eq[2 * i] = mask.power_calc(freq + f_offset + 0.5 * 4e-3, 4e-3)
+
+ should_eq[2 * i + 1] = should_eq[2 * i]
+ eq[2 * i + 1] = mask.power_calc(freq - f_offset - 0.5 * 4e-3, 4e-3)
+
+ # substitute last should eq with spurious emissions instead of formula
+ should_eq[-1] = spurious_emissions + 10 * np.log10(4e-3)
+ should_eq[-2] = spurious_emissions + 10 * np.log10(4e-3)
+
+ npt.assert_almost_equal(should_eq, eq)
+
+ npt.assert_equal(
+ -np.inf,
+ mask.power_calc(
+ freq, band,
+ ),
+ )
+
+ fll = mask.power_calc(freq + 1.5 * band, 2 * band)
+
+ # this test only passes when considering 0 decimal places...
+ # there is a noticeable difference in result from continous integration (by hand)
+ # and the discrete integration implemented on SHARC. Could be mitigated
+ # by implementing the mask differently
+ self.assertAlmostEqual(fll, 10 * np.log10(165.33 * 1000), places=0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_station.py b/tests/test_station.py
index 3c4256171..365e43d67 100644
--- a/tests/test_station.py
+++ b/tests/test_station.py
@@ -10,46 +10,48 @@
from sharc.support.enumerations import StationType
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
from sharc.antenna.antenna_omni import AntennaOmni
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.station import Station
+
class StationTest(unittest.TestCase):
def setUp(self):
- #Array parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_normalization = False
- self.param.bs_normalization_file = None
- self.param.bs_element_pattern = "M2101"
- self.param.bs_minimum_array_gain = -200
- self.param.bs_element_max_g = 10
- self.param.bs_element_phi_3db = 65
- self.param.bs_element_theta_3db = 75
- self.param.bs_element_am = 35
- self.param.bs_element_sla_v = 25
- self.param.bs_n_rows = 8
- self.param.bs_n_columns = 8
- self.param.bs_element_horiz_spacing = 0.5
- self.param.bs_element_vert_spacing = 0.5
- self.param.bs_multiplication_factor = 12
- self.param.bs_downtilt = 0
-
- self.param.ue_element_pattern = "M2101"
- self.param.ue_normalization = False
- self.param.ue_normalization_file = None
- self.param.ue_minimum_array_gain = -200
- self.param.ue_element_max_g = 10
- self.param.ue_element_phi_3db = 75
- self.param.ue_element_theta_3db = 65
- self.param.ue_element_am = 25
- self.param.ue_element_sla_v = 35
- self.param.ue_n_rows = 2
- self.param.ue_n_columns = 2
- self.param.ue_element_horiz_spacing = 0.5
- self.param.ue_element_vert_spacing = 0.5
- self.param.ue_multiplication_factor = 12
+ # Array parameters
+ self.bs_param = ParametersAntennaImt()
+ self.ue_param = ParametersAntennaImt()
+
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.normalization = False
+ self.bs_param.normalization_file = None
+ self.bs_param.element_pattern = "M2101"
+ self.bs_param.minimum_array_gain = -200
+ self.bs_param.element_max_g = 10
+ self.bs_param.element_phi_3db = 65
+ self.bs_param.element_theta_3db = 75
+ self.bs_param.element_am = 35
+ self.bs_param.element_sla_v = 25
+ self.bs_param.n_rows = 8
+ self.bs_param.n_columns = 8
+ self.bs_param.element_horiz_spacing = 0.5
+ self.bs_param.element_vert_spacing = 0.5
+ self.bs_param.multiplication_factor = 12
+ self.bs_param.downtilt = 0
+
+ self.ue_param.element_pattern = "M2101"
+ self.ue_param.normalization = False
+ self.ue_param.normalization_file = None
+ self.ue_param.minimum_array_gain = -200
+ self.ue_param.element_max_g = 10
+ self.ue_param.element_phi_3db = 75
+ self.ue_param.element_theta_3db = 65
+ self.ue_param.element_am = 25
+ self.ue_param.element_sla_v = 35
+ self.ue_param.n_rows = 2
+ self.ue_param.n_columns = 2
+ self.ue_param.element_horiz_spacing = 0.5
+ self.ue_param.element_vert_spacing = 0.5
+ self.ue_param.multiplication_factor = 12
self.station = Station()
self.station.id = 1
@@ -59,8 +61,8 @@ def setUp(self):
self.station.height = 6
self.station.tx_power = 20
self.station.rx_power = -3
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.station.antenna = AntennaBeamformingImt(par,300,-10)
+ par = self.bs_param.get_antenna_parameters()
+ self.station.antenna = AntennaBeamformingImt(par, 300, -10)
self.station2 = Station()
self.station2.id = 1
@@ -70,8 +72,8 @@ def setUp(self):
self.station2.height = 6
self.station2.tx_power = 17
self.station2.rx_power = 9
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.station2.antenna = AntennaBeamformingImt(par,270,2)
+ par = self.ue_param.get_antenna_parameters()
+ self.station2.antenna = AntennaBeamformingImt(par, 270, 2)
self.station3 = Station()
self.station3.id = 2
@@ -87,7 +89,7 @@ def test_id(self):
self.assertEqual(self.station.id, 1)
def test_station_type(self):
- self.assertEqual(self.station.station_type,StationType.IMT_BS)
+ self.assertEqual(self.station.station_type, StationType.IMT_BS)
def test_x(self):
self.assertEqual(self.station.x, 10)
diff --git a/tests/test_station_factory.py b/tests/test_station_factory.py
index 96cf8cf39..004fa869c 100644
--- a/tests/test_station_factory.py
+++ b/tests/test_station_factory.py
@@ -9,16 +9,162 @@
import numpy as np
import numpy.testing as npt
+from sharc.parameters.imt.parameters_imt import ParametersImt
from sharc.station_factory import StationFactory
+from sharc.topology.topology_ntn import TopologyNTN
+from sharc.parameters.parameters_single_space_station import ParametersSingleSpaceStation
+
class StationFactoryTest(unittest.TestCase):
-
+ """Test cases for StationFactory."""
+
def setUp(self):
- pass
-
+ self.station_factory = StationFactory()
+
def test_generate_imt_base_stations(self):
pass
-
-
+
+ def test_generate_imt_base_stations_ntn(self):
+ """Test for IMT-NTN space station generation."""
+ seed = 100
+ rng = np.random.RandomState(seed)
+
+ param_imt = ParametersImt()
+ param_imt.topology.type = "NTN"
+
+ # Paramters for IMT-NTN
+ param_imt.topology.ntn.bs_height = 1200000 # meters
+ param_imt.topology.ntn.cell_radius = 45000 # meters
+ param_imt.topology.ntn.bs_azimuth = 60 # degrees
+ param_imt.topology.ntn.bs_elevation = 45 # degrees
+ param_imt.topology.ntn.num_sectors = 1
+
+ ntn_topology = TopologyNTN(
+ param_imt.topology.ntn.intersite_distance,
+ param_imt.topology.ntn.cell_radius,
+ param_imt.topology.ntn.bs_height,
+ param_imt.topology.ntn.bs_azimuth,
+ param_imt.topology.ntn.bs_elevation,
+ param_imt.topology.ntn.num_sectors,
+ )
+
+ ntn_topology.calculate_coordinates()
+ ntn_bs = StationFactory.generate_imt_base_stations(param_imt, param_imt.bs.antenna.array, ntn_topology, rng)
+ npt.assert_equal(ntn_bs.height, param_imt.topology.ntn.bs_height)
+ # the azimuth seen from BS antenna
+ npt.assert_almost_equal(ntn_bs.azimuth[0], param_imt.topology.ntn.bs_azimuth - 180, 1e-3)
+ # Elevation w.r.t to xy plane
+ npt.assert_almost_equal(ntn_bs.elevation[0], -45.0, 1e-2)
+ npt.assert_almost_equal(
+ ntn_bs.x, param_imt.topology.ntn.bs_height *
+ np.tan(np.radians(param_imt.topology.ntn.bs_elevation)) *
+ np.cos(np.radians(param_imt.topology.ntn.bs_azimuth)), 1e-2,
+ )
+
+ def test_generate_imt_ue_outdoor_ntn(self):
+ """Basic test for IMT UE NTN generation."""
+ seed = 100
+ rng = np.random.RandomState(seed)
+
+ # Parameters used for IMT-NTN and UE distribution
+ param_imt = ParametersImt()
+ param_imt.topology.type = "NTN"
+ param_imt.ue.azimuth_range = (-180, 180)
+ param_imt.ue.distribution_type = "ANGLE_AND_DISTANCE"
+ param_imt.ue.distribution_azimuth = "UNIFORM"
+ param_imt.ue.distribution_distance = "UNIFORM"
+ param_imt.ue.k = 1000
+
+ # Paramters for IMT-NTN
+ param_imt.topology.ntn.bs_height = 1200000 # meters
+ param_imt.topology.ntn.cell_radius = 45000 # meters
+ param_imt.topology.ntn.bs_azimuth = 60 # degrees
+ param_imt.topology.ntn.bs_elevation = 45 # degrees
+ param_imt.topology.ntn.num_sectors = 1
+
+ ntn_topology = TopologyNTN(
+ param_imt.topology.ntn.intersite_distance,
+ param_imt.topology.ntn.cell_radius,
+ param_imt.topology.ntn.bs_height,
+ param_imt.topology.ntn.bs_azimuth,
+ param_imt.topology.ntn.bs_elevation,
+ param_imt.topology.ntn.num_sectors,
+ )
+
+ ntn_topology.calculate_coordinates()
+ ntn_ue = StationFactory.generate_imt_ue_outdoor(param_imt, param_imt.ue.antenna.array, rng, ntn_topology)
+ dist = np.sqrt(ntn_ue.x**2 + ntn_ue.y**2)
+ # test if the maximum distance is close to the cell radius within a 100km range
+ npt.assert_almost_equal(dist.max(), param_imt.topology.ntn.cell_radius, -2)
+
+ def test_generate_single_space_station(self):
+ """Basic test for space station generation."""
+ seed = 100
+
+ param = ParametersSingleSpaceStation()
+ # just passing required parameters:
+ param.frequency = 8000
+ param.bandwidth = 100
+ param.channel_model = "P619"
+ param.tx_power_density = -200
+ param.geometry.es_altitude = 0
+ param.geometry.azimuth.fixed = 0
+ param.antenna.pattern = "OMNI"
+ param.antenna.gain = 10
+
+ param.geometry.location.type = "FIXED"
+ param.geometry.altitude = 35786000.0
+ param.geometry.es_lat_deg = 0
+ param.geometry.es_long_deg = 0
+ param.geometry.location.fixed.lat_deg = 0
+ param.geometry.location.fixed.long_deg = 0
+
+ param.propagate_parameters()
+ # This should not error on this test:
+ param.validate()
+
+ # experimental from simulator
+ max_gso_fov = 81.30784
+
+ def get_ground_elevation(ss):
+ return np.rad2deg(np.arctan2(ss.height, np.sqrt(ss.x**2 + ss.y**2)))
+
+ space_station = StationFactory.generate_single_space_station(param)
+
+ # test if the maximum distance is close to the cell radius within a 100km range
+ npt.assert_almost_equal(space_station.height, param.geometry.altitude)
+ npt.assert_almost_equal(get_ground_elevation(space_station), 90)
+
+ param.geometry.es_lat_deg = max_gso_fov
+
+ space_station = StationFactory.generate_single_space_station(param)
+
+ npt.assert_almost_equal(get_ground_elevation(space_station), 0, 5)
+ npt.assert_almost_equal(space_station.height, 0, 0)
+
+ param.geometry.es_lat_deg = 0
+ param.geometry.es_long_deg = max_gso_fov
+
+ space_station = StationFactory.generate_single_space_station(param)
+
+ npt.assert_almost_equal(get_ground_elevation(space_station), 0, 5)
+ npt.assert_almost_equal(space_station.height, 0, 0)
+
+ param.geometry.es_long_deg = 0
+ param.geometry.location.fixed.lat_deg = max_gso_fov
+
+ space_station = StationFactory.generate_single_space_station(param)
+
+ npt.assert_almost_equal(get_ground_elevation(space_station), 0, 5)
+ npt.assert_almost_equal(space_station.height, 0, 0)
+
+ param.geometry.location.fixed.lat_deg = 0
+ param.geometry.location.fixed.long_deg = max_gso_fov
+
+ space_station = StationFactory.generate_single_space_station(param)
+ npt.assert_almost_equal(get_ground_elevation(space_station), 0, 5)
+ npt.assert_almost_equal(space_station.height, 0, 0)
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_station_factory_ngso.py b/tests/test_station_factory_ngso.py
new file mode 100644
index 000000000..5d863fc5d
--- /dev/null
+++ b/tests/test_station_factory_ngso.py
@@ -0,0 +1,152 @@
+import unittest
+from sharc.parameters.parameters_mss_d2d import ParametersOrbit, ParametersMssD2d
+from sharc.parameters.imt.parameters_imt import ParametersImt
+from sharc.topology.topology_single_base_station import TopologySingleBaseStation
+from sharc.support.enumerations import StationType
+from sharc.station_factory import StationFactory
+from sharc.station_manager import StationManager
+from sharc.support.sharc_geom import GeometryConverter, lla2ecef
+
+import numpy as np
+import numpy.testing as npt
+
+
+class StationFactoryNgsoTest(unittest.TestCase):
+ def setUp(self):
+ # Adding multiple shells to this constellation
+ # Creating orbital parameters for the first orbit
+ orbit_1 = ParametersOrbit(
+ n_planes=20, # Number of orbital planes
+ sats_per_plane=32, # Satellites per plane
+ phasing_deg=3.9, # Phasing angle in degrees
+ long_asc_deg=18.0, # Longitude of ascending node
+ inclination_deg=54.5, # Orbital inclination in degrees
+ perigee_alt_km=525.0, # Perigee altitude in kilometers
+ apogee_alt_km=525.0, # Apogee altitude in kilometers
+ )
+
+ # Creating orbital parameters for the second orbit
+ orbit_2 = ParametersOrbit(
+ n_planes=12, # Number of orbital planes
+ sats_per_plane=20, # Satellites per plane
+ phasing_deg=2.0, # Phasing angle in degrees
+ long_asc_deg=30.0, # Longitude of ascending node
+ inclination_deg=26.0, # Orbital inclination in degrees
+ perigee_alt_km=580.0, # Perigee altitude in kilometers
+ apogee_alt_km=580.0, # Apogee altitude in kilometers
+ )
+
+ # Creating an NGSO constellation and adding the defined orbits
+ self.lat = -15.7801
+ self.long = -47.9292
+ self.alt = 1200
+
+ self.geoconvert = GeometryConverter()
+ self.geoconvert.set_reference(
+ -15.7801,
+ -47.9292,
+ 1200,
+ )
+ self.param = ParametersMssD2d(
+ name="Acme-Star-1", # Name of the constellation
+ antenna_pattern="ITU-R-S.1528-Taylor", # Antenna type
+ orbits=[orbit_1, orbit_2], # List of orbital parameters
+ num_sectors=1,
+ )
+ self.param.antenna_s1528.frequency = 43000.0
+ self.param.antenna_s1528.bandwidth = 500.0
+ self.param.antenna_s1528.antenna_gain = 46.6
+
+ # Creating an IMT topology
+ imt_topology = TopologySingleBaseStation(
+ cell_radius=500,
+ num_clusters=2,
+ )
+
+ # random number generator
+ self.seed = 42
+ rng = np.random.RandomState(seed=self.seed)
+
+ self.ngso_manager = StationFactory.generate_mss_d2d(self.param, rng, self.geoconvert)
+
+ def test_ngso_manager(self):
+ self.assertEqual(self.ngso_manager.station_type, StationType.MSS_D2D)
+ self.assertEqual(self.ngso_manager.num_stations, 20 * 32 + 12 * 20)
+ self.assertEqual(self.ngso_manager.x.shape, (20 * 32 + 12 * 20,))
+ self.assertEqual(self.ngso_manager.y.shape, (20 * 32 + 12 * 20,))
+ self.assertEqual(self.ngso_manager.height.shape, (20 * 32 + 12 * 20,))
+
+ def test_satellite_antenna_pointing(self):
+ # by default, satellites should always point to nadir (earth center)
+
+ # Test: check if azimuth is pointing towards correct direction
+ # y > 0 <=> azimuth < 0
+ # y < 0 <=> azimuth > 0
+ npt.assert_array_equal(np.sign(self.ngso_manager.azimuth), -np.sign(self.ngso_manager.y))
+
+ # Test: check if center of earth is 0deg off axis, and that its distance to satellite is correct
+ earth_center = StationManager(1)
+ earth_center.x = np.array([0.])
+ earth_center.y = np.array([0.])
+ x, y, z = lla2ecef(self.lat, self.long, self.alt)
+ earth_center.z = np.array([
+ -np.sqrt(
+ x * x + y * y + z * z,
+ ),
+ ])
+
+ self.assertNotAlmostEqual(earth_center.z[0], 0.)
+
+ off_axis_angle = self.ngso_manager.get_off_axis_angle(earth_center)
+ distance_to_center_of_earth = self.ngso_manager.get_3d_distance_to(earth_center)
+ distance_to_center_of_earth_should_eq = np.sqrt(
+ self.ngso_manager.x ** 2 +
+ self.ngso_manager.y ** 2 +
+ (
+ np.sqrt(
+ x * x + y * y + z * z,
+ ) + self.ngso_manager.z
+ ) ** 2,
+ )
+
+ npt.assert_allclose(off_axis_angle, 0.0, atol=1e-05)
+
+ npt.assert_allclose(
+ distance_to_center_of_earth.flatten(),
+ distance_to_center_of_earth_should_eq,
+ atol=1e-05,
+ )
+
+ def test_satellite_coordinate_reversing(self):
+ # by default, satellites should always point to nadir (earth center)
+ rng = np.random.RandomState(seed=self.seed)
+
+ ngso_original_coord = StationFactory.generate_mss_d2d(self.param, rng, self.geoconvert)
+ self.geoconvert.revert_station_2d_to_3d(ngso_original_coord)
+ # Test: check if azimuth is pointing towards correct direction
+ # y > 0 <=> azimuth < 0
+ # y < 0 <=> azimuth > 0
+ npt.assert_array_equal(np.sign(ngso_original_coord.azimuth), -np.sign(ngso_original_coord.y))
+
+ # Test: check if center of earth is 0deg off axis
+ earth_center = StationManager(1)
+ earth_center.x = np.array([0.])
+ earth_center.y = np.array([0.])
+ earth_center.z = np.array([0.])
+
+ off_axis_angle = ngso_original_coord.get_off_axis_angle(earth_center)
+
+ npt.assert_allclose(off_axis_angle, 0.0, atol=1e-05)
+
+ self.geoconvert.convert_station_3d_to_2d(ngso_original_coord)
+
+ npt.assert_allclose(self.ngso_manager.x, ngso_original_coord.x, atol=1e-500)
+ npt.assert_allclose(self.ngso_manager.y, ngso_original_coord.y, atol=1e-500)
+ npt.assert_allclose(self.ngso_manager.z, ngso_original_coord.z, atol=1e-500)
+ npt.assert_allclose(self.ngso_manager.height, ngso_original_coord.height, atol=1e-500)
+ npt.assert_allclose(self.ngso_manager.azimuth, ngso_original_coord.azimuth, atol=1e-500)
+ npt.assert_allclose(self.ngso_manager.elevation, ngso_original_coord.elevation, atol=1e-500)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_station_manager.py b/tests/test_station_manager.py
index a1dbf64a0..721fa40bb 100644
--- a/tests/test_station_manager.py
+++ b/tests/test_station_manager.py
@@ -11,7 +11,7 @@
from sharc.support.enumerations import StationType
from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt
-from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
+from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt
from sharc.station import Station
from sharc.station_manager import StationManager
@@ -19,77 +19,90 @@
class StationManagerTest(unittest.TestCase):
def setUp(self):
- #Array parameters
- self.param = ParametersAntennaImt()
-
- self.param.adjacent_antenna_model = "SINGLE_ELEMENT"
- self.param.bs_normalization = False
- self.param.bs_normalization_file = None
- self.param.bs_element_pattern = "M2101"
- self.param.bs_minimum_array_gain = -200
- self.param.bs_downtilt = 0
-
- self.param.bs_element_max_g = 10
- self.param.bs_element_phi_3db = 65
- self.param.bs_element_theta_3db = 75
- self.param.bs_element_am = 35
- self.param.bs_element_sla_v = 25
- self.param.bs_n_rows = 8
- self.param.bs_n_columns = 8
- self.param.bs_element_horiz_spacing = 0.5
- self.param.bs_element_vert_spacing = 0.5
- self.param.bs_multiplication_factor = 12
-
- self.param.ue_element_pattern = "M2101"
- self.param.ue_normalization = False
- self.param.ue_normalization_file = None
- self.param.ue_minimum_array_gain = -200
-
- self.param.ue_element_max_g = 10
- self.param.ue_element_phi_3db = 75
- self.param.ue_element_theta_3db = 65
- self.param.ue_element_am = 25
- self.param.ue_element_sla_v = 35
- self.param.ue_n_rows = 2
- self.param.ue_n_columns = 2
- self.param.ue_element_horiz_spacing = 0.5
- self.param.ue_element_vert_spacing = 0.5
- self.param.ue_multiplication_factor = 12
+ # Array parameters
+ self.bs_param = ParametersAntennaImt()
+ self.ue_param = ParametersAntennaImt()
+
+ self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT"
+ self.bs_param.normalization = False
+ self.bs_param.normalization_file = None
+ self.bs_param.element_pattern = "M2101"
+ self.bs_param.minimum_array_gain = -200
+ self.bs_param.downtilt = 0
+
+ self.bs_param.element_max_g = 10
+ self.bs_param.element_phi_3db = 65
+ self.bs_param.element_theta_3db = 75
+ self.bs_param.element_am = 35
+ self.bs_param.element_sla_v = 25
+ self.bs_param.n_rows = 8
+ self.bs_param.n_columns = 8
+ self.bs_param.element_horiz_spacing = 0.5
+ self.bs_param.element_vert_spacing = 0.5
+ self.bs_param.multiplication_factor = 12
+
+ self.ue_param.element_pattern = "M2101"
+ self.ue_param.normalization = False
+ self.ue_param.normalization_file = None
+ self.ue_param.minimum_array_gain = -200
+
+ self.ue_param.element_max_g = 10
+ self.ue_param.element_phi_3db = 75
+ self.ue_param.element_theta_3db = 65
+ self.ue_param.element_am = 25
+ self.ue_param.element_sla_v = 35
+ self.ue_param.n_rows = 2
+ self.ue_param.n_columns = 2
+ self.ue_param.element_horiz_spacing = 0.5
+ self.ue_param.element_vert_spacing = 0.5
+ self.ue_param.multiplication_factor = 12
self.station_manager = StationManager(3)
self.station_manager.x = np.array([10, 20, 30])
self.station_manager.y = np.array([15, 25, 35])
+ self.station_manager.z = np.array([1, 2, 3])
self.station_manager.height = np.array([1, 2, 3])
self.station_manager.intersite_dist = 100.0
# this is for downlink
self.station_manager.tx_power = dict({0: [27, 30], 1: [35], 2: [40]})
self.station_manager.rx_power = np.array([-50, -35, -10])
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.station_manager.antenna = np.array([AntennaBeamformingImt(par,60,-10), AntennaBeamformingImt(par,180,-10), AntennaBeamformingImt(par,300,-10)])
+ par = self.bs_param.get_antenna_parameters()
+ self.station_manager.antenna = np.array([
+ AntennaBeamformingImt(
+ par, 60, -10,
+ ), AntennaBeamformingImt(par, 180, -10), AntennaBeamformingImt(par, 300, -10),
+ ])
self.station_manager.station_type = StationType.IMT_BS
self.station_manager2 = StationManager(2)
self.station_manager2.x = np.array([100, 200])
self.station_manager2.y = np.array([105, 250])
+ self.station_manager2.z = np.array([4, 5])
self.station_manager2.height = np.array([4, 5])
self.station_manager2.intersite_dist = 100.0
# this is for downlink
- self.station_manager2.tx_power = dict({0: [25], 1: [28,35]})
+ self.station_manager2.tx_power = dict({0: [25], 1: [28, 35]})
self.station_manager2.rx_power = np.array([-50, -35])
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.station_manager2.antenna = np.array([AntennaBeamformingImt(par,0,-5), AntennaBeamformingImt(par,180,-5)])
+ par = self.bs_param.get_antenna_parameters()
+ self.station_manager2.antenna = np.array(
+ [AntennaBeamformingImt(par, 0, -5), AntennaBeamformingImt(par, 180, -5)],
+ )
self.station_manager2.station_type = StationType.IMT_BS
self.station_manager3 = StationManager(1)
self.station_manager3.x = np.array([300])
self.station_manager3.y = np.array([400])
+ self.station_manager3.z = np.array([2])
self.station_manager3.height = np.array([2])
self.station_manager3.intersite_dist = 100.0
# this is for uplink
self.station_manager3.tx_power = 22
- self.station_manager3.rx_power = np.array([-50,-35])
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.station_manager3.antenna = np.array([AntennaBeamformingImt(par,0,-30), AntennaBeamformingImt(par,35,45)])
+ self.station_manager3.rx_power = np.array([-50, -35])
+ par = self.ue_param.get_antenna_parameters()
+ self.station_manager3.antenna = np.array(
+ [AntennaBeamformingImt(par, 0, -30), AntennaBeamformingImt(par, 35, 45)],
+ )
self.station_manager3.station_type = StationType.IMT_UE
self.station = Station()
@@ -99,8 +112,8 @@ def setUp(self):
self.station.height = 1
self.station.tx_power = 30
self.station.rx_power = -50
- par = self.param.get_antenna_parameters(StationType.IMT_UE)
- self.station.antenna = AntennaBeamformingImt(par,100,-10)
+ par = self.ue_param.get_antenna_parameters()
+ self.station.antenna = AntennaBeamformingImt(par, 100, -10)
self.station.station_type = StationType.IMT_UE
self.station2 = Station()
@@ -110,11 +123,10 @@ def setUp(self):
self.station2.height = 2
self.station2.tx_power = 35
self.station2.rx_power = -35
- par = self.param.get_antenna_parameters(StationType.IMT_BS)
- self.station2.antenna = AntennaBeamformingImt(par,-90,-15)
+ par = self.bs_param.get_antenna_parameters()
+ self.station2.antenna = AntennaBeamformingImt(par, -90, -15)
self.station2.station_type = StationType.IMT_BS
-
def test_num_stations(self):
self.assertEqual(self.station_manager.num_stations, 3)
self.assertEqual(self.station_manager2.num_stations, 2)
@@ -122,85 +134,107 @@ def test_num_stations(self):
def test_station_type(self):
self.assertEqual(self.station_manager.station_type, StationType.IMT_BS)
- self.assertEqual(self.station_manager2.station_type, StationType.IMT_BS)
- self.assertEqual(self.station_manager3.station_type, StationType.IMT_UE)
+ self.assertEqual(
+ self.station_manager2.station_type,
+ StationType.IMT_BS,
+ )
+ self.assertEqual(
+ self.station_manager3.station_type,
+ StationType.IMT_UE,
+ )
def test_x(self):
# get a single value from the original array
self.assertEqual(self.station_manager.x[0], 10)
# get two specific values
- npt.assert_array_equal(self.station_manager.x[[1,2]], [20,30])
+ npt.assert_array_equal(self.station_manager.x[[1, 2]], [20, 30])
# get values in reverse order
- npt.assert_array_equal(self.station_manager.x[[2,1,0]], [30,20,10])
+ npt.assert_array_equal(self.station_manager.x[[2, 1, 0]], [30, 20, 10])
# get all values (no need to specify the id's)
- npt.assert_array_equal(self.station_manager.x, [10,20,30])
+ npt.assert_array_equal(self.station_manager.x, [10, 20, 30])
# set a single value and get it
self.station_manager.x[0] = 8
- npt.assert_array_equal(self.station_manager.x[[0,1]], [8,20])
+ npt.assert_array_equal(self.station_manager.x[[0, 1]], [8, 20])
# set two values and then get all values
- self.station_manager.x[[1,2]] = [16,32]
- npt.assert_array_equal(self.station_manager.x, [8,16,32])
+ self.station_manager.x[[1, 2]] = [16, 32]
+ npt.assert_array_equal(self.station_manager.x, [8, 16, 32])
def test_y(self):
# get a single value from the original array
self.assertEqual(self.station_manager.y[0], 15)
# get two specific values
- npt.assert_array_equal(self.station_manager.y[[1,2]], [25,35])
+ npt.assert_array_equal(self.station_manager.y[[1, 2]], [25, 35])
# get values in reverse order
- npt.assert_array_equal(self.station_manager.y[[2,1,0]], [35,25,15])
+ npt.assert_array_equal(self.station_manager.y[[2, 1, 0]], [35, 25, 15])
# get all values (no need to specify the id's)
- npt.assert_array_equal(self.station_manager.y, [15,25,35])
+ npt.assert_array_equal(self.station_manager.y, [15, 25, 35])
# set a single value and get it
self.station_manager.y[1] = 9
- npt.assert_array_equal(self.station_manager.y[[0,1]], [15,9])
+ npt.assert_array_equal(self.station_manager.y[[0, 1]], [15, 9])
# set two values and then get all values
- self.station_manager.y[[0,2]] = [7,21]
- npt.assert_array_equal(self.station_manager.y, [7,9,21])
+ self.station_manager.y[[0, 2]] = [7, 21]
+ npt.assert_array_equal(self.station_manager.y, [7, 9, 21])
def test_height(self):
# get a single value from the original array
self.assertEqual(self.station_manager.height[0], 1)
# get two specific values
- npt.assert_array_equal(self.station_manager.height[[0,2]], [1,3])
+ npt.assert_array_equal(self.station_manager.height[[0, 2]], [1, 3])
# get values in reverse order
- npt.assert_array_equal(self.station_manager.height[[2,1,0]], [3,2,1])
+ npt.assert_array_equal(
+ self.station_manager.height[[2, 1, 0]], [3, 2, 1],
+ )
# get all values (no need to specify the id's)
- npt.assert_array_equal(self.station_manager.height, [1,2,3])
+ npt.assert_array_equal(self.station_manager.height, [1, 2, 3])
# set a single value and get it
self.station_manager.height[1] = 7
- npt.assert_array_equal(self.station_manager.height[[1,2]], [7,3])
+ npt.assert_array_equal(self.station_manager.height[[1, 2]], [7, 3])
# set two values and then get all values
- self.station_manager.height[[0,2]] = [5,4]
- npt.assert_array_equal(self.station_manager.height, [5,7,4])
+ self.station_manager.height[[0, 2]] = [5, 4]
+ npt.assert_array_equal(self.station_manager.height, [5, 7, 4])
def test_tx_power(self):
# get a single value from the original array
- self.assertEqual(self.station_manager.tx_power[0], [27,30])
+ self.assertEqual(self.station_manager.tx_power[0], [27, 30])
self.assertEqual(self.station_manager.tx_power[1], [35])
# get all values (no need to specify the id's)
- npt.assert_array_equal(self.station_manager.tx_power, dict({0: [27, 30], 1: [35], 2: [40]}))
+ npt.assert_array_equal(
+ self.station_manager.tx_power, dict(
+ {0: [27, 30], 1: [35], 2: [40]},
+ ),
+ )
# set a single value and get it
- self.station_manager.tx_power[0] = [33,38]
- npt.assert_array_equal(self.station_manager.tx_power[0], [33,38])
+ self.station_manager.tx_power[0] = [33, 38]
+ npt.assert_array_equal(self.station_manager.tx_power[0], [33, 38])
# set two values and then get all values
- self.station_manager.tx_power[2] = [20,25]
- npt.assert_array_equal(self.station_manager.tx_power, dict({0: [33,38], 1: [35], 2: [20,25]}))
+ self.station_manager.tx_power[2] = [20, 25]
+ npt.assert_array_equal(
+ self.station_manager.tx_power, dict(
+ {0: [33, 38], 1: [35], 2: [20, 25]},
+ ),
+ )
def test_rx_power(self):
# get a single value from the original array
self.assertEqual(self.station_manager.rx_power[2], -10)
# get two specific values
- npt.assert_array_equal(self.station_manager.rx_power[[0,1]], [-50,-35])
+ npt.assert_array_equal(
+ self.station_manager.rx_power[[0, 1]], [-50, -35],
+ )
# get values in reverse order
- npt.assert_array_equal(self.station_manager.rx_power[[2,1,0]], [-10,-35,-50] )
+ npt.assert_array_equal(
+ self.station_manager.rx_power[[2, 1, 0]], [-10, -35, -50],
+ )
# get all values (no need to specify the id's)
- npt.assert_array_equal(self.station_manager.rx_power, [-50,-35,-10])
+ npt.assert_array_equal(self.station_manager.rx_power, [-50, -35, -10])
# set a single value and get it
self.station_manager.rx_power[2] = -15
- npt.assert_array_equal(self.station_manager.rx_power[[2,0]], [-15,-50])
+ npt.assert_array_equal(
+ self.station_manager.rx_power[[2, 0]], [-15, -50],
+ )
# set two values and then get all values
- self.station_manager.rx_power[[0,1]] = [-60,-30]
- npt.assert_array_equal(self.station_manager.rx_power, [-60,-30,-15])
+ self.station_manager.rx_power[[0, 1]] = [-60, -30]
+ npt.assert_array_equal(self.station_manager.rx_power, [-60, -30, -15])
def test_antenna(self):
self.assertEqual(self.station_manager.antenna[0].azimuth, 60)
@@ -247,8 +281,10 @@ def test_station(self):
self.station.id = 1
self.assertTrue(self.station != self.station_manager.get_station(0))
# test station type
- self.assertEqual(self.station_manager.get_station(0).station_type,\
- StationType.IMT_BS)
+ self.assertEqual(
+ self.station_manager.get_station(0).station_type,
+ StationType.IMT_BS,
+ )
def test_station_list(self):
# test if manager returns the correct station list
@@ -256,7 +292,7 @@ def test_station_list(self):
self.assertTrue(self.station in station_list)
self.assertTrue(self.station2 in station_list)
#
- station_list = self.station_manager.get_station_list([0,2])
+ station_list = self.station_manager.get_station_list([0, 2])
self.assertTrue(self.station in station_list)
self.assertTrue(self.station2 not in station_list)
#
@@ -265,91 +301,136 @@ def test_station_list(self):
self.assertTrue(self.station2 not in station_list)
def test_distance_to(self):
- ref_distance = np.array([[ 356.405, 180.277]])
+ ref_distance = np.array([[356.405, 180.277]])
distance = self.station_manager3.get_distance_to(self.station_manager2)
npt.assert_allclose(distance, ref_distance, atol=1e-2)
- ref_distance = np.asarray([[ 127.279, 302.200],
- [ 113.137, 288.140],
- [ 98.994, 274.089]])
+ ref_distance = np.asarray([
+ [127.279, 302.200],
+ [113.137, 288.140],
+ [98.994, 274.089],
+ ])
distance = self.station_manager.get_distance_to(self.station_manager2)
npt.assert_allclose(distance, ref_distance, atol=1e-2)
def test_3d_distance_to(self):
- ref_distance = np.asarray([[ 356.411, 180.302]])
- distance = self.station_manager3.get_3d_distance_to(self.station_manager2)
+ ref_distance = np.asarray([[356.411, 180.302]])
+ distance = self.station_manager3.get_3d_distance_to(
+ self.station_manager2,
+ )
npt.assert_allclose(distance, ref_distance, atol=1e-2)
- ref_distance = np.asarray([[ 127.314, 302.226],
- [ 113.154, 288.156],
- [ 99, 274.096]])
- distance = self.station_manager.get_3d_distance_to(self.station_manager2)
+ ref_distance = np.asarray([
+ [127.314, 302.226],
+ [113.154, 288.156],
+ [99, 274.096],
+ ])
+ distance = self.station_manager.get_3d_distance_to(
+ self.station_manager2,
+ )
npt.assert_allclose(distance, ref_distance, atol=1e-2)
-
+
def test_wrap_around(self):
self.station_manager = StationManager(2)
self.station_manager.x = np.array([0, 150])
self.station_manager.y = np.array([0, -32])
+ self.station_manager.z = np.array([4, 5])
self.station_manager.height = np.array([4, 5])
self.station_manager.intersite_dist = 100.0
-
+
self.station_manager2 = StationManager(3)
- self.station_manager2.x = np.array([10, 200, 30])
+ self.station_manager2.x = np.array([10, 200, 30])
self.station_manager2.y = np.array([15, 250, -350])
+ self.station_manager2.z = np.array([1, 2, 3])
self.station_manager2.height = np.array([1, 2, 3])
-
+
# 2D Distance
- d_2D, d_3D, phi, theta = self.station_manager.get_dist_angles_wrap_around(self.station_manager2)
- ref_d_2D = np.asarray([[ 18.03, 150.32, 85.39],
- [ 147.68, 181.12, 205.25]])
+ d_2D, d_3D, phi, theta = self.station_manager.get_dist_angles_wrap_around(
+ self.station_manager2,
+ )
+ ref_d_2D = np.asarray([
+ [18.03, 150.32, 85.39],
+ [147.68, 181.12, 205.25],
+ ])
npt.assert_allclose(d_2D, ref_d_2D, atol=1e-2)
-
+
# 3D Distance
- ref_d_3D = np.asarray([[ 18.27, 150.33, 85.39],
- [ 147.73, 181.15, 205.26]])
+ ref_d_3D = np.asarray([
+ [18.27, 150.33, 85.39],
+ [147.73, 181.15, 205.26],
+ ])
npt.assert_allclose(d_3D, ref_d_3D, atol=1e-2)
-
+
# Point vec
- ref_phi = np.asarray([[ 56.31, -176.26 , 103.55],
- [ 161.44, -56.49, 145.92]])
+ ref_phi = np.asarray([
+ [56.31, -176.26, 103.55],
+ [161.44, -56.49, 145.92],
+ ])
npt.assert_allclose(phi, ref_phi, atol=1e-2)
-
- ref_theta = np.asarray([[ 99.45, 90.76, 90.67],
- [ 91.55, 90.95, 90.56]])
- npt.assert_allclose(theta,ref_theta, atol=1e-2)
+
+ ref_theta = np.asarray([
+ [99.45, 90.76, 90.67],
+ [91.55, 90.95, 90.56],
+ ])
+ npt.assert_allclose(theta, ref_theta, atol=1e-2)
def test_pointing_vector_to(self):
eps = 1e-1
# Test 1
- phi, theta = self.station_manager.get_pointing_vector_to(self.station_manager2)
- npt.assert_allclose(phi,np.array([[45.00, 51.04],
- [45.00, 51.34],
- [45.00, 51.67]]),atol=eps)
- npt.assert_allclose(theta,np.array([[88.65, 89.24],
- [88.89, 89.40],
- [89.42, 89.58]]),atol=eps)
+ phi, theta = self.station_manager.get_pointing_vector_to(
+ self.station_manager2,
+ )
+ npt.assert_allclose(
+ phi, np.array([
+ [45.00, 51.04],
+ [45.00, 51.34],
+ [45.00, 51.67],
+ ]), atol=eps,
+ )
+ npt.assert_allclose(
+ theta, np.array([
+ [88.65, 89.24],
+ [88.89, 89.40],
+ [89.42, 89.58],
+ ]), atol=eps,
+ )
# Test 2
- phi, theta = self.station_manager2.get_pointing_vector_to(self.station_manager)
- npt.assert_allclose(phi,np.array([[-135.00, -135.00, -135.00],
- [-128.96, -128.66, -128.33]]),atol=eps)
- npt.assert_allclose(theta,np.array([[91.35, 91.01, 90.58],
- [90.76, 90.60, 90.42]]),atol=eps)
+ phi, theta = self.station_manager2.get_pointing_vector_to(
+ self.station_manager,
+ )
+ npt.assert_allclose(
+ phi, np.array([
+ [-135.00, -135.00, -135.00],
+ [-128.96, -128.66, -128.33],
+ ]), atol=eps,
+ )
+ npt.assert_allclose(
+ theta, np.array([
+ [91.35, 91.01, 90.58],
+ [90.76, 90.60, 90.42],
+ ]), atol=eps,
+ )
# Test 3
- phi, theta = self.station_manager3.get_pointing_vector_to(self.station_manager2)
- npt.assert_allclose(phi,np.array([[-124.13, -123.69]]),atol=eps)
- npt.assert_allclose(theta,np.array([[89.73, 89.05]]),atol=eps)
+ phi, theta = self.station_manager3.get_pointing_vector_to(
+ self.station_manager2,
+ )
+ npt.assert_allclose(phi, np.array([[-124.13, -123.69]]), atol=eps)
+ npt.assert_allclose(theta, np.array([[89.73, 89.05]]), atol=eps)
# Test 4
- phi, theta = self.station_manager2.get_pointing_vector_to(self.station_manager3)
- npt.assert_allclose(phi,np.array([[55.86], [56.31]]),atol=eps)
- npt.assert_allclose(theta,np.array([[90.32], [90.95]]),atol=eps)
+ phi, theta = self.station_manager2.get_pointing_vector_to(
+ self.station_manager3,
+ )
+ npt.assert_allclose(phi, np.array([[55.86], [56.31]]), atol=eps)
+ npt.assert_allclose(theta, np.array([[90.32], [90.95]]), atol=eps)
def test_off_axis_angle(self):
sm1 = StationManager(1)
sm1.x = np.array([0])
sm1.y = np.array([0])
+ sm1.z = np.array([0])
sm1.height = np.array([0])
sm1.azimuth = np.array([0])
sm1.elevation = np.array([0])
@@ -357,6 +438,7 @@ def test_off_axis_angle(self):
sm2 = StationManager(6)
sm2.x = np.array([100, 100, 0, 100, 100, 100])
sm2.y = np.array([0, 0, 100, 100, 100, 100])
+ sm2.z = np.array([0, 100, 0, 0, 100, 100])
sm2.height = np.array([0, 100, 0, 0, 100, 100])
sm2.azimuth = np.array([180, 180, 180, 180, 180, 225])
sm2.elevation = np.array([0, 0, 0, 0, 0, 0])
@@ -368,6 +450,7 @@ def test_off_axis_angle(self):
sm3 = StationManager(1)
sm3.x = np.array([0])
sm3.y = np.array([0])
+ sm3.z = np.array([0])
sm3.height = np.array([0])
sm3.azimuth = np.array([45])
sm3.elevation = np.array([0])
@@ -375,6 +458,7 @@ def test_off_axis_angle(self):
sm4 = StationManager(2)
sm4.x = np.array([100, 60])
sm4.y = np.array([100, 80])
+ sm4.z = np.array([100, 100])
sm4.height = np.array([100, 100])
sm4.azimuth = np.array([180, 180])
sm4.elevation = np.array([0, 0])
@@ -386,6 +470,7 @@ def test_off_axis_angle(self):
sm5 = StationManager(1)
sm5.x = np.array([0])
sm5.y = np.array([0])
+ sm5.z = np.array([0])
sm5.height = np.array([0])
sm5.azimuth = np.array([0])
sm5.elevation = np.array([45])
@@ -393,6 +478,7 @@ def test_off_axis_angle(self):
sm6 = StationManager(2)
sm6.x = np.array([100, 100])
sm6.y = np.array([0, 0])
+ sm6.z = np.array([100, 100])
sm6.height = np.array([100, 100])
sm6.azimuth = np.array([180, 180])
sm6.elevation = np.array([0, 0])
@@ -404,6 +490,7 @@ def test_off_axis_angle(self):
sm6 = StationManager(1)
sm6.x = np.array([0])
sm6.y = np.array([0])
+ sm6.z = np.array([100])
sm6.height = np.array([100])
sm6.azimuth = np.array([0])
sm6.elevation = np.array([270])
@@ -411,41 +498,46 @@ def test_off_axis_angle(self):
sm7 = StationManager(2)
sm7.x = np.array([0, 100])
sm7.y = np.array([0, 0])
+ sm7.z = np.array([0, 0])
sm7.height = np.array([0, 0])
sm7.azimuth = np.array([180, 180])
sm7.elevation = np.array([0, 0])
phi_ref = np.array([[0, 45]])
- npt.assert_allclose(phi_ref, sm6.get_off_axis_angle(sm7), atol=1e-2)
-
-
+ npt.assert_allclose(phi_ref, sm6.get_off_axis_angle(sm7), atol=1e-2)
+
def test_elevation(self):
sm1 = StationManager(1)
sm1.x = np.array([0])
sm1.y = np.array([0])
+ sm1.z = np.array([10])
sm1.height = np.array([10])
-
+
sm2 = StationManager(6)
- sm2.x = np.array([10, 10, 0, 0, 30, 20])
- sm2.y = np.array([ 0, 0, 5, 10, 30, 20])
- sm2.height = np.array([10, 20, 5, 0, 20, 20])
-
+ sm2.x = np.array([10, 10, 0, 0, 30, 20])
+ sm2.y = np.array([0, 0, 5, 10, 30, 20])
+ sm2.z = np.array([10, 20, 5, 0, 20, 20])
+ sm2.height = np.array([10, 20, 5, 0, 20, 20])
+
elevation_ref = np.array([[0, 45, -45, -45, 13.26, 19.47]])
- npt.assert_allclose(elevation_ref, sm1.get_elevation(sm2), atol=1e-2)
-
+ npt.assert_allclose(elevation_ref, sm1.get_elevation(sm2), atol=1e-2)
+
#######################################################################
sm3 = StationManager(2)
sm3.x = np.array([0, 30])
sm3.y = np.array([0, 0])
+ sm3.z = np.array([10, 10])
sm3.height = np.array([10, 10])
-
+
sm4 = StationManager(2)
- sm4.x = np.array([10, 10])
- sm4.y = np.array([ 0, 0])
+ sm4.x = np.array([10, 10])
+ sm4.y = np.array([0, 0])
+ sm4.z = np.array([10, 20])
sm4.height = np.array([10, 20])
-
+
elevation_ref = np.array([[0, 45], [0, 26.56]])
- npt.assert_allclose(elevation_ref, sm3.get_elevation(sm4), atol=1e-2)
+ npt.assert_allclose(elevation_ref, sm3.get_elevation(sm4), atol=1e-2)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_topology_hotspot.py b/tests/test_topology_hotspot.py
index 3e6806950..d8ddfaed0 100644
--- a/tests/test_topology_hotspot.py
+++ b/tests/test_topology_hotspot.py
@@ -7,14 +7,14 @@
import unittest
import numpy as np
-#import numpy.testing as npt
+# import numpy.testing as npt
-from sharc.parameters.parameters_hotspot import ParametersHotspot
+from sharc.parameters.imt.parameters_hotspot import ParametersHotspot
from sharc.topology.topology_hotspot import TopologyHotspot
+
class TopologyHotspotTest(unittest.TestCase):
-
-
+
def setUp(self):
# For this test case, hotspot parameters are useless because we are
# testing only the validation methods
@@ -26,25 +26,30 @@ def setUp(self):
intersite_distance = 1000
num_clusters = 1
- self.topology = TopologyHotspot(param, intersite_distance, num_clusters)
-
-
+ self.topology = TopologyHotspot(
+ param, intersite_distance, num_clusters,
+ )
+
def test_overlapping_hotspots(self):
candidate_x = np.array([300])
candidate_y = np.array([0])
candidate_azimuth = np.array([-180])
set_x = np.array([0, 200])
- set_y = np.array([0, 0])
+ set_y = np.array([0, 0])
set_azimuth = np.array([0, -180])
radius = 100
- self.assertFalse(self.topology.overlapping_hotspots(candidate_x,
- candidate_y,
- candidate_azimuth,
- set_x,
- set_y,
- set_azimuth,
- radius))
-
+ self.assertFalse(
+ self.topology.overlapping_hotspots(
+ candidate_x,
+ candidate_y,
+ candidate_azimuth,
+ set_x,
+ set_y,
+ set_azimuth,
+ radius,
+ ),
+ )
+
candidate_x = np.array([0])
candidate_y = np.array([0])
candidate_azimuth = np.array([0])
@@ -52,45 +57,56 @@ def test_overlapping_hotspots(self):
set_y = np.array([150, 400])
set_azimuth = np.array([270, 270])
radius = 100
- self.assertTrue(self.topology.overlapping_hotspots(candidate_x,
- candidate_y,
- candidate_azimuth,
- set_x,
- set_y,
- set_azimuth,
- radius))
-
+ self.assertTrue(
+ self.topology.overlapping_hotspots(
+ candidate_x,
+ candidate_y,
+ candidate_azimuth,
+ set_x,
+ set_y,
+ set_azimuth,
+ radius,
+ ),
+ )
+
candidate_x = np.array([0])
candidate_y = np.array([0])
candidate_azimuth = np.array([0])
- set_x = np.array([ -1, 101])
- set_y = np.array([ 0, 0])
+ set_x = np.array([-1, 101])
+ set_y = np.array([0, 0])
set_azimuth = np.array([180, 0])
radius = 100
- self.assertFalse(self.topology.overlapping_hotspots(candidate_x,
- candidate_y,
- candidate_azimuth,
- set_x,
- set_y,
- set_azimuth,
- radius))
-
+ self.assertFalse(
+ self.topology.overlapping_hotspots(
+ candidate_x,
+ candidate_y,
+ candidate_azimuth,
+ set_x,
+ set_y,
+ set_azimuth,
+ radius,
+ ),
+ )
+
candidate_x = np.array([1])
candidate_y = np.array([0])
candidate_azimuth = np.array([0])
- set_x = np.array([ 0])
- set_y = np.array([ 1])
+ set_x = np.array([0])
+ set_y = np.array([1])
set_azimuth = np.array([90])
radius = 100
- self.assertTrue(self.topology.overlapping_hotspots(candidate_x,
- candidate_y,
- candidate_azimuth,
- set_x,
- set_y,
- set_azimuth,
- radius))
-
-
-
+ self.assertTrue(
+ self.topology.overlapping_hotspots(
+ candidate_x,
+ candidate_y,
+ candidate_azimuth,
+ set_x,
+ set_y,
+ set_azimuth,
+ radius,
+ ),
+ )
+
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tests/test_topology_imt_mss_dc.py b/tests/test_topology_imt_mss_dc.py
new file mode 100644
index 000000000..5d2c1af57
--- /dev/null
+++ b/tests/test_topology_imt_mss_dc.py
@@ -0,0 +1,135 @@
+import unittest
+import numpy as np
+import numpy.testing as npt
+from sharc.topology.topology_imt_mss_dc import TopologyImtMssDc
+from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc
+from sharc.station_manager import StationManager
+from sharc.parameters.parameters_orbit import ParametersOrbit
+from sharc.support.sharc_geom import GeometryConverter, lla2ecef
+
+
+class TestTopologyImtMssDc(unittest.TestCase):
+ """
+ Unit tests for the TopologyImtMssDc class.
+ This test suite includes the following tests:
+ - test_initialization: Verifies the initialization of the IMT MSS-DC topology.
+ - test_calculate_coordinates: Tests the calculation of coordinates for the topology.
+ - test_visible_satellites: Checks the visibility of satellites based on elevation angle.
+ Classes:
+ TestTopologyImtMssDc: Contains unit tests for the TopologyImtMssDc class.
+ Methods:
+ setUp: Sets up the test environment, including parameters and geometry converter.
+ test_initialization: Tests the initialization of the IMT MSS-DC topology.
+ test_calculate_coordinates: Tests the calculation of coordinates for the topology.
+ test_visible_satellites: Tests the visibility of satellites based on elevation angle.
+ """
+
+ def setUp(self):
+ # Define the parameters for the IMT MSS-DC topology
+ orbit = ParametersOrbit(
+ n_planes=20,
+ sats_per_plane=32,
+ phasing_deg=3.9,
+ long_asc_deg=18.0,
+ inclination_deg=54.5,
+ perigee_alt_km=525,
+ apogee_alt_km=525,
+ )
+ self.params = ParametersImtMssDc(
+ beam_radius=36516.0,
+ num_beams=19,
+ orbits=[orbit],
+ )
+ self.params.sat_is_active_if.conditions = ["MINIMUM_ELEVATION_FROM_ES"]
+ self.params.sat_is_active_if.minimum_elevation_from_es = 5.0
+
+ # Define the geometry converter
+ self.geometry_converter = GeometryConverter()
+ self.geometry_converter.set_reference(-15.0, -42.0, 1200)
+
+ # Define the Earth center coordinates
+ self.earth_center_x = np.array([0.])
+ self.earth_center_y = np.array([0.])
+ x, y, z = lla2ecef(
+ self.geometry_converter.ref_lat,
+ self.geometry_converter.ref_long,
+ self.geometry_converter.ref_alt,
+ )
+ self.earth_center_z = np.array([-np.sqrt(x * x + y * y + z * z)])
+
+ # Instantiate the IMT MSS-DC topology
+ self.imt_mss_dc_topology = TopologyImtMssDc(self.params, self.geometry_converter)
+
+ def test_initialization(self):
+ self.assertTrue(self.imt_mss_dc_topology.is_space_station)
+ self.assertEqual(self.imt_mss_dc_topology.num_sectors, self.params.num_beams)
+ self.assertEqual(len(self.imt_mss_dc_topology.orbits), len(self.params.orbits))
+
+ def test_calculate_coordinates(self):
+ self.imt_mss_dc_topology.calculate_coordinates()
+ center_beam_idxs = np.arange(self.imt_mss_dc_topology.num_base_stations // self.imt_mss_dc_topology.num_sectors) *\
+ self.imt_mss_dc_topology.num_sectors
+ self.assertIsNotNone(self.imt_mss_dc_topology.space_station_x)
+ self.assertIsNotNone(self.imt_mss_dc_topology.space_station_y)
+ self.assertIsNotNone(self.imt_mss_dc_topology.space_station_z)
+ self.assertIsNotNone(self.imt_mss_dc_topology.elevation)
+ self.assertIsNotNone(self.imt_mss_dc_topology.azimuth)
+ self.assertEqual(len(self.imt_mss_dc_topology.space_station_x), self.imt_mss_dc_topology.num_base_stations)
+ self.assertEqual(len(self.imt_mss_dc_topology.space_station_y), self.imt_mss_dc_topology.num_base_stations)
+ self.assertEqual(len(self.imt_mss_dc_topology.space_station_z), self.imt_mss_dc_topology.num_base_stations)
+
+ # Test: check if azimuth is pointing towards correct direction
+ # y > 0 <=> azimuth < 0
+ # y < 0 <=> azimuth > 0
+ npt.assert_array_equal(
+ np.sign(self.imt_mss_dc_topology.azimuth[center_beam_idxs]),
+ -np.sign(self.imt_mss_dc_topology.space_station_y[center_beam_idxs]),
+ )
+
+ # Test: check if the altitude is calculated correctly
+ rx = self.imt_mss_dc_topology.space_station_x - self.earth_center_x
+ ry = self.imt_mss_dc_topology.space_station_y - self.earth_center_y
+ rz = self.imt_mss_dc_topology.space_station_z - self.earth_center_z
+ r = np.sqrt(rx**2 + ry**2 + rz**2)
+ expected_alt_km = (r - np.abs(self.earth_center_z)) / 1e3
+ npt.assert_array_almost_equal(expected_alt_km, self.params.orbits[0].apogee_alt_km, decimal=0)
+
+ # by default, satellites should always point to nadir (earth center)
+ ref_earth_center = StationManager(1)
+ ref_earth_center.x = self.earth_center_x
+ ref_earth_center.y = self.earth_center_y
+ ref_earth_center.z = self.earth_center_z
+
+ ref_space_stations = StationManager(self.imt_mss_dc_topology.num_base_stations)
+ ref_space_stations.x = self.imt_mss_dc_topology.space_station_x
+ ref_space_stations.y = self.imt_mss_dc_topology.space_station_y
+ ref_space_stations.z = self.imt_mss_dc_topology.space_station_z
+
+ phi, theta = ref_space_stations.get_pointing_vector_to(ref_earth_center)
+ npt.assert_array_almost_equal(
+ np.squeeze(phi[center_beam_idxs]), self.imt_mss_dc_topology.azimuth[center_beam_idxs],
+ decimal=3,
+ )
+ npt.assert_array_almost_equal(
+ np.squeeze(theta[center_beam_idxs]), 90 - self.imt_mss_dc_topology.elevation[center_beam_idxs],
+ decimal=3,
+ )
+
+ def test_visible_satellites(self):
+ self.imt_mss_dc_topology.calculate_coordinates(random_number_gen=np.random.RandomState(8))
+ min_elevation_angle = 5.0
+
+ # calculate the elevation angles with respect to the x-y plane
+ xy_plane_elevations = np.degrees(
+ np.arctan2(
+ self.imt_mss_dc_topology.space_station_z,
+ np.sqrt(self.imt_mss_dc_topology.space_station_x**2 + self.imt_mss_dc_topology.space_station_y**2),
+ ),
+ )
+ # Add a tolerance to the elevation angle because of the Earth oblateness
+ expected_atol = 2e-2
+ npt.assert_array_less(min_elevation_angle - expected_atol, xy_plane_elevations)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_topology_macrocell_.py b/tests/test_topology_macrocell_.py
index 659de69ca..31df91f68 100644
--- a/tests/test_topology_macrocell_.py
+++ b/tests/test_topology_macrocell_.py
@@ -11,11 +11,12 @@
from sharc.topology.topology_macrocell import TopologyMacrocell
+
class TopologyMacrocellTest(unittest.TestCase):
-
+
def setUp(self):
pass
-
+
def test_intersite_distance(self):
intersite_distance = 1000
num_clusters = 1
@@ -23,7 +24,7 @@ def test_intersite_distance(self):
topology.calculate_coordinates()
self.assertEqual(topology.intersite_distance, 1000)
self.assertAlmostEqual(topology.cell_radius, 666.66, places=1)
-
+
# when intersite distance changes, cell radius also changes
intersite_distance = 600
num_clusters = 1
@@ -31,7 +32,7 @@ def test_intersite_distance(self):
topology.calculate_coordinates()
self.assertEqual(topology.intersite_distance, 600)
self.assertEqual(topology.cell_radius, 400)
-
+
# let's change it one more time...
intersite_distance = 1500
num_clusters = 1
@@ -47,56 +48,64 @@ def test_num_clusters(self):
topology = TopologyMacrocell(intersite_distance, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.num_clusters, 1)
-
+
# set to 7 clusters
intersite_distance = 1000
num_clusters = 7
topology = TopologyMacrocell(intersite_distance, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.num_clusters, 7)
-
+
# set to any other value raises an exception
intersite_distance = 1000
num_clusters = 3
with self.assertRaises(ValueError):
topology = TopologyMacrocell(intersite_distance, num_clusters)
-
+
intersite_distance = 1000
num_clusters = 8
with self.assertRaises(ValueError):
topology = TopologyMacrocell(intersite_distance, num_clusters)
-
+
def test_coordinates(self):
"""
- TODO: test the case when number of clusters is 7
+ TODO: test the case when number of clusters is 7
"""
intersite_distance = 1000
num_clusters = 1
topology = TopologyMacrocell(intersite_distance, num_clusters)
topology.calculate_coordinates()
-
+
num_sites = 19
num_bs_per_site = 3
- num_bs = num_sites*num_bs_per_site
-
+ num_bs = num_sites * num_bs_per_site
+
# check the number of base stations
self.assertEqual(len(topology.x), num_bs)
self.assertEqual(len(topology.y), num_bs)
self.assertEqual(len(topology.azimuth), num_bs)
-
+
# check coordinates
- x_ref = np.repeat(np.array([0, 1000, 500, -500, -1000, -500,
- 500, 2000, 1500, 1000, 0, -1000,
- -1500, -2000, -1500, -1000, 0, 1000, 1500]), num_bs_per_site)
- y_ref = np.repeat(np.array([0, 0, 866.02, 866.02, 0, -866.02,
- -866.02, 0, 866.02, 1732.05, 1732.05, 1732.05,
- 866.02, 0, -866.02, -1732.05, -1732.05, -1732.05, -866.02]), num_bs_per_site)
+ x_ref = np.repeat(
+ np.array([
+ 0, 1000, 500, -500, -1000, -500,
+ 500, 2000, 1500, 1000, 0, -1000,
+ -1500, -2000, -1500, -1000, 0, 1000, 1500,
+ ]), num_bs_per_site,
+ )
+ y_ref = np.repeat(
+ np.array([
+ 0, 0, 866.02, 866.02, 0, -866.02,
+ -866.02, 0, 866.02, 1732.05, 1732.05, 1732.05,
+ 866.02, 0, -866.02, -1732.05, -1732.05, -1732.05, -866.02,
+ ]), num_bs_per_site,
+ )
az_ref = np.tile([60, 180, 300], num_sites)
-
+
npt.assert_allclose(topology.x, x_ref, atol=1e-2)
npt.assert_allclose(topology.y, y_ref, atol=1e-2)
npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2)
-
+
# change intersite distance and check new cell radius and coordinates
intersite_distance = 500
num_clusters = 1
@@ -109,19 +118,26 @@ def test_coordinates(self):
self.assertEqual(len(topology.azimuth), num_bs)
# check coordinates
- x_ref = np.repeat(np.array([0, 500, 250, -250, -500, -250,
- 250, 1000, 750, 500, 0, -500,
- -750, -1000, -750, -500, 0, 500, 750]), num_bs_per_site)
- y_ref = np.repeat(np.array([0, 0, 433.01, 433.01, 0, -433.01,
- -433.01, 0, 433.01, 866.02, 866.02, 866.02,
- 433.01, 0, -433.01, -866.02, -866.02, -866.02, -433.01]), num_bs_per_site)
+ x_ref = np.repeat(
+ np.array([
+ 0, 500, 250, -250, -500, -250,
+ 250, 1000, 750, 500, 0, -500,
+ -750, -1000, -750, -500, 0, 500, 750,
+ ]), num_bs_per_site,
+ )
+ y_ref = np.repeat(
+ np.array([
+ 0, 0, 433.01, 433.01, 0, -433.01,
+ -433.01, 0, 433.01, 866.02, 866.02, 866.02,
+ 433.01, 0, -433.01, -866.02, -866.02, -866.02, -433.01,
+ ]), num_bs_per_site,
+ )
az_ref = np.tile([60, 180, 300], num_sites)
-
+
npt.assert_allclose(topology.x, x_ref, atol=1e-2)
npt.assert_allclose(topology.y, y_ref, atol=1e-2)
- npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2)
+ npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2)
+
-
if __name__ == '__main__':
unittest.main()
-
\ No newline at end of file
diff --git a/tests/test_topology_ntn.py b/tests/test_topology_ntn.py
new file mode 100644
index 000000000..a7a698e26
--- /dev/null
+++ b/tests/test_topology_ntn.py
@@ -0,0 +1,186 @@
+"""
+Created on Tue Dec 28 21:41:00 2024
+
+@author: Vitor Borges
+"""
+
+import unittest
+import numpy as np
+import math
+from sharc.topology.topology_ntn import TopologyNTN
+
+
+class TopologyNTNTest(unittest.TestCase):
+
+ def setUp(self):
+ self.bs_height = 1000e3 # meters
+ self.bs_azimuth = 45 # degrees
+ self.bs_elevation = 45 # degrees
+ self.beamwidth = 10
+ denominator = math.tan(np.radians(self.beamwidth))
+ nominator = math.cos(np.radians(self.bs_elevation))
+ self.cell_radius = self.bs_height * denominator / nominator # meters
+ self.intersite_distance = self.cell_radius * np.sqrt(3) # meters
+
+ self.cos = lambda x: np.cos(np.radians(x))
+ self.sin = lambda x: np.sin(np.radians(x))
+ self.tan = lambda x: np.tan(np.radians(x))
+
+ def test_single_sector(self):
+ topology = TopologyNTN(
+ self.intersite_distance,
+ self.cell_radius,
+ self.bs_height,
+ self.bs_azimuth,
+ self.bs_elevation,
+ num_sectors=1,
+ )
+ topology.calculate_coordinates()
+ expected_x = expected_y = expected_z = [0]
+ self.assertListEqual(list(topology.x), expected_x)
+ self.assertListEqual(list(topology.y), expected_y)
+ self.assertListEqual(list(topology.z), expected_z)
+ expected_azimuth = [-135.0]
+ for actual_azi, expected_azi in zip(
+ topology.azimuth, expected_azimuth,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_azi, expected_azi, places=3)
+ expected_elevation = [-45.0]
+ for expected_elev, actual_elev in zip(
+ topology.elevation, expected_elevation,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_elev, expected_elev, places=3)
+
+ def test_seven_sectors(self):
+ topology = TopologyNTN(
+ self.intersite_distance,
+ self.cell_radius,
+ self.bs_height,
+ self.bs_azimuth,
+ self.bs_elevation,
+ num_sectors=7,
+ )
+ topology.calculate_coordinates()
+
+ d = self.intersite_distance
+
+ # defining expected x, y, z
+ expected_x = [0]
+ expected_x.extend([d * self.cos(30 + 60 * k) for k in range(6)])
+ expected_y = [0]
+ expected_y.extend([d * self.sin(30 + 60 * k) for k in range(6)])
+ expected_z = [0]
+ expected_z.extend([0 for _ in range(6)])
+
+ # testing expected x, y, z
+ for actual_x, expec_x in zip(topology.x, expected_x):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_x, expec_x, places=3)
+ for actual_y, expec_y in zip(topology.y, expected_y):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_y, expec_y, places=3)
+ self.assertListEqual(list(topology.z), expected_z)
+
+ # defining expected azimuth and elevation
+ space_x = np.repeat(topology.space_station_x, topology.num_sectors)
+ space_y = np.repeat(topology.space_station_y, topology.num_sectors)
+ space_z = np.repeat(topology.space_station_z, topology.num_sectors)
+ # using expected_x and expected_y so this test is independent of other
+ # tests
+ expected_azimuth = np.arctan2(
+ expected_y - space_y,
+ expected_x - space_x,
+ ) * 180 / np.pi
+ expected_dist_xy = np.sqrt(
+ (expected_x - space_x)**2 + (expected_y - space_y)**2,
+ )
+ # using expected_z so this test is independent of other tests
+ expected_elevation = np.arctan2(
+ expected_z - space_z,
+ expected_dist_xy,
+ ) * 180 / np.pi
+
+ # testing expected azimuth and elevation
+ for actual_azi, expected_azi in zip(
+ topology.azimuth, expected_azimuth,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_azi, expected_azi, places=3)
+ for expected_elev, actual_elev in zip(
+ topology.elevation, expected_elevation,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_elev, expected_elev, places=3)
+
+ def test_nineteen_sectors(self):
+ topology = TopologyNTN(
+ self.intersite_distance,
+ self.cell_radius,
+ self.bs_height,
+ self.bs_azimuth,
+ self.bs_elevation,
+ num_sectors=19,
+ )
+ topology.calculate_coordinates()
+
+ d = self.intersite_distance
+
+ # defining expected x, y, z
+ expected_x = [0]
+ expected_y = [0]
+ expected_x.extend([d * self.cos(30 + 60 * k) for k in range(6)])
+ expected_y.extend([d * self.sin(30 + 60 * k) for k in range(6)])
+ for k in range(6):
+ # already rotated 30 degrees
+ angle = 30 + k * 60
+ expected_x.append(2 * d * self.cos(angle))
+ expected_y.append(2 * d * self.sin(angle))
+ expected_x.append(d * self.cos(angle) + d * self.cos(angle + 60))
+ expected_y.append(d * self.sin(angle) + d * self.sin(angle + 60))
+ expected_z = [0 for _ in range(19)]
+
+ # testing expected x, y, z
+ for actual_x, expec_x in zip(topology.x, expected_x):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_x, expec_x, places=3)
+ for actual_y, expec_y in zip(topology.y, expected_y):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_y, expec_y, places=3)
+ self.assertListEqual(list(topology.z), expected_z)
+
+ # defining expected azimuth and elevation
+ space_x = np.repeat(topology.space_station_x, topology.num_sectors)
+ space_y = np.repeat(topology.space_station_y, topology.num_sectors)
+ space_z = np.repeat(topology.space_station_z, topology.num_sectors)
+ # using expected_x and expected_y so this test is independent of other
+ # tests
+ expected_azimuth = np.arctan2(
+ expected_y - space_y,
+ expected_x - space_x,
+ ) * 180 / np.pi
+ expected_distance_xy = np.sqrt(
+ (expected_x - space_x)**2 + (expected_y - space_y)**2,
+ )
+ # using expected_z so this test is independent of other tests
+ expected_elevation = np.arctan2(
+ expected_z - space_z,
+ expected_distance_xy,
+ ) * 180 / np.pi
+
+ # testing expected azimuth and elevation
+ for actual_azi, expected_azi in zip(
+ topology.azimuth, expected_azimuth,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_azi, expected_azi, places=3)
+ for expected_elev, actual_elev in zip(
+ topology.elevation, expected_elevation,
+ ):
+ # cannot check asserListEqual because of float precision
+ self.assertAlmostEqual(actual_elev, expected_elev, places=3)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_topology_single_base_station.py b/tests/test_topology_single_base_station.py
index 0fdd6fd88..8dd228ec8 100644
--- a/tests/test_topology_single_base_station.py
+++ b/tests/test_topology_single_base_station.py
@@ -11,11 +11,12 @@
from sharc.topology.topology_single_base_station import TopologySingleBaseStation
+
class TopologySingleBaseStationTest(unittest.TestCase):
-
+
def setUp(self):
pass
-
+
def test_coordinates(self):
cell_radius = 1500
num_clusters = 1
@@ -28,17 +29,16 @@ def test_coordinates(self):
num_clusters = 2
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
- npt.assert_equal(topology.x, np.array([0, 2*cell_radius]))
+ npt.assert_equal(topology.x, np.array([0, 2 * cell_radius]))
npt.assert_equal(topology.y, np.array([0, 0]))
-
def test_num_clusters(self):
cell_radius = 1500
num_clusters = 1
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.num_clusters, 1)
-
+
cell_radius = 1500
num_clusters = 2
topology = TopologySingleBaseStation(cell_radius, num_clusters)
@@ -49,7 +49,6 @@ def test_num_clusters(self):
num_clusters = 3
with self.assertRaises(ValueError):
topology = TopologySingleBaseStation(cell_radius, num_clusters)
-
def test_intersite_distance(self):
cell_radius = 1500
@@ -57,42 +56,39 @@ def test_intersite_distance(self):
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.intersite_distance, 3000)
-
+
cell_radius = 1000
num_clusters = 1
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.intersite_distance, 2000)
-
def test_cell_radius(self):
cell_radius = 1500
num_clusters = 1
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.cell_radius, 1500)
-
+
cell_radius = 1000
num_clusters = 1
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.cell_radius, 1000)
-
-
+
def test_azimuth(self):
cell_radius = 1500
num_clusters = 1
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
self.assertEqual(topology.azimuth, 0)
-
+
cell_radius = 1000
num_clusters = 2
topology = TopologySingleBaseStation(cell_radius, num_clusters)
topology.calculate_coordinates()
npt.assert_equal(topology.azimuth, np.array([0, 180]))
-
-
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/tox.yaml b/tox.yaml
new file mode 100644
index 000000000..46abcda43
--- /dev/null
+++ b/tox.yaml
@@ -0,0 +1,9 @@
+tox:
+ envlist : py26, py27, py33, py34, py35, flake8
+testenv:flake8:
+ basepython :python
+ deps :flake8
+ commands :flake8 sharc
+testenv:
+ PYTHONPATH : {toxinidir}:{toxinidir}/sharc
+ py.test --basetemp :{envtmpdir}