diff --git a/.gitignore b/.gitignore index 4e51303c2..4496b10bf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /sharc/output/*.csv /sharc/output/*.png /sharc/output_*/* -/sharc/campaigns/*/output/ +/sharc/campaigns/ __pycache__ diff --git a/docs/docusaurus/docs/How to/Create a simulation campaign in the campaigns folder.md b/docs/docusaurus/docs/How to/Create a simulation campaign in the campaigns folder.md deleted file mode 100644 index 1b66e3ae4..000000000 --- a/docs/docusaurus/docs/How to/Create a simulation campaign in the campaigns folder.md +++ /dev/null @@ -1,123 +0,0 @@ - -# Create a Simulation Campaign - ---- - -## 1. Setup the Environment - -### Step 1: Create the Campaign Folder -Choose a descriptive name for your campaign folder based on the simulation parameters. -- **Example:** `imt_hibs_ras_2600_MHz` - ---- - -### Step 2: Structure the Campaign Folder -Create the following subfolders to organize your files: - -- **`input` Folder:** - Stores all input configuration files. - - **Path:** `campaigns/imt_hibs_ras_2600_MHz/input/` - - **Contents:** `.yaml` files with simulation parameters. - -- **`output` Folder:** - SHARC saves all simulation results here. - - **Path:** `campaigns/imt_hibs_ras_2600_MHz/output/` - - **Contents:** Simulation data and visualizations. - -- **`scripts` Folder:** - Contains scripts for running simulations and analyzing results. - - **Path:** `campaigns/imt_hibs_ras_2600_MHz/scripts/` - - **Contents:** Python scripts for running or plotting results from campaigns. - ---- - -## 2. Configuring Your Simulation - -### Step 3: Create a Parameter File -Define your simulation parameters in a `.yaml` file. -- **Example File:** `parameters_hibs_ras_2600_MHz_0km.yaml` -- **Location:** `campaigns/imt_hibs_ras_2600_MHz/input/` - ---- - -### Step 4: Define Simulation Parameters in the `.yaml` File -Customize your simulation settings in the `.yaml` file. Key sections include: - -```yaml -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: RAS - ########################################################################### -**(example)** -``` - ---- - -### Step 5: Create Multiple Simulation Configurations -To study different scenarios, create additional `.yaml` files in the `input` folder. -- **Examples:** - - `parameters_hibs_ras_2600_MHz_10km.yaml` - - `parameters_hibs_ras_2600_MHz_20km.yaml` - ---- - -## 3. Running Simulations - -### Step 6: Run the Simulations -In the `scripts` folder, create Python scripts to automate simulation execution. - -#### Multi-threaded Simulation -Run multiple simulations in parallel for efficiency. -- **Script:** `start_simulations_multi_thread.py` - -```python -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) -**(example)** -``` - -#### Single-threaded Simulation -Run a single simulation for testing purposes. -- **Script:** `start_simulations_single_thread.py` - -```python -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) -**(example)** -``` - ---- - -## 4. Post-processing and Analyzing Results - -### Step 7: Post-process and Analyze Results -Create scripts to read the output data and generate visualizations. - -#### Example Plot Script: `plot_results.py` -This script reads simulation data and generates plots. - ---- diff --git a/docs/docusaurus/docs/How to/Run the simulator using main interface.md b/docs/docusaurus/docs/How to/Run the simulator using main interface.md index 47a92f450..ecadec818 100644 --- a/docs/docusaurus/docs/How to/Run the simulator using main interface.md +++ b/docs/docusaurus/docs/How to/Run the simulator using main interface.md @@ -70,17 +70,13 @@ usage: main_cli.py -p python main_cli.py -p /path/to/parameters.yaml ``` - If you don't specify a parameters file, it will default to `input/parameters.yaml`: - ```bash - python main_cli.py - ``` - 3. **View Logs:** - The simulation will start, and logs will be displayed in the terminal. You can monitor these logs for progress and results. Logging is automatically set up via the `Logging.setup_logging()` function. + The simulation will start, and logs will be displayed in the terminal. You might also pass a log file with the + `-l` parameter. ## 5. Parameters File -The configuration file (`parameters.yaml`) should define the simulation parameters. +The configuration file should define the simulation parameters. ### Example `parameters.yaml` @@ -95,13 +91,16 @@ general: imt_link: DOWNLINK ########################################################################### # The chosen system for sharing study - # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, - system: RAS + system: SINGLE_EARTH_STATION ########################################################################### **(example)** ``` These parameters will be used by the simulator during execution, and you can modify them as needed. +The parameter file used for testing in `tests/parameters/parameters_for_testing.yaml` +is a good staring point for checking all possible parameters for every system implemented in the simulator. +For details of the parameter structure and logic refer to the `Parameter` package. You'll find all +the expected parameters and their validation. ## 6. Simulator Components diff --git a/docs/docusaurus/docs/Systems/Earth Exploration Satellite Service.md b/docs/docusaurus/docs/Systems/Earth Exploration Satellite Service.md index dd8ffdadf..cf799fa05 100644 --- a/docs/docusaurus/docs/Systems/Earth Exploration Satellite Service.md +++ b/docs/docusaurus/docs/Systems/Earth Exploration Satellite Service.md @@ -11,7 +11,7 @@ sidebar_position: 1 The **Earth Exploration Satellite Service** is a radiocommunication service that employs satellites to gather environmetal data, facilitating analysis and monitoring of the Earth's surface and atmosphere. The systme is essential in land-use studies, oceanography, -disaster and environmental management. The implemented spacial system is indispensable for research +disaster and environmental management. The implemented spatial system is indispensable for research and management actities in atmospherical context. ### Overview diff --git a/sharc/antenna/antenna_array.py b/sharc/antenna/antenna_array.py new file mode 100644 index 000000000..b639cfc3f --- /dev/null +++ b/sharc/antenna/antenna_array.py @@ -0,0 +1,525 @@ +""" +Optimized implementation of M.2101 antenna array. + +This antenna was created after the already existing antenna_beamforming_imt +since that implementation is too slow for use with a lot of stations. + +This is supposed to be a faster implementation, and substitute the previous one +in a future release +""" + +from sharc.antenna.antenna import Antenna +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt +from sharc.support.geometry import RigidTransform +from sharc.support.sharc_geom import polar_to_cartesian, cartesian_to_polar + +import numpy as np +import typing + + +class AntennaArray(Antenna): + """Implements M.2101 antenna array.""" + + def __init__( + self, + par: ParametersAntennaImt, + global2local_transform: RigidTransform = None, + ): + """Constructs antenna array. + + Parameters + ---------- + par: ParametersAntennaImt + Antenna parameters. Partial support only. + global2local_transform: RigidTransform, optional + Transformation from global to local coordinate system. If None, + no transformation is applied. + + Notes + ----- + By partial support, it is meant that not all parameters + from ParametersAntennaImt are used in this implementation. + For example, normalization and subarray support are not + implemented. + """ + super().__init__() + self.par = par + self.always_first_beam = False + + self.global2local_transform = global2local_transform + if self.global2local_transform is not None: + if self.global2local_transform.N > 1: + raise ValueError( + "global2local_transform is supposed to have a single" + " transformation for the purposes of antenna calculations" + ) + + def set_always_first_beam(self): + """Sets the antenna to always use the first beam. + + When this flag is set, :meth:`calculate_gain` ignores any ``beams_l`` + argument and selects the first beam (index 0) for all direction angles. + """ + self.always_first_beam = True + + def calculate_gain(self, *args, **kwargs) -> np.array: + """Calculates the antenna gain. + + Parameters + ---------- + phi_vec : np.ndarray + Azimuth angles [degrees] in global coordinate system. + theta_vec : np.ndarray + Elevation angles [degrees] in global coordinate system. + co_channel : bool, optional + If True, co-channel interference is considered (default is True). + beams_l : np.ndarray, optional + Indices of beams to consider for each angle. If not provided, + all beams are considered. Also, if always_first_beam is set, + this parameter is ignored. + + Returns + ------- + np.ndarray + Antenna gain [dBi] for each direction. + """ + phi_vec = np.atleast_1d(kwargs["phi_vec"]) + theta_vec = np.atleast_1d(kwargs["theta_vec"]) + co_channel = kwargs.get("co_channel", True) + adj_antenna_model = ( + self.par.adjacent_antenna_model == "SINGLE_ELEMENT" + and not co_channel + ) + if self.always_first_beam: + beam_idxs = np.zeros(len(phi_vec), dtype=int) + elif "beams_l" in kwargs.keys(): + beam_idxs = np.asarray(kwargs["beams_l"], dtype=int) + else: + beam_idxs = np.arange(len(phi_vec)) + + assert phi_vec.shape == (len(phi_vec),) + assert theta_vec.shape == (len(theta_vec),) + phi_vec, theta_vec = self._to_local_coord( + phi_vec, + theta_vec, + ) + + el_g = self._element_gain( + phi_vec, theta_vec, + ) + assert el_g.shape == theta_vec.shape + + if adj_antenna_model: + return el_g + + ar_g = self._array_gain( + phi_vec, theta_vec, beam_idxs + ) + assert ar_g.shape == theta_vec.shape + + return ar_g + el_g + + def _element_gain( + self, phi: np.ndarray, theta: np.ndarray + ): + """ + Calculates the element gain for given angles. + + Parameters + ---------- + phi : np.ndarray + Azimuth angles [degrees] in local coordinate system. + theta : np.ndarray + Elevation angles [degrees] in local coordinate system. + """ + return self._element_gain_dispatch( + self.par, phi, theta, + ) + + def _array_gain( + self, + phi: np.ndarray, theta: np.ndarray, + beam_idxs: np.ndarray + ): + """Calculates the array gain for given angles and beam indices. + + Notes + ----- + The mathematical formulation is based on M.2101, but formulation + has been optimized for computational efficiency. It considers + separability of the array factor into row and column components, + allowing for reduced memory bandwidth and faster runtime. + """ + if len(self.beams_list) == 0: + beam_phi, beam_theta = phi, theta + else: + beam_phi, beam_theta = np.array(self.beams_list).T + beam_phi, beam_theta = self._to_local_coord(beam_phi, beam_theta) + + beam_etilt = beam_theta - 90. + beams_w_vec_row, beams_w_vec_col = self._weight_vector_components( + beam_phi, beam_etilt, + self.par.n_rows, self.par.n_columns, + self.par.element_vert_spacing, + self.par.element_horiz_spacing, + ) + w_vec_row, w_vec_col = ( + beams_w_vec_row[beam_idxs], + beams_w_vec_col[beam_idxs] + ) + + v_vec_row, v_vec_col = self._super_position_vector_components( + phi, theta, + self.par.n_rows, self.par.n_columns, + self.par.element_vert_spacing, + self.par.element_horiz_spacing, + ) + + # NOTE: this formula has the same result to the one presented on M.2101 + # but it is optimized for computation + # considering W(m, n) = W(m)W(n) and V(m, n) = V(m)V(n) + g = 10 * np.log10( + abs( + np.sum(v_vec_row * w_vec_row, axis=-1) + * np.sum(v_vec_col * w_vec_col, axis=-1) + )**2 + ) + + return np.maximum(g, self.par.minimum_array_gain) + + @staticmethod + def _super_position_vector( + phi_tilt: np.ndarray, + theta_tilt: np.ndarray, + n_rows: int, n_cols: int, + dv: float, dh: float, + ) -> np.array: + vn, vm = AntennaArray._super_position_vector_components( + phi_tilt, + theta_tilt, + n_rows, n_cols, + dv, dh, + ) + + return vn[:, :, None] * vm[:, None, :] + + @staticmethod + def _weight_vector( + phi_tilt: np.ndarray, + theta_tilt: np.ndarray, + n_rows: int, n_cols: int, + dv: float, dh: float, + ) -> np.array: + wn, wm = AntennaArray._weight_vector_components( + phi_tilt, + theta_tilt, + n_rows, n_cols, + dv, dh, + ) + + return wn[:, :, None] * wm[:, None, :] + + @staticmethod + def _weight_vector_components( + phi_tilt: np.ndarray, + theta_tilt: np.ndarray, + n_rows: int, n_cols: int, + dv: float, dh: float, + ) -> typing.Tuple[np.ndarray, np.ndarray]: + """ + Calculates the complex weight vectors for beamforming. + Angles are in the local coordinate system. + + Parameters + ---------- + phi_tilt: np.ndarray + electrical horizontal steering [degrees] + theta_tilt: np.ndarray + electrical down-tilt steering [degrees] + + Returns + ------- + w_vec: (np.ndarray, np.ndarray) + weighting vectors, first for rows, second for columns + """ + # shape (Na, 1, 1) + r_phi = np.atleast_1d( + np.deg2rad(phi_tilt) + )[:, np.newaxis] + r_theta = np.atleast_1d( + np.deg2rad(theta_tilt) + )[:, np.newaxis] + + # shape (1, Nr, 1) + n = np.arange(n_rows)[np.newaxis, :] + 1 + # shape (1, 1, Nc) + m = np.arange(n_cols)[np.newaxis, :] + 1 + + exp_arg_n = (n - 1) * dv * np.sin(r_theta) + exp_arg_m = - (m - 1) * dh * np.cos(r_theta) * np.sin(r_phi) + + w_vec_n = (1 / np.sqrt(n_rows * n_cols)) *\ + np.exp(2 * np.pi * 1.0j * exp_arg_n) + + w_vec_m = np.exp(2 * np.pi * 1.0j * exp_arg_m) + + return (w_vec_n, w_vec_m) + + @staticmethod + def _super_position_vector_components( + phi: float, theta: float, + n_rows: int, n_cols: int, + dv: float, dh: float, + ) -> typing.Tuple[np.ndarray, np.ndarray]: + """ + 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: typing.Tuple[np.ndarray, np.ndarray]: + superposition vector components, first for rows, second for columns + + Notes + ----- + This implementation is optimized for computational efficiency, + using recursive relationships to avoid redundant calculations. + """ + phi = np.atleast_1d(phi) + theta = np.atleast_1d(theta) + + # (Na,) + A = dv * np.cos(np.deg2rad(theta)) + B = dh * np.sin(np.deg2rad(theta)) * np.sin(np.deg2rad(phi)) + + # instead of calculating exp for every row, there is a recursive + # relation that speeds this up. Small n_rows means that floating + # point error should not accumulate + # The relationship is: V(n) = V(n-1)V(2) | n > 2 + # V(1) = 1., V(2) = exp(...) + row_phase = np.empty((len(theta), n_rows), dtype=np.complex128) + row_phase[:, 0] = 1. + + row_phase_term = np.exp( + 2j * np.pi * A + ) + row_phase[:, 1:] = row_phase_term[:, None] + # recursive relationship by cumulative product + np.cumprod(row_phase, axis=-1, out=row_phase) + + # instead of calculating exp for every col, there is a recursive + # relation that speeds this up. Small n_cols means that floating + # point error should not accumulate + # The relationship is: V(n) = V(n-1)V(2) | n > 2 + # V(1) = 1., V(2) = exp(...) + col_phase = np.empty((len(theta), n_cols), dtype=np.complex128) + col_phase[:, 0] = 1. + + col_phase_term = np.exp( + 2j * np.pi * B + ) + col_phase[:, 1:] = col_phase_term[:, None] + # recursive relationship by cumulative product + np.cumprod(col_phase, axis=-1, out=col_phase) + + return (row_phase, col_phase) + + @staticmethod + def _element_gain_dispatch(par: ParametersAntennaImt, phi, theta): + """ + Dispatch to the correct element gain calculation method. + + Parameters + ---------- + par: ParametersAntennaImt + Antenna parameters. + phi: np.ndarray + Azimuth angles [degrees] in local coordinate system. + theta: np.ndarray + Elevation angles [degrees] in local coordinate system. + """ + if par.element_pattern == "M2101": + return AntennaArray._calculate_m2101_element_gain( + phi, theta, + par.element_phi_3db, par.element_theta_3db, + par.element_max_g, par.element_sla_v, par.element_am, + par.multiplication_factor, + ) + else: + raise NotImplementedError( + "No implementation done for element_pattern" + f"='{par.element_pattern}'" + ) + + @staticmethod + def _calculate_m2101_element_gain( + phi: np.ndarray, theta: np.ndarray, + phi_3db: np.ndarray, theta_3db: np.ndarray, + g_max: np.ndarray, sla_v: np.ndarray, am: np.ndarray, + multiplication_factor: np.ndarray = 12 + ): + """Calculates and returns element gain as described in M.2101.""" + g_horizontal = -1.0 * np.minimum( + multiplication_factor * (phi / phi_3db)**2, am + ) + g_vertical = -1.0 * np.minimum( + multiplication_factor * ((theta - 90.) / theta_3db)**2, sla_v + ) + + att = -1.0 * ( + g_horizontal + + g_vertical + ) + + return g_max - np.minimum(att, am) + + def _to_local_coord(self, phi, theta): + """ + Transform angles from global to local coordinate system. + + Parameters + ---------- + phi: np.ndarray + Azimuth angles [degrees] in global coordinate system. + theta: np.ndarray + Elevation angles [degrees] in global coordinate system. + + Returns + ------- + phi: np.ndarray + Azimuth angles [degrees] in local coordinate system. + theta: np.ndarray + Elevation angles [degrees] in local coordinate system. + + Notes + ----- + The transformation is done by converting to Cartesian coordinates, + applying the transformation defined on construction, + and converting back to spherical coordinates. It is assumed that + theta is defined with z axis as reference, and phi with x axis as reference + and increasing towards y axis. + """ + if self.global2local_transform is None: + return np.array(phi), np.array(theta) + + theta_from_plane = 90 - theta + vecs = np.stack(polar_to_cartesian(1, phi, theta_from_plane), axis=-1) + transformed_vecs = self.global2local_transform.apply_vectors( + vecs + ) + x, y, z = transformed_vecs.T + _r, phi, elev_from_plane = cartesian_to_polar(x, y, z) + + theta = 90 - elev_from_plane + + return phi, theta + + def add_beam(self, phi_etilt: float, theta_etilt: float): + """ + Add new beam to antenna. + Does not receive angles in local coordinate system. + Theta taken with z axis as reference. + + Parameters + ---------- + phi_etilt: float + azimuth electrical tilt angle [degrees] + theta_etilt: float + elevation electrical tilt angle [degrees] + """ + phi_etilt, theta_etilt = np.atleast_1d(phi_etilt), np.atleast_1d(theta_etilt) + + self.beams_list.append( + (np.ndarray.item(phi_etilt), np.ndarray.item(theta_etilt)), + ) + + +if __name__ == "__main__": + antenna_params = ParametersAntennaImt() + antenna_params.adjacent_antenna_model = "SINGLE_ELEMENT" + antenna_params.normalization = False + antenna_params.minimum_array_gain = -200 + + antenna_params.element_pattern = "M2101" + antenna_params.element_max_g = 6.5 + antenna_params.element_phi_3db = 65 + antenna_params.element_theta_3db = 90 + antenna_params.element_am = 30 + antenna_params.element_sla_v = 30 + antenna_params.n_rows = 8 + antenna_params.n_columns = 8 + antenna_params.element_horiz_spacing = 0.5 + antenna_params.element_vert_spacing = 0.5 + antenna_params.multiplication_factor = 12 + + par = antenna_params.get_antenna_parameters() + # from sharc.support.geometry import ENUReferenceFrame + # ref_frame = ENUReferenceFrame( + # lat=np.array([90.]), + # lon=np.array([-90.]), + # alt=np.array([0.]), + # ) + # antenna = AntennaArray(par, ref_frame.from_ecef) + antenna = AntennaArray(par) + + antenna.add_beam(np.array(0.), np.array(90.)) + + phi_scan = np.linspace(-180., 180., num=360) + theta = np.zeros_like(phi_scan) + 90. + + # gain = antenna._element_gain( + # phi_scan, + # theta, + # ) + gain = antenna.calculate_gain( + phi_vec=phi_scan, + theta_vec=theta, + beams_l=np.zeros_like(phi_scan), + ) + + import matplotlib.pyplot as plt + + fig = plt.figure(figsize=(15, 5), facecolor='w', edgecolor='k') + ax1 = fig.add_subplot(121) + + ax1.plot(phi_scan, gain) + top_y_lim1 = np.ceil(np.max(gain) / 10) * 10 + ax1.set_xlim(-180, 180) + ax1.set_ylim(top_y_lim1 - 60, top_y_lim1) + ax1.grid(True) + ax1.set_xlabel(r"$\varphi$ [deg]") + ax1.set_ylabel("Gain [dBi]") + + theta_scan = np.linspace(0., 180., num=360) + phi = np.zeros_like(theta_scan) + + # gain = antenna._element_gain( + # phi, + # theta_scan, + # ) + gain = antenna.calculate_gain( + phi_vec=phi, + theta_vec=theta_scan, + beams_l=np.zeros_like(theta_scan), + ) + # fig = plt.figure(figsize=(15, 5), facecolor='w', edgecolor='k') + ax2 = fig.add_subplot(122, sharey=ax1) + + ax2.plot(theta_scan, gain) + top_y_lim2 = np.ceil(np.max(gain) / 10) * 10 + top_y_lim2 = np.maximum(top_y_lim1, top_y_lim2) + + ax2.set_xlim(0, 180.) + ax2.set_ylim(top_y_lim2 - 60, top_y_lim2) + ax2.grid(True) + ax2.set_xlabel(r"$\vartheta$ [deg]") + ax2.set_ylabel("Gain [dBi]") + + plt.show() diff --git a/sharc/antenna/antenna_f1245_fs.py b/sharc/antenna/antenna_f1245_fs.py new file mode 100644 index 000000000..2643abb81 --- /dev/null +++ b/sharc/antenna/antenna_f1245_fs.py @@ -0,0 +1,186 @@ +import matplotlib.pyplot as plt +from sharc.antenna.antenna import Antenna +from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter +import numpy as np +import math + + +class Antenna_f1245_fs(Antenna): + """Class that implements the ITU-R F.1245 antenna pattern for fixed + satellite service earth stations.""" + + def __init__(self, param: ParametersAntennaWithDiameter): + super().__init__() + self.peak_gain = param.gain + lmbda = 3e8 / (param.frequency * 1e6) + self.d_lmbda = param.diameter / lmbda + self.g_l = 2 + 15 * math.log10(self.d_lmbda) + self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l) + self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6) + + def calculate_gain(self, *args, **kwargs) -> np.array: + """ + Calculate the antenna gain for the given parameters. + + Parameters + ---------- + *args : tuple + Positional arguments (not used). + **kwargs : dict + Keyword arguments, expects 'phi_vec', 'theta_vec', and 'beams_l'. + + Returns + ------- + np.array + Calculated antenna gain values. + """ + # phi_vec = np.absolute(kwargs["phi_vec"]) + # theta_vec = np.absolute(kwargs["theta_vec"]) + # beams_l = np.absolute(kwargs["beams_l"]) + off_axis = np.absolute(kwargs["off_axis_angle_vec"]) + if self.d_lmbda > 100: + gain = self.calculate_gain_greater(off_axis) + else: + gain = self.calculate_gain_less(off_axis) + # idx_max_gain = np.where(beams_l == -1)[0] + # gain = self.peak_gain + return gain + + def calculate_gain_greater(self, phi: float) -> np.array: + """ + For frequencies in the range 1 GHz to about 70 GHz, in cases where the + ratio between the antenna diameter and the wavelength is GREATER than + 100, this method should be used. + Parameter + --------- + phi : off-axis angle [deg] + Returns + ------- + a numpy array containing the gains in the given angles + """ + gain = np.zeros(phi.shape) + idx_0 = np.where(phi < self.phi_m)[0] + gain[idx_0] = self.peak_gain - 2.5e-3 * \ + np.power(self.d_lmbda * phi[idx_0], 2) + phi_thresh = max(self.phi_m, self.phi_r) + idx_1 = np.where((self.phi_m <= phi) & (phi < phi_thresh))[0] + gain[idx_1] = self.g_l + idx_2 = np.where((phi_thresh <= phi) & (phi < 48))[0] + gain[idx_2] = 29 - 25 * np.log10(phi[idx_2]) + idx_3 = np.where((48 <= phi) & (phi <= 180))[0] + gain[idx_3] = -13 + return gain + + def calculate_gain_less(self, phi: float) -> np.array: + """ + For frequencies in the range 1 GHz to about 70 GHz, in cases where the + ratio between the antenna diameter and the wavelength is LESS than + or equal to 100, this method should be used. + Parameter + --------- + phi : off-axis angle [deg] + Returns + ------- + a numpy array containing the gains in the given angles + """ + gain = np.zeros(phi.shape) + idx_0 = np.where(phi < self.phi_m)[0] + gain[idx_0] = self.peak_gain - 0.0025 * \ + np.power(self.d_lmbda * phi[idx_0], 2) + idx_1 = np.where((self.phi_m <= phi) & (phi < 48))[0] + gain[idx_1] = 39 - 5 * \ + math.log10(self.d_lmbda) - 25 * np.log10(phi[idx_1]) + idx_2 = np.where((48 <= phi) & (phi < 180))[0] + gain[idx_2] = -3 - 5 * math.log10(self.d_lmbda) + return gain + + def add_beam(self, phi: float, theta: float): + """ + Add a new beam to the antenna. + + Parameters + ---------- + phi : float + Azimuth angle in degrees. + theta : float + Elevation angle in degrees. + """ + self.beams_list.append((phi, theta)) + + def calculate_off_axis_angle(self, Az, b): + """ + Calculate the off-axis angle between the main beam and a given direction. + + Parameters + ---------- + Az : float or np.array + Azimuth angle(s) in degrees. + b : float or np.array + Elevation angle(s) in degrees. + + Returns + ------- + float or np.array + Off-axis angle(s) in degrees. + """ + Az0 = self.beams_list[0][0] + a = 90 - self.beams_list[0][1] + C = Az0 - Az + off_axis_rad = np.arccos( + np.cos( + np.radians(a)) * + np.cos( + np.radians(b)) + + np.sin( + np.radians(a)) * + np.sin( + np.radians(b)) * + np.cos( + np.radians(C)), + ) + off_axis_deg = np.degrees(off_axis_rad) + return off_axis_deg + + +if __name__ == '__main__': + off_axis_angle_vec = np.linspace(0.1, 180, num=1001) + # initialize antenna parameters + param = ParametersAntennaWithDiameter() + param.frequency = 2155 + param.gain = 33.1 + param.diameter = 2 + antenna_gt = Antenna_f1245_fs(param) + antenna_gt.add_beam(0, 0) + gain_gt = antenna_gt.calculate_gain( + off_axis_angle_vec=off_axis_angle_vec, + ) + param.diameter = 3 + antenna_gt = Antenna_f1245_fs(param) + gain_gt_3 = antenna_gt.calculate_gain( + off_axis_angle_vec=off_axis_angle_vec, + ) + param.diameter = 1.8 + antenna_gt = Antenna_f1245_fs(param) + gain_gt_18 = antenna_gt.calculate_gain( + off_axis_angle_vec=off_axis_angle_vec, + ) + + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + plt.semilogx(off_axis_angle_vec, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 2$ $m$") + plt.semilogx(off_axis_angle_vec, gain_gt_3, "-y", label="$f = 10.7$ $GHz,$ $D = 3$ $m$") + plt.semilogx(off_axis_angle_vec, gain_gt_18, "-g", label="$f = 10.7$ $GHz,$ $D = 1.8$ $m$") + + plt.title("ITU-R F.1245 antenna radiation pattern") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") + plt.ylabel("Gain relative to $G_m$ [dB]") + plt.legend(loc="lower left") + # plt.xlim((phi[0], phi[-1])) + plt.ylim((-20, 50)) + # ax = plt.gca() + # ax.set_yticks([-30, -20, -10, 0]) + # ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + plt.grid() + plt.show() diff --git a/sharc/antenna/antenna_factory.py b/sharc/antenna/antenna_factory.py index 051e1f332..aed067148 100644 --- a/sharc/antenna/antenna_factory.py +++ b/sharc/antenna/antenna_factory.py @@ -12,6 +12,7 @@ from sharc.antenna.antenna_s580 import AntennaS580 from sharc.antenna.antenna_s1528 import AntennaS1528 from sharc.antenna.antenna_s1855 import AntennaS1855 +from sharc.antenna.antenna_f1245_fs import Antenna_f1245_fs from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Leo, AntennaS1528Taylor from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt @@ -49,6 +50,8 @@ def create_antenna( return AntennaS465(antenna_params.itu_r_s_465_modified) case "ITU-R S.1855": return AntennaS1855(antenna_params.itu_r_s_1855) + case "ITU-R F.1245_fs": + return Antenna_f1245_fs(antenna_params.itu_r_f_1245_fs) case "ITU-R Reg. RR. Appendice 7 Annex 3": return AntennaReg_RR_A7_3(antenna_params.itu_reg_rr_a7_3) case "MSS Adjacent": diff --git a/sharc/antenna/antenna_s1528.py b/sharc/antenna/antenna_s1528.py index b8ee90764..de9a966ba 100644 --- a/sharc/antenna/antenna_s1528.py +++ b/sharc/antenna/antenna_s1528.py @@ -175,12 +175,18 @@ def __init__(self, param: ParametersAntennaS1528): # the system design self.l_s = param.antenna_l_s + # far-out side-lobe level [dBi] + if param.far_out_side_lobe is None: + self.l_f = 0 + else: + self.l_f = param.far_out_side_lobe + # 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 = 0 + if param.major_minor_axis_ratio is None: + self.z = 1 + else: + self.z = param.major_minor_axis_ratio # back-lobe level self.l_b = np.maximum( @@ -190,19 +196,22 @@ def __init__(self, param: ParametersAntennaS1528): # one-half the 3 dB beamwidth in the plane of interest 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)) - elif self.l_s == -20: - 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)) - elif self.l_s == -30: - self.a = 2.58 * math.sqrt(1 - 0.4 * math.log10(self.z)) + if math.isclose(self.z, 1.0): + self.a = 2.58 else: - sys.stderr.write( - "ERROR\nInvalid AntennaS1528 L_s parameter: " + str(self.l_s), - ) - sys.exit(1) + if self.l_s == -15: + 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)) + elif self.l_s == -25: + 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)) + else: + sys.stderr.write( + f"ERROR\nInvalid AntennaS1528 L_s parameter {self.l_s} for z={self.z}" + ) + sys.exit(1) self.b = 6.32 self.alpha = 1.5 diff --git a/sharc/antenna/antenna_s672.py b/sharc/antenna/antenna_s672.py index 3ca6dad35..e370ab16e 100644 --- a/sharc/antenna/antenna_s672.py +++ b/sharc/antenna/antenna_s672.py @@ -6,7 +6,7 @@ """ from sharc.antenna.antenna import Antenna -from sharc.parameters.parameters_fss_ss import ParametersFssSs +from sharc.parameters.antenna.parameters_antenna_s672 import ParametersAntennaS672 import numpy as np import sys @@ -18,7 +18,7 @@ class AntennaS672(Antenna): according to Recommendation ITU-R S.672-4 Annex 1 """ - def __init__(self, param: ParametersFssSs): + def __init__(self, param: ParametersAntennaS672): super().__init__() self.peak_gain = param.antenna_gain self.l_s = param.antenna_l_s @@ -37,7 +37,7 @@ def __init__(self, param: ParametersFssSs): self.b = 6.32 - self.psi_0 = param.antenna_3_dB / 2 + self.psi_0 = param.antenna_3_dB_bw / 2 self.psi_1 = self.psi_0 * \ np.power(10, (self.peak_gain + self.l_s + 20) / 25) @@ -83,10 +83,10 @@ def calculate_gain(self, *args, **kwargs) -> np.array: import matplotlib.pyplot as plt # initialize antenna parameters - param = ParametersFssSs() + param = ParametersAntennaS672() param.antenna_gain = 50 param.antenna_pattern = "ITU-R S.672-4" - param.antenna_3_dB = 2 + param.antenna_3_dB_bw = 2 psi = np.linspace(1, 30, num=1000) param.antenna_l_s = -20 diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md b/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md deleted file mode 100644 index b84dfe69e..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md +++ /dev/null @@ -1,39 +0,0 @@ - -# 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 deleted file mode 100644 index d54ba5d98..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml +++ /dev/null @@ -1,374 +0,0 @@ -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 deleted file mode 100644 index 7345c4302..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml +++ /dev/null @@ -1,373 +0,0 @@ -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 deleted file mode 100644 index b1821243f..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml +++ /dev/null @@ -1,373 +0,0 @@ -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 deleted file mode 100644 index ae126fade..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml +++ /dev/null @@ -1,373 +0,0 @@ -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 deleted file mode 100644 index aef44111d..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md +++ /dev/null @@ -1,3 +0,0 @@ -# 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 deleted file mode 100644 index 92d8a0725..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Script for post-processing and plotting IMT HIBS RAS 2600 MHz simulation results. -Adds legends to result folders and generates plots using SHARC's PostProcessor. -""" -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 deleted file mode 100644 index 69455420e..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Script to start IMT HIBS RAS 2600 MHz simulations in multi-threaded mode using SHARC. -""" -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 deleted file mode 100644 index 7246055e5..000000000 --- a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Script to start IMT HIBS RAS 2600 MHz simulation in single-threaded mode using SHARC. -""" -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 deleted file mode 100644 index 65ce1b706..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv +++ /dev/null @@ -1,78 +0,0 @@ -# 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 deleted file mode 100644 index 06747adbf..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml +++ /dev/null @@ -1,376 +0,0 @@ -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 deleted file mode 100644 index ccb1d74f2..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml +++ /dev/null @@ -1,371 +0,0 @@ -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 deleted file mode 100644 index 66a2e8738..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# 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 deleted file mode 100644 index c2dd66160..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Script for post-processing and plotting IMT Hotspot EESS Active simulation results. -Adds legends to result folders and generates plots using SHARC's PostProcessor. -""" -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 deleted file mode 100644 index e77abad9d..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 1be6efde8..000000000 --- a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index cd95f43fe..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv +++ /dev/null @@ -1,37 +0,0 @@ -# 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 deleted file mode 100644 index 729134dc3..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv +++ /dev/null @@ -1,27 +0,0 @@ -# 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 deleted file mode 100644 index 9440c604d..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml +++ /dev/null @@ -1,270 +0,0 @@ -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 deleted file mode 100644 index 82c6210ce..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml +++ /dev/null @@ -1,278 +0,0 @@ -# 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 deleted file mode 100644 index ac91bca59..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml +++ /dev/null @@ -1,271 +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_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 deleted file mode 100644 index 8867c10f8..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/readme.md +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index 530640842..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py +++ /dev/null @@ -1,150 +0,0 @@ -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 deleted file mode 100644 index d6da27253..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index fe3a8c9ac..000000000 --- a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 56da3c38f..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml +++ /dev/null @@ -1,387 +0,0 @@ -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 deleted file mode 100644 index 2c71d5fed..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml +++ /dev/null @@ -1,391 +0,0 @@ -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 deleted file mode 100644 index 587c95b83..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml +++ /dev/null @@ -1,391 +0,0 @@ -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 deleted file mode 100644 index 3c7928171..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml +++ /dev/null @@ -1,391 +0,0 @@ -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 deleted file mode 100644 index 5bf3b0451..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml +++ /dev/null @@ -1,377 +0,0 @@ -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 deleted file mode 100644 index afa02e826..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml +++ /dev/null @@ -1,377 +0,0 @@ -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 deleted file mode 100644 index 231043535..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml +++ /dev/null @@ -1,377 +0,0 @@ -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 deleted file mode 100644 index f667ca5dc..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml +++ /dev/null @@ -1,377 +0,0 @@ -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 deleted file mode 100644 index 3f8549b01..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py +++ /dev/null @@ -1,105 +0,0 @@ -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 deleted file mode 100644 index 2e3ac7123..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 799615c6b..000000000 --- a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 6a163a53a..000000000 --- a/sharc/campaigns/imt_ntn_footprint/scripts/imt_ntn_footprint.py +++ /dev/null @@ -1,103 +0,0 @@ -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 deleted file mode 100644 index b81d3b0ee..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/README.md +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index ff5a730c5..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/input/parameters_imt_ntn_to_imt_tn_sep_distance_template.yaml +++ /dev/null @@ -1,514 +0,0 @@ -# ########################################################################### -# 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 deleted file mode 100644 index 12648dcba..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/calculate_cluster_positions.py +++ /dev/null @@ -1,140 +0,0 @@ -# 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 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 deleted file mode 100644 index 5cc4a6cc4..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/parameter_gen.py +++ /dev/null @@ -1,54 +0,0 @@ -"""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 deleted file mode 100644 index b1d3a1945..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/plot_results.py +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 9301a71a3..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index d2cc5583d..000000000 --- a/sharc/campaigns/imt_ntn_to_imt_tn_co_channel/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,14 +0,0 @@ -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_eess/input/.gitignore b/sharc/campaigns/mss_d2d_to_eess/input/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/input/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/sharc/campaigns/mss_d2d_to_eess/scripts/base_input.yaml b/sharc/campaigns/mss_d2d_to_eess/scripts/base_input.yaml deleted file mode 100644 index bdcd18a25..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/scripts/base_input.yaml +++ /dev/null @@ -1,552 +0,0 @@ -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 - 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: 681603 - ########################################################################### - # 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_eess/output/ - ########################################################################### - # output folder prefix - output_dir_prefix: output_mss_d2d_to_eess_base -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: False - ########################################################################### - # IMT center frequency [MHz] - frequency: 2167.5 # B1/B4 - SPACE STATION - 2110-2 170 - ########################################################################### - # IMT bandwidth [MHz] - bandwidth: 5.0 - ########################################################################### - # IMT resource block bandwidth [MHz] - rb_bandwidth: 0.180 - # IMT resource block bandwidth [MHz] - ########################################################################### - adjacent_ch_reception: ACS - ########################################################################### - adjacent_ch_emissions: SPECTRAL_MASK - adjacent_ch_leak_ratio: 50.0 # dB - ########################################################################### - # 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: MSS - ########################################################################### - # 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: - ## Refernce latitude and longitude taken from INPE/Cachoeria Paulista - ########################################################################### - # The latitude position - central_latitude: -22.681603 - ########################################################################### - # The longitude position - central_longitude: -45.002609 - ########################## - central_altitude: 563 # meters - ########################################################################### - # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" - # "INDOOR", "MSS_DC" - type: MSS_DC - ########################################################################### - # Topology parameters for the MSS-DC network - mss_dc: - # MSS_DC cell radius in network topology [m] - beam_radius: 39475.0 - # Number of sectors - num_beams: 1 - beam_positioning: - # type may be one of - # "ANGLE_FROM_SUBSATELLITE", "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE", - # "SERVICE_GRID" - # when "ANGLE_FROM_SUBSATELLITE", both phi and theta must be specified - # when "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE", both phi and distance - # must be specified - type: ANGLE_FROM_SUBSATELLITE - # theta is the off axis angle from satellite nadir - angle_from_subsatellite_theta: - # type may be one of - # "FIXED", "~U(MIN,MAX)", "~SQRT(U(0,1))*MAX" - type: FIXED - # used if TYPE == FIXED is specified - fixed: 0 - # used if some distribution type is specified - # distribution: - # min: 0.0 - # max: 66.1 - # phi completes polar coordinates - # equivalent to "azimuth" from subsatellite in earth plane - angle_from_subsatellite_phi: - # type may be one of - # "FIXED", "~U(MIN,MAX)", "~SQRT(U(0,1))*MAX" - type: FIXED - # used if TYPE == FIXED is specified - fixed: 0 - # used if some distribution type is specified - # distribution: - # min: 0.0 - # max: 66.1 - # distance from subsatellite. Substitutes theta - # distance_from_subsatellite: - # # type may be one of - # # "FIXED", "~U(MIN,MAX)", "~SQRT(U(0,1))*MAX" - # type: FIXED - # # used if TYPE == FIXED is specified - # fixed: 0 - # # used if some distribution type is specified - # distribution: - # min: 0.0 - # max: 66.1 - # service_grid: - # # by default this already gets shapefile from natural earth - # country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp - # # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` - # country_names: - # - Brazil - # - Chile - - # This margin defines where the grid is constructed - # margin from inside of border [km] - # By default, this is made to be the same as beam_radius - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - # grid_margin_from_border: 0.11 - - # This margin defines what satellites may serve the grid - # it should normally be a smaller value than the grid from border - # since a satellite not right above the grid may serve some point better - # margin from inside of border [km] - # By default, this is made to be the same as beam_radius - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - # eligible_sats_margin_from_border: -2.1 - 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: 5.0 - # maximum_elevation_from_es: 80.0 - # 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: 111.0 - ########################################################################### - # Parameters for the orbits - orbits: # System 3 parameters - # 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 - ########################################################################### - # 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: 41.4 # -55.6 [dB/Hz] + 10log(5*1e6)[Hz] + 30.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: - pattern: ARRAY - # gain: 0 # set to zero when using the adjacent MSS antenna model - # itu_r_s_1528: - # antenna_pattern: ITU-R-S.1528-Taylor - # ### 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 - # mss_adjacent: - # frequency: 2167.5 # MHz - # # Base Station Antenna parameters: - 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: 0.0 # the topology MSS takes care of this - ########################################################################### - # 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: 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: 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 - subarray: - ########################################################################### - # to use subarray, set this to true - is_enabled: false - ########################################################################### - # Number of rows in subarray - n_rows: 3 - ########################################################################### - # BS array element vertical spacing (d/lambda). - element_vert_spacing: 0.7 - ########################################################################### - # Sub array eletrical downtilt [deg] - # notice that electrical tilt == -1 * downtilt - 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: 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: FSPL - 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 -# Single Earth Station representing an EESS Earth Station in the 2.0 GHz band. -# Referce data comes from the REPLY LIAISON STATEMENT TO WORKING PARTY 4C ON -# WRC-27 AGENDA ITEMS 1.12, 1.13, 1.14 AND 1.15 - Document 4C/196-E -single_earth_station: - frequency: 2200 # MHz - bandwidth: 4 # MHz - # tx power density just so simulator runs - tx_power_density: -200 - # also just so simulator runs - noise_temperature: 190 - # This is based in pratical data found in other documents. - # 35-45dBs are generally assumed. We take 35 as a worst case. - adjacent_ch_selectivity: 35 - # Channel model - channel_model: FSPL - # NOTE: when using P.619 it is suggested that polarization loss = 3dB is good - polarization_loss: 0 - # Earth Station parameters - geometry: - height: 7 - azimuth: - # Type of elevation. - type: UNIFORM_DIST - uniform_dist: - max: 180 - min: -180 - # Elevation angle [degrees] - elevation: - # Type of elevation. - type: FIXED - fixed: 90 - # Station 2d location [meters]: - location: - type: FIXED - fixed: - x: 0 - y: 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 S.672" - # since 20deg beamwidth would already cover every area - pattern: ITU-R S.465 - ########################################################################### - # Station peak receive antenna gain [dBi] - gain: 45.8 - itu_r_s_465: - # Diameter [m] - diameter: 12 # derived with efficiency 0.5 - diff --git a/sharc/campaigns/mss_d2d_to_eess/scripts/generate_inputs.py b/sharc/campaigns/mss_d2d_to_eess/scripts/generate_inputs.py deleted file mode 100644 index a4ea945ff..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/scripts/generate_inputs.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Generates scenarios based on main parameters -""" -from dataclasses import dataclass -import typing -import numpy as np -import yaml -import os -from copy import deepcopy - -from sharc.parameters.parameters_base import tuple_constructor - - -@dataclass -class ESParams(): - """ - Data class representing Earth Station parameters for scenario generation. - - Attributes: - name (str): Name of the earth station system. - antenna_gain (float): Antenna gain in dBi. - receive_temperature (float): Receiver noise temperature in K. - frequency (float): Operating frequency in MHz. - bandwidth (float): Bandwidth in MHz. - antenna_diameter (float, optional): Antenna diameter in meters. - antenna_pattern (Literal): Antenna pattern type. - """ - name: str - # [dBi] - antenna_gain: float - # [K] - receive_temperature: float - # [MHz] - frequency: float - # [MHz] - bandwidth: float - # [m] - antenna_diameter: float = None - # TODO: add "ITU RR. Appendix 8, annex III" antenna pattern - antenna_pattern: typing.Literal[ - "ITU-R S.465", "ITU RR. Appendix 8, annex III" - ] = "ITU-R S.465" - - def __post_init__(self): - if self.antenna_diameter is None: - # antenna efficiency: - n = 0.5 - lmbda = 3e8 / (self.frequency * 1e6) - G = 10**(self.antenna_gain / 10) - self.antenna_diameter = float(np.round( - lmbda * np.sqrt(G / n) / np.pi, decimals=2 - )) - print( - f"Earth station { - self.name} antenna diameter of { - self.antenna_diameter} " f"has been assumed for an efficiency of {n}.") - - -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") - -parameter_file_name = os.path.join(local_dir, "./base_input.yaml") - -# load the base parameters from the yaml file -with open(parameter_file_name, 'r') as file: - gen_parameters = yaml.safe_load(file) - -# doesn't matter from which, both will give same result -output_prefix_pattern = gen_parameters['general']['output_dir_prefix'].replace( - "_base", "_") - -freq = 2200 - -system_B = ESParams( - name="system_b", - antenna_gain=45.8, - receive_temperature=190, - frequency=freq, - bandwidth=4, # MHz -) - -system_D = ESParams( - name="system_d", - antenna_gain=39, - receive_temperature=120, - frequency=freq, - bandwidth=6, # MHz -) - -for sys in [ - system_B, - system_D, -]: - sys_parameters = deepcopy(gen_parameters) - - sys_parameters['single_earth_station']['frequency'] = sys.frequency - sys_parameters['single_earth_station']['bandwidth'] = sys.bandwidth - - sys_parameters['single_earth_station']['antenna']['gain'] = sys.antenna_gain - sys_parameters['single_earth_station']['antenna']['pattern'] = sys.antenna_pattern - sys_parameters['single_earth_station']['antenna']['itu_r_s_465']['diameter'] = sys.antenna_diameter - - sys_parameters['single_earth_station']['noise_temperature'] = sys.receive_temperature - - for eess_elev in [ - 5, 30, 60, 90 - ]: - parameters = deepcopy(sys_parameters) - - parameters['single_earth_station']['geometry']['elevation']['type'] = "FIXED" - parameters['single_earth_station']['geometry']['elevation']['fixed'] = eess_elev - - specific = f"{eess_elev}elev_{sys.name}" - parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace( - "", specific) - - with open( - os.path.join(input_dir, f"./parameters_mss_d2d_to_eess_{specific}.yaml"), - 'w' - ) as file: - yaml.dump(parameters, file, default_flow_style=False) - - # also do uniform dist of elevation angles - parameters = deepcopy(sys_parameters) - parameters['single_earth_station']['geometry']['elevation']['type'] = "UNIFORM_DIST" - parameters['single_earth_station']['geometry']['elevation']['uniform_dist'] = { - 'min': 5, 'max': 90, } - - specific = f"uniform_elev_{sys.name}" - parameters['general']['output_dir_prefix'] = output_prefix_pattern.replace( - "", specific) - - with open( - os.path.join(input_dir, f"./parameters_mss_d2d_to_eess_{specific}.yaml"), - 'w' - ) as file: - yaml.dump(parameters, file, default_flow_style=False) diff --git a/sharc/campaigns/mss_d2d_to_eess/scripts/plot_results.py b/sharc/campaigns/mss_d2d_to_eess/scripts/plot_results.py deleted file mode 100644 index 97cfbdc99..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/scripts/plot_results.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -from pathlib import Path -from sharc.results import Results -from sharc.post_processor import PostProcessor - -auto_open = False - -local_dir = os.path.dirname(os.path.abspath(__file__)) -campaign_base_dir = str((Path(__file__) / ".." / "..").resolve()) - -post_processor = PostProcessor() - -# Samples to plot CCDF from -samples_for_ccdf = [ - "system_dl_interf_power_per_mhz" -] - -samples_for_cdf = [ - "imt_system_antenna_gain", - "imt_system_path_loss", - "system_dl_interf_power", - "system_imt_antenna_gain", - "system_inr", "ccdf" -] - -ccdf_results = Results.load_many_from_dir( - os.path.join( - campaign_base_dir, - "output"), - filter_fn=lambda x: "mss_d2d_to_eess" in x, - only_latest=True, - only_samples=samples_for_ccdf) - -cdf_results = Results.load_many_from_dir( - os.path.join( - campaign_base_dir, - "output"), - filter_fn=lambda x: "mss_d2d_to_eess" in x, - only_latest=True, - only_samples=samples_for_cdf) - -readable_name = { - "system_d": "System D", - "system_b": "System B", -} - - -def linestyle_getter(results): - """ - Determine the line style for plotting based on the results' output directory. - - Parameters - ---------- - results : Results - The results object containing the output directory information. - - Returns - ------- - str - The line style to use for plotting (e.g., 'dash' or 'solid'). - """ - if "system_d" in results.output_directory: - return "dash" - return "solid" - - -post_processor.add_results_linestyle_getter(linestyle_getter) - -for sys_name in ["system_d", "system_b"]: - for elev in [5, 30, 60, 90, "uniform_"]: - if elev == "uniform_": - readable_elev = "Elev = Unif. Dist." - else: - readable_elev = f"Elev = {elev}º" - # IMT-MSS-D2D-DL to EESS - post_processor\ - .add_plot_legend_pattern( - dir_name_contains=f"{elev}elev_{sys_name}", - legend=f"{readable_name[sys_name]}, {readable_elev}" - ) -# ^: typing.List[Results] - -plots = post_processor.generate_ccdf_plots_from_results( - ccdf_results -) - -post_processor.add_plots(plots) - -plots = post_processor.generate_cdf_plots_from_results( - cdf_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 = -154.0 # dBm/MHz -perc_time = 0.01 -system_dl_interf_power_per_mhz = post_processor.get_plot_by_results_attribute_name( - "system_dl_interf_power_per_mhz", plot_type="ccdf") -system_dl_interf_power_per_mhz.add_vline( - protection_criteria, - line_dash="dash", - annotation=dict( - text="Protection Criteria: " + - str(protection_criteria) + - " dB[W/MHz]", - xref="x", - yref="y", - x=protection_criteria + - 0.5, - y=0.8, - font=dict( - size=12, - color="red"))) -system_dl_interf_power_per_mhz.add_hline(perc_time, line_dash="dash", annotation=dict( - text="Time Percentage: " + str(perc_time * 100) + "%", - xref="x", yref="y", - x=protection_criteria + 0.5, y=perc_time + 0.01, - font=dict(size=12, color="blue") -)) - - -attributes_to_plot = [ - ("imt_system_antenna_gain", "cdf"), - ("imt_system_path_loss", "cdf"), - ("system_dl_interf_power", "cdf"), - ("system_dl_interf_power_per_mhz", "ccdf"), - ("system_imt_antenna_gain", "cdf"), - ("system_inr", "cdf"), -] - -for attr, plot_type in attributes_to_plot: - file = os.path.join(campaign_base_dir, "output", f"{attr}.html") - post_processor\ - .get_plot_by_results_attribute_name(attr, plot_type=plot_type)\ - .write_html(file=file, include_plotlyjs="cdn", auto_open=auto_open) diff --git a/sharc/campaigns/mss_d2d_to_eess/scripts/start_simulations_multi_thread.py b/sharc/campaigns/mss_d2d_to_eess/scripts/start_simulations_multi_thread.py deleted file mode 100644 index 2864e427f..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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_eess" - -# 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_eess/scripts/start_simulations_single_thread.py b/sharc/campaigns/mss_d2d_to_eess/scripts/start_simulations_single_thread.py deleted file mode 100644 index 98e457d90..000000000 --- a/sharc/campaigns/mss_d2d_to_eess/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,12 +0,0 @@ -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_eess" - -# 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_eess.yaml') diff --git a/sharc/campaigns/mss_d2d_to_imt/input/.gitignore b/sharc/campaigns/mss_d2d_to_imt/input/.gitignore deleted file mode 100644 index aaefe7107..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/input/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# 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 deleted file mode 100644 index bcf2a9156..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_dl_co_channel_system_A.yaml +++ /dev/null @@ -1,461 +0,0 @@ -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, 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: 67465 - ########################################################################### - # 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: 2160.0 - ########################################################################### - # IMT bandwidth [MHz] - bandwidth: 5.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 - From 5D/716 NON-AAS Antenna - Urban - 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: 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: 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 # Only one UE to access the worst-case interference from MSS-DC - ########################################################################### - # 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: 2160.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: -54.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 - polarization_loss: 0.0 # dB - # 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 deleted file mode 100644 index 7ff6776a0..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_lat_variation_template.yaml +++ /dev/null @@ -1,461 +0,0 @@ -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, 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: 67463 - ########################################################################### - # 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: 2160.0 - ########################################################################### - # IMT bandwidth [MHz] - bandwidth: 5.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 - From 5D/716 NON-AAS Antenna - Urban - 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 # Only one UE to access the worst-case interference from MSS-DC - ########################################################################### - # 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: 2160.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: -54.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 - polarization_loss: 0.0 # dB - # 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 deleted file mode 100644 index 8950bc7c0..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/input/parameters_mss_d2d_to_imt_ul_co_channel_system_A.yaml +++ /dev/null @@ -1,461 +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, 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: 67464 - ########################################################################### - # 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: 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 - 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 - From 5D/716 NON-AAS Antenna - Urban - 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 # Only one UE to access the worst-case interference from MSS-DC - ########################################################################### - # 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: 2160.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: -54.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 - polarization_loss: 0.0 # dB - # 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 deleted file mode 100644 index 124aa6e42..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/readme.md +++ /dev/null @@ -1,26 +0,0 @@ -# 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 deleted file mode 100644 index 38c6e504c..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/orbit_model_anaylisis.py +++ /dev/null @@ -1,145 +0,0 @@ -# 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, - model_time_as_random_variable=False, - t_min=0.0, - t_max=None - ) - - # 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) - # altitude of the satellites in kilometers - sat_altitude_km = orbit.apogee_alt_km - num_of_visible_sats_per_drop = [] - elev_angles = calc_elevation( - GROUND_STA_LAT, - pos_vec['lat'], - GROUND_STA_LON, - pos_vec['lon'], - sat_height=sat_altitude_km * 1e3, - es_height=0) - 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_height=sat_altitude_km * 1e3, - es_height=0) - 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 deleted file mode 100644 index 1a6b2f744..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/parameter_gen_lat_variation.py +++ /dev/null @@ -1,48 +0,0 @@ -"""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 deleted file mode 100644 index e9cf059c1..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results.py +++ /dev/null @@ -1,150 +0,0 @@ -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 deleted file mode 100644 index 200ae7c5d..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/plot_results_lat_variation.py +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index 6fc589b96..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations.py +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index af7756669..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/start_simulations_lat_variation.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index ce2e04fdc..000000000 --- a/sharc/campaigns/mss_d2d_to_imt/scripts/taylor_diagram_for_mss_d2d.py +++ /dev/null @@ -1,62 +0,0 @@ - -""" -Script to generate a Taylor diagram for the MSS D2D to IMT sharing scenario. -Parameters are based on Annex 4 to Working Party 4C Chair’s Report, 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 deleted file mode 100644 index d6b7ef32c..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/input/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.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 deleted file mode 100644 index c3b7989fd..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 8fc8c2bdc..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/base_input.yaml +++ /dev/null @@ -1,566 +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, 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: "OFF" - ########################################################################### - # 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: 500 - # 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] - # NOTE: element gain includes ohmic loss - ohmic_loss: 0.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 - ########################################################################### - # 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: 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: 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: 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: 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: 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 - # Polarization loss [dB] - # P.619 suggests 3dB polarization loss as good constant value for monte carlo - polarization_loss: 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: - # 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: FSPL - # 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 deleted file mode 100644 index 850e2de78..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/generate_parameters_from_reference.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Generates scenarios based on main parameters -""" -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']['beam_positioning'] = {} - - parameters['mss_d2d']['beam_positioning']['type'] = "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE" - - # for uniform area distribution - parameters['mss_d2d']['beam_positioning']['angle_from_subsatellite_phi'] = { - 'type': "~U(MIN,MAX)", 'distribution': {'min': -180., 'max': 180., }} - parameters['mss_d2d']['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 deleted file mode 100644 index 25b66cb86..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_3d_param_file.py +++ /dev/null @@ -1,177 +0,0 @@ -# Generates a 3D plot of the Earth with the satellites positions -""" -Script to generate a 3D plot of the Earth with satellite positions for the MSS D2D to IMT cross-border scenario. -""" -# https://geopandas.org/en/stable/docs/user_guide/io.html -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 -from sharc.satellite.scripts.plot_globe import plot_globe_with_borders, plot_mult_polygon - - -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, False) - - polygons_lim = plot_mult_polygon( - parameters.mss_d2d.sat_is_active_if.lat_long_inside_country.filter_polygon, - geoconv, - False) - 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 deleted file mode 100644 index 4d6b3b99c..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/plot_results.py +++ /dev/null @@ -1,148 +0,0 @@ -"""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() - -# If set to True the plots will be opened in the browser automatically -auto_open = False - -# 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 link in ["dl", "ul"]: - for prefix in prefixes: - if prefix == "border": - km = "0km" - else: - km = prefix - post_processor\ - .add_plot_legend_pattern( - dir_name_contains=f"{prefix}_base_" + link, - legend=f"19 sectors ({km})" - ).add_plot_legend_pattern( - dir_name_contains=f"{prefix}_activate_random_beam_5p_" + link, - legend=f"19 sectors, load=1/19 ({km})" - ).add_plot_legend_pattern( - dir_name_contains=f"{prefix}_activate_random_beam_30p_" + link, - legend=f"19 sectors, load=30% ({km})" - ).add_plot_legend_pattern( - dir_name_contains=f"{prefix}_random_pointing_1beam_" + link, - legend=f"1 sector random pointing ({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) - -# Define line styles for different prefixes - the size must match the -# number of unique legends -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_ccdf_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", plot_type='ccdf')\ - .add_vline(protection_criteria, line_dash="dash", annotation=dict( - text="Protection criteria", - xref="x", - yref="paper", - x=protection_criteria + 1.0, # Offset for visibility - y=0.95 - )) -perc_of_time = 0.01 -post_processor\ - .get_plot_by_results_attribute_name("imt_dl_inr", plot_type="ccdf")\ - .add_hline(perc_of_time, line_dash="dash") -post_processor\ - .get_plot_by_results_attribute_name("imt_ul_inr", plot_type='ccdf')\ - .add_vline(protection_criteria, line_dash="dash", annotation=dict( - text="Protection criteria", - xref="x", - yref="paper", - x=protection_criteria + 1.0, # Offset for visibility - y=0.95 - )) -post_processor\ - .get_plot_by_results_attribute_name("imt_ul_inr", plot_type="ccdf")\ - .add_hline(perc_of_time, 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", plot_type='ccdf')\ - .add_vline(pfd_protection_criteria, line_dash="dash", annotation=dict( - text="PFD protection criteria", - xref="x", - yref="paper", - x=pfd_protection_criteria + 1.0, # Offset for visibility - y=0.95 - )) - - -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", -] - -# 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: - fig = post_processor.get_plot_by_results_attribute_name( - attr, plot_type='ccdf') - fig.update_layout(template="plotly_white") - fig.write_html( - htmls_dir / f"{attr}.html", - include_plotlyjs="cdn", - auto_open=auto_open) 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 deleted file mode 100644 index 9a6a98363..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_multi_thread.py +++ /dev/null @@ -1,14 +0,0 @@ - -"""Script to start MSS D2D to IMT cross-border campaign simulations in multi-threaded mode.""" -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 deleted file mode 100644 index d43854297..000000000 --- a/sharc/campaigns/mss_d2d_to_imt_cross_border/scripts/start_simulations_single_thread.py +++ /dev/null @@ -1,16 +0,0 @@ - -"""Script to start MSS D2D to IMT cross-border campaign simulations in single-threaded mode.""" -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_cross_border_0km_random_pointing_1beam_dl.yaml') diff --git a/sharc/input/parameters.yaml b/sharc/input/parameters.yaml index 8edf9877f..da66eb265 100644 --- a/sharc/input/parameters.yaml +++ b/sharc/input/parameters.yaml @@ -914,11 +914,18 @@ single_space_station: ########################################################################### # earth station lat long [deg] es_long_deg: 1 + # Where to point antenna if azimuth|elevation use "POINTING_AT_LAT_LONG_ALT" + # latitude [deg] + pointing_at_lat: 0. + # longitude [deg] + pointing_at_long: 0. + # altitude [m] + pointing_at_alt: 0. ########################################################################### # Antenna azimuth angle [degrees] azimuth: ########################################################################### - # Type of azimuth. May be "FIXED" or "POINTING_AT_IMT" + # Type of azimuth. May be "FIXED", "POINTING_AT_IMT" or "POINTING_AT_LAT_LONG_ALT" type: POINTING_AT_IMT ########################################################################### # Arbitrary value of azimuth when type == "FIXED" [deg] @@ -927,7 +934,7 @@ single_space_station: # Antenna elevation angle [degrees] elevation: ########################################################################### - # Type of elevation. May be "FIXED" or "POINTING_AT_IMT" + # Type of elevation. May be "FIXED", "POINTING_AT_IMT" or "POINTING_AT_LAT_LONG_ALT" type: POINTING_AT_IMT ########################################################################### # Arbitrary value of elevation when type == "FIXED" [deg] diff --git a/sharc/main_cli.py b/sharc/main_cli.py index 6d13173e8..a9ffa5363 100644 --- a/sharc/main_cli.py +++ b/sharc/main_cli.py @@ -7,9 +7,9 @@ import os import sys -import getopt +import argparse -from sharc.support.sharc_logger import Logging, SimulationLogger +from sharc.support.sharc_logger import setup_logging, SimulationLogger from sharc.controller import Controller from sharc.gui.view_cli import ViewCli from sharc.model import Model @@ -34,28 +34,25 @@ def main(argv): param_file = "" - try: - opts, _ = getopt.getopt(argv, "hp:") - except getopt.GetoptError: - print("usage: main_cli.py -p ") - sys.exit(2) - - if not opts: - param_file = os.path.join(os.getcwd(), "input", "parameters.yaml") - else: - for opt, arg in opts: - if opt == "-h": - print("usage: main_cli.py -p ") - sys.exit() - elif opt == "-p": - param_file = os.path.join(os.getcwd(), arg) + parser = argparse.ArgumentParser(description="SHARC - Radio Sharing and Compatiblity Monte Carlo Simulator") + parser.add_argument("-p", "--param-file", default=os.path.join(os.getcwd(), "input", "parameters.yaml"), + help="Path to parameter file (default: input/parameters.yaml)") + parser.add_argument("-l", "--log-file", default=None, + help="Path to output log file (optional)") + + args = parser.parse_args(argv) + param_file = os.path.join(os.getcwd(), args.param_file) if not os.path.isabs(args.param_file) else args.param_file + + log_file = None + if args.log_file is not None: + log_file = os.path.join(os.getcwd(), args.log_file) if not os.path.isabs(args.log_file) else args.log_file + + setup_logging(log_file=log_file) # Logger setup start sim_logger = SimulationLogger(param_file) sim_logger.start() - Logging.setup_logging() - model = Model() view_cli = ViewCli() controller = Controller() diff --git a/sharc/parameters/antenna/parameters_antenna_s1528.py b/sharc/parameters/antenna/parameters_antenna_s1528.py index e0eb4703c..ed2f204f1 100644 --- a/sharc/parameters/antenna/parameters_antenna_s1528.py +++ b/sharc/parameters/antenna/parameters_antenna_s1528.py @@ -15,31 +15,35 @@ class ParametersAntennaS1528(ParametersBase): 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 + antenna_l_s: float = None # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] - antenna_3_dB_bw: float = 0.65 + antenna_3_dB_bw: float = None ##################################################################### # 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 + slr: float = None # Number of secondary lobes considered in the diagram (coincide with the # roots of the Bessel function) - n_side_lobes: int = 2 + n_side_lobes: int = None # 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 + l_r: float = None + l_t: float = None + + # Recommends 1.2 only + # For elliptical antennas, this is the ratio major axis/minor axis + # we assume circular antennas, so z = 1 + major_minor_axis_ratio: float = None + + # Far-out side-lobe level + far_out_side_lobe: float = None def load_parameters_from_file(self, config_file: str): """Load the parameters from file an run a sanity check. @@ -76,23 +80,6 @@ def load_from_parameters(self, param: ParametersBase): 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): """ Validate the parameters for the S.1528 antenna configuration. @@ -109,12 +96,3 @@ def validate(self, ctx: str): 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/antenna/parameters_antenna_s672.py b/sharc/parameters/antenna/parameters_antenna_s672.py new file mode 100644 index 000000000..f174c5c62 --- /dev/null +++ b/sharc/parameters/antenna/parameters_antenna_s672.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersAntennaS672(ParametersBase): + """Dataclass containing the Antenna Pattern S.672 parameters for the simulator. + """ + section_name: str = "ITU-R-S.678" + # Peak antenna gain [dBi] + antenna_gain: float | None = None + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s: float | None = None + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB_bw: float | None = None + + 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_s678") + + def load_from_parameters(self, param: ParametersBase): + """Load from another parameter object + + Parameters + ---------- + param : ParametersBase + Parameters object containing ParametersAntennaS1528 + """ + self.antenna_l_s = param.antenna_l_s + self.antenna_3_dB_bw = param.antenna_3_dB_bw + return self + + def validate(self, ctx: str): + """ + Validate the parameters for the S.1528 antenna configuration. + + Checks that required attributes are set and that the antenna pattern is valid. + + Parameters + ---------- + ctx : str + Context string for error messages. + """ + # Now do the sanity check for some parameters + if None in [self.antenna_l_s, self.antenna_3_dB_bw]: + raise ValueError( + f"{ctx}.[antenna_l_s, antenna_3_dB_bw] = {[self.antenna_l_s, self.antenna_3_dB_bw]}.\ + They need to all be set!") diff --git a/sharc/parameters/imt/parameters_grid.py b/sharc/parameters/imt/parameters_grid.py new file mode 100644 index 000000000..551a5d2df --- /dev/null +++ b/sharc/parameters/imt/parameters_grid.py @@ -0,0 +1,569 @@ +from warnings import warn +from dataclasses import dataclass, field +import numpy as np +import typing +from pathlib import Path +import shapely as shp +from collections.abc import Iterable + +from sharc.support.sharc_utils import load_gdf +from sharc.support.sharc_geom import ( + shrink_countries_by_km, + generate_grid_in_multipolygon, + shrink_lonlat_polygon_by_km, +) +from sharc.satellite.utils.sat_utils import lla2ecef +from sharc.parameters.parameters_base import ParametersBase + +SHARC_ROOT_DIR = (Path(__file__) / ".." / ".." / ".." / "..").resolve() + + +@dataclass +class ParametersZone(ParametersBase): + """Defines parameters for the creation of a 'zone' polygon. + """ + @dataclass + class ParametersCircle(ParametersBase): + center_lat: typing.Optional[float] = None + center_lon: typing.Optional[float] = None + radius_km: typing.Optional[float] = None + + _polygon: shp.Polygon = None + + def validate(self, ctx): + """ + Validates instance parameters. + + Ensures attributes make sense + + Parameters + ---------- + ctx : str + Context string for error messages. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + if None in [ + self.center_lat, + self.center_lon, + self.radius_km, + ]: + raise ValueError( + f"{ctx}.(center_lat|center_lon|radius_km) need to be set" + ) + + if self.radius_km <= 0: + raise ValueError(f"{ctx}.radius_km needs to be positive") + + if not (-180. <= self.center_lon <= 180.): + raise ValueError(f"{ctx}.center_lon needs to be in [-180, 180]") + + if not (-90. <= self.center_lat <= 90.): + raise ValueError(f"{ctx}.center_lat needs to be in [-90, 90]") + + super().validate(ctx) + + self._calculate_polygon() + + def _calculate_polygon(self): + """ + Calculates circle lon,lat polygon according to its attributes + """ + self._polygon = shrink_lonlat_polygon_by_km( + shp.geometry.Point(self.center_lon, self.center_lat), + -self.radius_km + ) + + @dataclass + class ParametersFromCountries(ParametersBase): + country_shapes_filename: Path = SHARC_ROOT_DIR / "sharc" / \ + "data" / "countries" / "ne_110m_admin_0_countries.shp" + + 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 = None + + _polygon: shp.Polygon = None + _unprocessed_polygon: shp.Polygon = None + + def validate(self, ctx): + """ + Validates instance parameters. + Raises ValueError + If a parameter is not valid. + """ + # conditional is weird due to suboptimal way of working with nested + # array parameters + if len(self.country_names) == 0 or ( + 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") + + if not isinstance( + self.margin_from_border, + float) and not isinstance( + self.margin_from_border, + int): + raise ValueError( + f"{ctx}.margin_from_border needs to be a number") + + self._calculate_polygon() + + def _calculate_polygon(self): + filtered_gdf = load_gdf( + self.country_shapes_filename, + { + "NAME": self.country_names + }, + "from_countries", + ) + + # shrink countries and unite + # them into a single MultiPolygon + self._unprocessed_polygon = filtered_gdf.geometry.values + + shrinked = shrink_countries_by_km( + filtered_gdf.geometry.values, self.margin_from_border + ) + self._polygon = shp.ops.unary_union(shrinked) + + assert self._polygon.is_valid, \ + shp.validation.explain_validity(self._polygon) + + assert not self._polygon.is_empty, \ + "Can't have a empty grid_borders_polygon as filter" + + __ALLOWED_TYPES = [None, "CIRCLE", "FROM_COUNTRIES"] + _ACCEPT_NONE_TYPE: bool = False + + type: typing.Literal[None, "CIRCLE", "FROM_COUNTRIES"] = None + + circle: ParametersCircle = field(default_factory=ParametersCircle) + + from_countries: ParametersFromCountries = field(default_factory=ParametersFromCountries) + + _polygon: shp.geometry.Polygon = None + _unprocessed_polygon: shp.geometry.Polygon = None + + def _set_chosen_pol(self): + self.chosen_pol = None + if self.type is None: + return + + if self.type == "CIRCLE": + self.chosen_pol = self.circle + elif self.type == "FROM_COUNTRIES": + self.chosen_pol = self.from_countries + else: + raise NotImplementedError( + f"Cannot set chosen_pol for type == '{self.type}'" + ) + + self._polygon = self.chosen_pol._polygon + if hasattr(self.chosen_pol, "_unprocessed_polygon"): + self._unprocessed_polygon = self.chosen_pol._unprocessed_polygon + else: + self._unprocessed_polygon = self.chosen_pol._polygon + + def validate(self, ctx): + """ + Validates instance parameters. + + Ensures attributes make sense + + Parameters + ---------- + ctx : str + Context string for error messages. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + if self.type not in self.__ALLOWED_TYPES: + raise ValueError(f"{ctx}.type should be in {self.__ALLOWED_TYPES}") + + if self.type is None: + return + + if self.type == "CIRCLE": + self.circle.validate(f"{ctx}.circle") + elif self.type == "FROM_COUNTRIES": + self.from_countries.validate(f"{ctx}.from_countries") + else: + raise NotImplementedError( + "No validation implemented for\n" + f"\t{ctx}.type == {self.type}" + ) + + self._set_chosen_pol() + self._calculate_polygon() + + if (not self._polygon.is_valid + or self._polygon.is_empty + or self._polygon.area <= 0 + ): + raise Exception(f"Bad {ctx}._polygon was generated") + + def _calculate_polygon(self): + self._set_chosen_pol() + + if self.chosen_pol is None: + if self._ACCEPT_NONE_TYPE: + return + raise ValueError("No polygon type has been set for zone") + + self.chosen_pol._calculate_polygon() + self._polygon = self.chosen_pol._polygon + if hasattr(self.chosen_pol, "_unprocessed_polygon"): + self._unprocessed_polygon = self.chosen_pol._unprocessed_polygon + else: + self._unprocessed_polygon = self.chosen_pol._polygon + + def apply_exclusion_zone(self, lon, lat): + """ + Returns coordinates that are not contained in polygon + """ + if self.type is None: + return np.stack((lon, lat)) + + msk = ~shp.vectorized.contains( + self._polygon, + lon, + lat, + ) + + return np.stack((lon[msk], lat[msk])) + + +@dataclass +class ParametersTerrestrialGrid(ParametersBase): + """Defines parameters for the creation of a (lon, lat) grid considering + spherical Earth. + """ + cell_radius: float = None + + transform_grid_randomly: bool = False + + grid_exclusion_zone: ParametersZone = field( + default_factory=lambda: ParametersZone(_ACCEPT_NONE_TYPE=True) + ) + + grid_in_zone: ParametersZone = field( + default_factory=lambda: ParametersZone(type="FROM_COUNTRIES") + ) + + # 2xN, ([lon], [lat]) + lon_lat_grid = None + + def validate(self, ctx: str): + """ + Validate the service grid parameters. + + Ensures that country names and beam radius are set and valid, and sets grid margin if needed. + + Parameters + ---------- + ctx : str + Context string for error messages. + """ + # NOTE: prefer this to be set by a parent/composition + if not isinstance( + self.cell_radius, + float) and not isinstance( + self.cell_radius, + int): + raise ValueError(f"{ctx}.cell_radius needs to be a number") + + if self.grid_in_zone.from_countries.margin_from_border is None: + self.grid_in_zone.from_countries.margin_from_border = self.cell_radius / 1e3 + if self.grid_exclusion_zone.from_countries.margin_from_border is None: + self.grid_exclusion_zone.from_countries.margin_from_border = self.cell_radius / 1e3 + + super().validate(ctx) + + self._recalculate_grid_polygon_if_needed(ctx) + + def reset_grid( + self, + ctx: str, + rng: np.random.RandomState, + force_update=False, + ): + """ + After creating grid, there are some features that can only be implemented + with knowledge of other parts of the simulator. + """ + needed = self._recalculate_grid_polygon_if_needed(ctx, force_update) + + if needed or force_update: + lon, lat = generate_grid_in_multipolygon( + self.grid_in_zone._polygon, + self.cell_radius, + self.transform_grid_randomly, + rng + ) + + self.lon_lat_grid = self.grid_exclusion_zone.apply_exclusion_zone( + lon, lat + ) + + self.ecef_grid = lla2ecef( + self.lon_lat_grid[1], self.lon_lat_grid[0], 0) + + def _recalculate_grid_polygon_if_needed(self, ctx: str, force_update=False) -> bool: + if self.grid_in_zone._polygon is not None and not force_update: + return False + self.grid_in_zone._calculate_polygon() + self.grid_exclusion_zone._calculate_polygon() + return True + + +@dataclass +class ParametersSatelliteWithServiceGrid(ParametersTerrestrialGrid): + """ + Adds parameters for satellite that uses terrestrial service grid for positioning + """ + # margin from inside of border [km] + # if positive, makes border smaller by x km + # if negative, makes border bigger by x km + eligible_sats_margin_from_border: float = None + + eligibility_polygon: typing.Union[shp.MultiPolygon, shp.Polygon] = None + + beam_radius: float = None + + # [deg] + minimum_service_angle: float = 5.0 + + def validate(self, ctx): + """Validates instance parameters. + Parameters + ctx : str + Context string for error messages. + Raises ValueError + If a parameter is not valid. + """ + if self.cell_radius is not None: + warn( + f"{ctx}.cell_radius should be set through beam_radius parameter" + ) + self.cell_radius = self.beam_radius + + if self.minimum_service_angle < 0. or self.minimum_service_angle > 90: + raise ValueError(f"{ctx}.minimum_service_angle should be in [0, 90]") + + if not isinstance( + self.eligible_sats_margin_from_border, + float) and not isinstance( + self.eligible_sats_margin_from_border, + int): + raise ValueError( + f"{ctx}.eligible_sats_margin_from_border needs to be a number") + + super().validate(ctx) + + def load_from_active_sat_conditions( + self, + sat_is_active_if: "ParametersSelectActiveSatellite", + ): + """ + Load grid parameters from active satellite selection conditions. + + Parameters + ---------- + sat_is_active_if : ParametersSelectActiveSatellite + The object containing satellite selection and country information. + """ + if ( + len(self.grid_in_zone.from_countries.country_names) == 0 + or self.grid_in_zone.from_countries.country_names[0] == "" + ): + self.grid_in_zone.from_countries.country_names = sat_is_active_if.lat_long_inside_country.country_names + if self.eligible_sats_margin_from_border is None: + self.eligible_sats_margin_from_border = sat_is_active_if.lat_long_inside_country.margin_from_border + + def _recalculate_grid_polygon_if_needed(self, ctx: str, force_update=False): + if self.eligibility_polygon is not None and not force_update: + return False + self.grid_in_zone._calculate_polygon() + self.grid_exclusion_zone._calculate_polygon() + + # For creating selectable satellite zone + # we consider polygon before shrinking country borders or other + # geometry processing + pols = self.grid_in_zone._unprocessed_polygon + + if not isinstance(pols, Iterable): + pols = [pols] + + self.eligibility_polygon = shp.ops.unary_union(shrink_countries_by_km( + pols, + self.eligible_sats_margin_from_border, + )) + + assert self.eligibility_polygon.is_valid, \ + shp.validation.explain_validity(self.eligibility_polygon) + + assert not self.eligibility_polygon.is_empty, \ + "Can't have a empty eligibility_polygon as filter" + + return True + + +@dataclass +class ParametersSelectActiveSatellite(ParametersBase): + """ + Parameters for selecting active satellites based on geographic and elevation criteria. + """ + @dataclass + class ParametersLatLongInsideCountry(ParametersBase): + """ + Parameters for checking if a location is inside a given country. + """ + country_shapes_filename: Path = SHARC_ROOT_DIR / "sharc" / \ + "data" / "countries" / "ne_110m_admin_0_countries.shp" + + 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: typing.Union[shp.MultiPolygon, shp.Polygon] = None + + def validate(self, ctx: str): + """ + Validate the country names and filter polygon for the location check. + + Parameters + ---------- + ctx : str + Context string for error messages. + """ + # conditional is weird due to suboptimal way of working with nested + # array parameters + if len(self.country_names) == 0 or ( + 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") + + self.reset_filter_polygon(ctx) + + def reset_filter_polygon(self, ctx: str, force_update=False): + """ + Reset the filter polygon for country boundaries, optionally forcing update. + + Parameters + ---------- + ctx : str + Context string for error messages. + force_update : bool, optional + If True, force update even if already set (default is False). + """ + if self.filter_polygon is not None and not force_update: + return + + filtered_gdf = load_gdf( + self.country_shapes_filename, + { + "NAME": self.country_names + }, + ctx, + ) + + # 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 + )) + + assert self.filter_polygon.is_valid, shp.validation.explain_validity( + self.filter_polygon) + + __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): + """ + Validate the satellite selection conditions and their parameters. + + Parameters + ---------- + ctx : str + Context string for error messages. + """ + 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!" + ) diff --git a/sharc/parameters/imt/parameters_imt_mss_dc.py b/sharc/parameters/imt/parameters_imt_mss_dc.py index 3f31ed8da..c14079f4f 100644 --- a/sharc/parameters/imt/parameters_imt_mss_dc.py +++ b/sharc/parameters/imt/parameters_imt_mss_dc.py @@ -3,13 +3,10 @@ import numpy as np import typing from pathlib import Path -import shapely as shp -from sharc.support.sharc_utils import load_gdf -from sharc.support.sharc_geom import shrink_countries_by_km, generate_grid_in_multipolygon -from sharc.satellite.utils.sat_utils import lla2ecef from sharc.parameters.parameters_base import ParametersBase from sharc.parameters.parameters_orbit import ParametersOrbit +from sharc.parameters.imt.parameters_grid import ParametersSatelliteWithServiceGrid, ParametersSelectActiveSatellite SHARC_ROOT_DIR = (Path(__file__) / ".." / ".." / ".." / "..").resolve() @@ -129,147 +126,6 @@ def validate(self, ctx): f"No validation implemented for {ctx}.type = { self.type}") - @dataclass - class ParametersServiceGrid(ParametersBase): - country_shapes_filename: Path = SHARC_ROOT_DIR / "sharc" / \ - "data" / "countries" / "ne_110m_admin_0_countries.shp" - - country_names: list[str] = field(default_factory=lambda: list([""])) - - transform_grid_randomly: bool = False - - # margin from inside of border [km] - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - grid_margin_from_border: float = None - - # margin from inside of border [km] - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - eligible_sats_margin_from_border: float = None - - beam_radius: float = None - - # 2xN, ([lon], [lat]) - lon_lat_grid = None - - eligibility_polygon: typing.Union[shp.MultiPolygon, shp.Polygon] = None - - def validate(self, ctx: str): - """ - Validate the service grid parameters. - - Ensures that country names and beam radius are set and valid, and sets grid margin if needed. - - Parameters - ---------- - ctx : str - Context string for error messages. - """ - # conditional is weird due to suboptimal way of working with nested - # array parameters - if len(self.country_names) == 0 or ( - 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") - - # NOTE: prefer this to be set by a parent/composition - if not isinstance( - self.beam_radius, - float) and not isinstance( - self.beam_radius, - int): - raise ValueError(f"{ctx}.beam_radius needs to be a number") - - if self.grid_margin_from_border is None: - self.grid_margin_from_border = self.beam_radius / 1e3 - if not isinstance( - self.grid_margin_from_border, - float) and not isinstance( - self.grid_margin_from_border, - int): - raise ValueError( - f"{ctx}.grid_margin_from_border needs to be a number") - - if not isinstance( - self.eligible_sats_margin_from_border, - float) and not isinstance( - self.eligible_sats_margin_from_border, - int): - raise ValueError( - f"{ctx}.eligible_sats_margin_from_border needs to be a number") - - self._load_geom_from_file_if_needed(ctx) - - super().validate(ctx) - - def load_from_active_sat_conditions( - self, - sat_is_active_if: "ParametersSelectActiveSatellite", - ): - """ - Load grid parameters from active satellite selection conditions. - - Parameters - ---------- - sat_is_active_if : ParametersSelectActiveSatellite - The object containing satellite selection and country information. - """ - if len(self.country_names) == 0 or self.country_names[0] == "": - self.country_names = sat_is_active_if.lat_long_inside_country.country_names - if self.eligible_sats_margin_from_border is None: - self.eligible_sats_margin_from_border = sat_is_active_if.lat_long_inside_country.margin_from_border - - def reset_grid( - self, - ctx: str, - rng: np.random.RandomState, - force_update=False, - ): - """ - After creating grid, there are some features that can only be implemented - with knowledge of other parts of the simulator. - """ - self._load_geom_from_file_if_needed(ctx, force_update) - - self.lon_lat_grid = generate_grid_in_multipolygon( - self.grid_borders_polygon, - self.beam_radius, - self.transform_grid_randomly, - rng - ) - - self.ecef_grid = lla2ecef( - self.lon_lat_grid[1], self.lon_lat_grid[0], 0) - - def _load_geom_from_file_if_needed(self, ctx: str, force_update=False): - if self.eligibility_polygon is not None and not force_update: - return - filtered_gdf = load_gdf( - self.country_shapes_filename, - { - "NAME": self.country_names - }, - ctx, - ) - - # shrink countries and unite - # them into a single MultiPolygon - shrinked = shrink_countries_by_km( - filtered_gdf.geometry.values, self.grid_margin_from_border - ) - self.grid_borders_polygon = shp.ops.unary_union(shrinked) - - assert self.grid_borders_polygon.is_valid, \ - shp.validation.explain_validity(self.grid_borders_polygon) - - assert not self.grid_borders_polygon.is_empty, \ - "Can't have a empty grid_borders_polygon as filter" - - self.eligibility_polygon = shp.ops.unary_union(shrink_countries_by_km( - filtered_gdf.geometry.values, self.eligible_sats_margin_from_border - )) - __ALLOWED_TYPES = [ "ANGLE_FROM_SUBSATELLITE", "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE", @@ -297,8 +153,8 @@ def _load_geom_from_file_if_needed(self, ctx: str, force_update=False): default_factory=lambda: ParametersSectorPositioning.ParametersSectorValue( MIN_VALUE=0.0)) - service_grid: ParametersServiceGrid = field( - default_factory=ParametersServiceGrid) + service_grid: ParametersSatelliteWithServiceGrid = field( + default_factory=ParametersSatelliteWithServiceGrid) def validate(self, ctx): """ @@ -333,158 +189,6 @@ def validate(self, ctx): ) -@dataclass -class ParametersSelectActiveSatellite(ParametersBase): - """ - Parameters for selecting active satellites based on geographic and elevation criteria. - """ - @dataclass - class ParametersLatLongInsideCountry(ParametersBase): - """ - Parameters for checking if a location is inside a given country. - """ - country_shapes_filename: Path = SHARC_ROOT_DIR / "sharc" / \ - "data" / "countries" / "ne_110m_admin_0_countries.shp" - - 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: typing.Union[shp.MultiPolygon, shp.Polygon] = None - - def validate(self, ctx: str): - """ - Validate the country names and filter polygon for the location check. - - Parameters - ---------- - ctx : str - Context string for error messages. - """ - # conditional is weird due to suboptimal way of working with nested - # array parameters - if len(self.country_names) == 0 or ( - 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") - - self.reset_filter_polygon(ctx) - - def reset_filter_polygon(self, ctx: str, force_update=False): - """ - Reset the filter polygon for country boundaries, optionally forcing update. - - Parameters - ---------- - ctx : str - Context string for error messages. - force_update : bool, optional - If True, force update even if already set (default is False). - """ - if self.filter_polygon is not None and not force_update: - return - - filtered_gdf = load_gdf( - self.country_shapes_filename, - { - "NAME": self.country_names - }, - ctx, - ) - - # 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 - )) - - assert self.filter_polygon.is_valid, shp.validation.explain_validity( - self.filter_polygon) - - __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): - """ - Validate the satellite selection conditions and their parameters. - - Parameters - ---------- - ctx : str - Context string for error messages. - """ - 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.""" diff --git a/sharc/parameters/imt/parameters_imt_topology.py b/sharc/parameters/imt/parameters_imt_topology.py index c4f633cb0..8f4c68025 100644 --- a/sharc/parameters/imt/parameters_imt_topology.py +++ b/sharc/parameters/imt/parameters_imt_topology.py @@ -8,6 +8,7 @@ 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 +from sharc.parameters.imt.parameters_spherical_sampling_from_grid import ParametersSamplingFromSphericalGrid @dataclass @@ -18,7 +19,8 @@ class ParametersImtTopology(ParametersBase): nested_parameters_enabled = True type: typing.Literal[ - "MACROCELL", "HOTSPOT", "INDOOR", "SINGLE_BS", "NTN", "MSS_DC" + "MACROCELL", "HOTSPOT", "INDOOR", "SINGLE_BS", "NTN", "MSS_DC", + "SAMPLING_FROM_SPHERICAL_GRID" ] = "MACROCELL" # these parameters are needed in case the other system requires coordinate @@ -33,6 +35,7 @@ class ParametersImtTopology(ParametersBase): single_bs: ParametersSingleBS = field(default_factory=ParametersSingleBS) ntn: ParametersNTN = field(default_factory=ParametersNTN) mss_dc: ParametersImtMssDc = field(default_factory=ParametersImtMssDc) + sampling_from_spherical_grid: ParametersSamplingFromSphericalGrid = field(default_factory=ParametersSamplingFromSphericalGrid) def validate(self, ctx): """ @@ -60,6 +63,8 @@ def validate(self, ctx): self.ntn.validate(f"{ctx}.ntn") case "MSS_DC": self.mss_dc.validate(f"{ctx}.mss_dc") + case "SAMPLING_FROM_SPHERICAL_GRID": + self.sampling_from_spherical_grid.validate(f"{ctx}.sampling_from_spherical_grid") case _: raise NotImplementedError( f"{ctx}.type == '{ diff --git a/sharc/parameters/imt/parameters_spherical_sampling_from_grid.py b/sharc/parameters/imt/parameters_spherical_sampling_from_grid.py new file mode 100644 index 000000000..d1808b513 --- /dev/null +++ b/sharc/parameters/imt/parameters_spherical_sampling_from_grid.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass, field + +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.imt.parameters_grid import ParametersTerrestrialGrid + + +@dataclass +class ParametersSamplingFromSphericalGrid(ParametersBase): + """ + Data class for spherical sampling from grid topology parameters. + """ + num_bs: int = None + + # It is necessary to decouple coverage radius and grid cell radius + # so that grid point calculation and ue positioning are decoupled + # This parameter determines max ue position + max_ue_distance: float = None + + grid: ParametersTerrestrialGrid = field(default_factory=ParametersTerrestrialGrid) + + def validate(self, ctx): + """ + Validate the topology parameters. + + Ensures that all attributes are set to valid values and types. + + Parameters + ---------- + ctx : str + Context string for error messages. + """ + if not isinstance( + self.num_bs, + int) or self.num_bs < 0: + raise ValueError(f"{ctx}.num_bs must be non-negative") + + if self.max_ue_distance <= 0.0: + raise ValueError(f"{ctx}.max_ue_distance must be positive") + + super().validate(ctx) diff --git a/sharc/parameters/parameters_antenna.py b/sharc/parameters/parameters_antenna.py index f004b3874..5394b9704 100644 --- a/sharc/parameters/parameters_antenna.py +++ b/sharc/parameters/parameters_antenna.py @@ -2,6 +2,7 @@ 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.antenna.parameters_antenna_s672 import ParametersAntennaS672 from sharc.parameters.antenna.parameters_antenna_with_freq import ParametersAntennaWithFreq from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt @@ -25,10 +26,12 @@ class ParametersAntenna(ParametersBase): "ITU-R S.1855", "ITU-R Reg. RR. Appendice 7 Annex 3", "ARRAY", + "ARRAY2", "ITU-R-S.1528-Taylor", "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", - "MSS Adjacent"] + "MSS Adjacent", + "ITU-R F.1245_fs"] # chosen antenna radiation pattern pattern: typing.Literal["OMNI", @@ -40,10 +43,12 @@ class ParametersAntenna(ParametersBase): "ITU-R S.1855", "ITU-R Reg. RR. Appendice 7 Annex 3", "ARRAY", + "ARRAY2", "ITU-R-S.1528-Taylor", "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", - "MSS Adjacent"] = None + "MSS Adjacent", + "ITU-R F.1245_fs"] = None # antenna gain [dBi] gain: float = None @@ -89,6 +94,14 @@ class ParametersAntenna(ParametersBase): default_factory=ParametersAntennaS1528, ) + itu_r_s_672: ParametersAntennaS672 = field( + default_factory=ParametersAntennaS672, + ) + + itu_r_f_1245_fs: ParametersAntennaWithDiameter = field( + default_factory=ParametersAntennaWithDiameter, + ) + def set_external_parameters(self, **kwargs): """ Set external parameters for all sub-parameters of the antenna. @@ -105,7 +118,8 @@ def set_external_parameters(self, **kwargs): param = getattr(self, attr_name) for k, v in kwargs.items(): - if k in dir(param): + # we only set if not already set + if k in dir(param) and getattr(param, k, None) is None: setattr(param, k, v) if "antenna_gain" in dir(param): @@ -144,7 +158,7 @@ def validate(self, ctx): f"{ctx}.pattern should be set. Is None instead", ) - if self.pattern != "ARRAY" and self.gain is None: + if self.pattern != "ARRAY" and self.pattern != "ARRAY2" and self.gain is None: raise ValueError( f"{ctx}.gain should be set if not using array antenna.", ) @@ -176,7 +190,7 @@ def validate(self, ctx): # 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": + case "ARRAY" | "ARRAY2": # TODO: validate here and make array non imt specific # self.array.validate( # f"{ctx}.array", @@ -188,8 +202,12 @@ def validate(self, ctx): 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 "ITU-R-S.672": + self.itu_r_s_672.validate(f"{ctx}.itu_r_s_672") case "MSS Adjacent": self.mss_adjacent.validate(f"{ctx}.mss_adjacent") + case "ITU-R F.1245_fs": + self.itu_r_f_1245_fs.validate(f"{ctx}.itu_r_f_1245_fs") case _: raise NotImplementedError( "ParametersAntenna.validate does not implement this antenna validation!", ) diff --git a/sharc/parameters/parameters_fss_ss.py b/sharc/parameters/parameters_fss_ss.py index 434a5205c..92d84bfce 100644 --- a/sharc/parameters/parameters_fss_ss.py +++ b/sharc/parameters/parameters_fss_ss.py @@ -4,7 +4,8 @@ from sharc.parameters.parameters_base import ParametersBase from sharc.parameters.parameters_p619 import ParametersP619 -from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 +from sharc.parameters.parameters_antenna import ParametersAntenna +from sharc.parameters.antenna.parameters_antenna_s672 import ParametersAntennaS672 @dataclass @@ -49,16 +50,15 @@ class ParametersFssSs(ParametersBase): # 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) + antenna: ParametersAntenna = field( + default_factory=lambda: ParametersAntenna( + pattern="ITU-R S.672", + itu_r_s_672=ParametersAntennaS672( + antenna_l_s=-20, + antenna_3_dB_bw=0.65 + ) + ) + ) ############################ # Parameters for the P.619 propagation model @@ -89,10 +89,10 @@ def load_parameters_from_file(self, config_file: str): super().load_parameters_from_file(config_file) # Now do the sanity check for some parameters - if self.antenna_pattern not in [ + 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}. \ + invalid value for parameter antenna_pattern - {self.antenna.pattern}. \ Possible values \ are \"ITU-R S.672\", \"ITU-R S.1528\", \"FSS_SS\", \"OMNI\"") @@ -111,10 +111,7 @@ def load_parameters_from_file(self, config_file: str): if str(self.param_p619.mean_clutter_height).lower() not in allowed: raise ValueError("Invalid type of mean_clutter_height. mean_clutter_height must be 'Low', 'Mid', or 'High'") - self.antenna_s1528.set_external_parameters( + self.antenna.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_mss_d2d.py b/sharc/parameters/parameters_mss_d2d.py index 172964dbb..c566269c0 100644 --- a/sharc/parameters/parameters_mss_d2d.py +++ b/sharc/parameters/parameters_mss_d2d.py @@ -4,6 +4,7 @@ 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.parameters_antenna import ParametersAntenna from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 @@ -76,18 +77,12 @@ class ParametersMssD2d(ParametersBase): # 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) + # Parameters for the antenna patterns + antenna: ParametersAntenna = field( + default_factory=lambda: ParametersAntenna( + pattern="ITU-R-S.1528-Taylor", + gain=30.0, + itu_r_s_1528=ParametersAntennaS1528())) sat_is_active_if: ParametersSelectActiveSatellite = field( default_factory=ParametersSelectActiveSatellite) @@ -182,12 +177,9 @@ def propagate_parameters(self): """ Propagate relevant parameters to nested antenna and beam positioning objects. """ - self.antenna_s1528.set_external_parameters( - antenna_pattern=self.antenna_pattern, + self.antenna.set_external_parameters( frequency=self.frequency, bandwidth=self.bandwidth, - antenna_l_s=self.antenna_l_s, - antenna_3_dB_bw=self.antenna_3_dB_bw, ) if self.beam_positioning.service_grid.beam_radius is None: self.beam_positioning.service_grid.beam_radius = self.cell_radius diff --git a/sharc/parameters/parameters_mss_ss.py b/sharc/parameters/parameters_mss_ss.py index 1a2b7d668..50a3d3acf 100644 --- a/sharc/parameters/parameters_mss_ss.py +++ b/sharc/parameters/parameters_mss_ss.py @@ -5,7 +5,7 @@ from sharc.parameters.parameters_base import ParametersBase from sharc.parameters.parameters_p619 import ParametersP619 -from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 +from sharc.parameters.parameters_antenna import ParametersAntenna import numpy as np @@ -66,18 +66,9 @@ class ParametersMssSs(ParametersBase): # 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 the antenna patterns + antenna: ParametersAntenna = field( + default_factory=ParametersAntenna) # paramters for channel model param_p619: ParametersP619 = field(default_factory=ParametersP619) @@ -131,12 +122,10 @@ def load_parameters_from_file(self, config_file: str): f"""ParametersImt: Inavlid Spectral Mask Name { self.spectral_mask}""") - self.antenna_s1528.set_external_parameters( + self.antenna.set_external_parameters( + pattern=self.antenna_pattern, 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 [ diff --git a/sharc/parameters/parameters_single_space_station.py b/sharc/parameters/parameters_single_space_station.py index 267f87dd3..b1bacfbec 100644 --- a/sharc/parameters/parameters_single_space_station.py +++ b/sharc/parameters/parameters_single_space_station.py @@ -57,13 +57,21 @@ class SpaceStationGeometry(ParametersBase): es_long_deg: float = -47.882778 es_lat_deg: float = -15.793889 + # Used if azimuth or elevation are POINTING_AT_LAT_LONG_ALT + # [deg] + pointing_at_lat: typing.Optional[float] = None + # [deg] + pointing_at_long: typing.Optional[float] = None + # [m] + pointing_at_alt: typing.Optional[float] = None + @dataclass class PointingParam(ParametersBase): """ Defines pointing parameters for the space station geometry. """ - __EXISTING_TYPES = ["FIXED", "POINTING_AT_IMT"] - type: typing.Literal["FIXED", "POINTING_AT_IMT"] = None + __EXISTING_TYPES = ["FIXED", "POINTING_AT_IMT", "POINTING_AT_LAT_LONG_ALT"] + type: typing.Literal["FIXED", "POINTING_AT_IMT", "POINTING_AT_LAT_LONG_ALT"] = None fixed: float = None def validate(self, ctx): diff --git a/sharc/post_processor.py b/sharc/post_processor.py index 9a0af4f54..9a8f67431 100644 --- a/sharc/post_processor.py +++ b/sharc/post_processor.py @@ -612,6 +612,7 @@ def aggregate_results( ul_tdd_factor: float, n_bs_sim: int, n_bs_actual: int, + n_aggregate: int | None = None, random_number_gen=np.random.RandomState(31), ): """ @@ -629,10 +630,14 @@ def aggregate_results( 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. + n_aggregate: int | None + The number of random samples to choose. + If None (default), it will be calculated automatically based on input sample sizes. 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." + @@ -642,12 +647,19 @@ def aggregate_results( 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)) + # Set the final n_aggregate value: use the user-provided value if valid, otherwise calculate a default. + if n_aggregate is None: + # If no value was provided, calculate a default based on the input sample sizes. + 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)) + + elif n_aggregate <= 0: + # Validate that the user-provided value is a positive integer. + raise ValueError(f"n_aggregate must be a positive integer, but got {n_aggregate}.") aggregate_samples = np.empty(n_aggregate) diff --git a/sharc/propagation/atmosphere.py b/sharc/propagation/atmosphere.py index 846ce1516..9d5208038 100644 --- a/sharc/propagation/atmosphere.py +++ b/sharc/propagation/atmosphere.py @@ -385,6 +385,9 @@ def get_reference_atmosphere_p835( """ h_km = altitude / 1000 + season = season.lower() + if season not in ['winter', 'summer']: + raise ValueError(f"Invalid season name {season}.") if latitude <= 22: # low latitude diff --git a/sharc/propagation/propagation.py b/sharc/propagation/propagation.py index 18db95e10..50285c2aa 100644 --- a/sharc/propagation/propagation.py +++ b/sharc/propagation/propagation.py @@ -7,8 +7,11 @@ from abc import ABC, abstractmethod import numpy as np +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sharc.propagation.propagation_path import PropagationPath -from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters @@ -22,14 +25,14 @@ def __init__(self, random_number_gen: np.random.RandomState): # Inicates whether this propagation model is for links between earth # and space self.is_earth_space_model = False + self.needs_antenna_gains = False @abstractmethod - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: "PropagationPath", station_a_gains=None, station_b_gains=None, ) -> np.array: diff --git a/sharc/propagation/propagation_abg.py b/sharc/propagation/propagation_abg.py index 53c6fbaab..a2e5ab1fc 100644 --- a/sharc/propagation/propagation_abg.py +++ b/sharc/propagation/propagation_abg.py @@ -8,6 +8,8 @@ import numpy as np from multipledispatch import dispatch +from sharc.propagation.propagation_path import PropagationPath + from sharc.propagation.propagation import Propagation from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters @@ -35,14 +37,11 @@ def __init__( self.building_loss = 20 self.shadowing_sigma_dB = 6.5 - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: "PropagationPath", station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -74,19 +73,22 @@ def get_loss( if wrap_around_enabled: _, distances_3d, _, _ = \ - station_a.get_dist_angles_wrap_around(station_b) + path.sta_a.geom.get_global_dist_angles_wrap_around(path.sta_b.geom) else: - distances_3d = station_a.get_3d_distance_to(station_b) + distances_3d = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) - indoor_stations = station_a.indoor + indoor_stations = path.sta_a.indoor - loss = \ + mskd_indoor = path.sta_a_to_masked(indoor_stations) + mskd_dist3d = path.mtx_to_masked(distances_3d) + mskd_loss = \ self.get_loss( - distances_3d, - frequency * np.ones(distances_3d.shape), - indoor_stations, + mskd_dist3d, + frequency * np.ones(mskd_dist3d.shape), + mskd_indoor, params.imt.shadowing, ) + loss = path.from_masked_mtx(mskd_loss) return loss @@ -124,7 +126,7 @@ def get_loss( shadowing = 0 building_loss = self.building_loss * \ - np.tile(indoor_stations, (distance.shape[1], 1)).transpose() + indoor_stations loss = 10 * self.alpha * np.log10(distance) + self.beta + 10 * \ self.gamma * np.log10(frequency * 1e-3) + shadowing + building_loss diff --git a/sharc/propagation/propagation_building_entry_loss.py b/sharc/propagation/propagation_building_entry_loss.py index 02a78ff27..7e64c4aba 100644 --- a/sharc/propagation/propagation_building_entry_loss.py +++ b/sharc/propagation/propagation_building_entry_loss.py @@ -15,6 +15,18 @@ class PropagationBuildingEntryLoss(Propagation): """ Implements the building entry loss according to ITU-R P.2109-0 (Prediction of Building Entry Loss) """ + def get_path_loss( + self, + params, + frequency: float, + path, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() def get_loss( self, frequency_MHz, elevation, prob="RANDOM", diff --git a/sharc/propagation/propagation_clear_air_452.py b/sharc/propagation/propagation_clear_air_452.py index 26095ca24..3657fa01a 100644 --- a/sharc/propagation/propagation_clear_air_452.py +++ b/sharc/propagation/propagation_clear_air_452.py @@ -5,7 +5,7 @@ from multipledispatch import dispatch from sharc.propagation.propagation import Propagation -from sharc.station_manager import StationManager +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.parameters import Parameters from sharc.parameters.parameters_p452 import ParametersP452 from sharc.propagation.clear_air_452_aux import p676_ga @@ -24,6 +24,7 @@ def __init__( model_params: ParametersP452): """Initialize PropagationClearAir with a random number generator and model parameters.""" super().__init__(random_number_gen) + self.needs_antenna_gains = True self.clutter = PropagationClutterLoss(random_number_gen) self.building_entry = PropagationBuildingEntryLoss( @@ -461,20 +462,6 @@ def pl_los(d, f, p, b0, w, T, press, dlt, dlr): # pylint: disable=function-redefined # pylint: disable=arguments-renamed - def __init__( - self, - random_number_gen: np.random.RandomState, - model_params: ParametersP452): - """Initialize PropagationClearAir with a random number generator and model parameters.""" - super().__init__(random_number_gen) - - self.clutter = PropagationClutterLoss(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): """Apply clutter loss correction according to ITU-R P.452-16.""" @@ -1508,16 +1495,13 @@ def dl_p(d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN): return Ldp, Ld50 - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, - station_a_gains=None, - station_b_gains=None, + path: PropagationPath, + station_a_gains, + station_b_gains, ) -> 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 @@ -1543,31 +1527,36 @@ def get_loss( 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 + masked_distance = path.mtx_to_masked(path.sta_a.geom.get_3d_distance_to( + path.sta_b.geom, + )) * (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), + np.ones(masked_distance.shape) * (1e-3) # P.452 expects GHz + # FIXME: Why always station_b? + masked_indoor_stations = path.sta_b_to_masked(path.sta_b.indoor) + # FIXME: Why station b -> station a? + masked_elevation = path.mtx_to_masked( + path.sta_b.geom.get_local_elevation(path.sta_a.geom).T ) - elevation = station_b.get_elevation(station_a) + if params.imt.interfered_with: - tx_gain = station_a_gains - rx_gain = station_b_gains + tx_gain = path.mtx_to_masked(station_a_gains) + rx_gain = path.mtx_to_masked(np.swapaxes(station_b_gains, 0, 1)) else: - tx_gain = station_b_gains - rx_gain = station_a_gains + tx_gain = path.mtx_to_masked(np.swapaxes(station_b_gains, 0, 1)) + rx_gain = path.mtx_to_masked(station_a_gains) - return self.get_loss( - distance, + masked_loss = self.get_loss( + masked_distance, frequency_array, - indoor_stations, - elevation, + masked_indoor_stations, + masked_elevation, tx_gain, rx_gain, ) + return path.from_masked_mtx(masked_loss) + # pylint: disable=arguments-differ @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray) @@ -1629,7 +1618,7 @@ def get_loss( num_dists = distance.size d = np.empty([num_dists, profile_length]) for ii in range(num_dists): - d[ii, :] = np.linspace(0, distance[0][ii], profile_length) + d[ii, :] = np.linspace(0, np.ravel(distance)[ii], profile_length) h = np.zeros(d.shape) @@ -1669,7 +1658,7 @@ def get_loss( # 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) - Lb = np.empty([1, num_dists]) + Lb = np.empty([num_dists]) # Effective Earth curvature Ce(km ^ -1) Ce = 1 / ae @@ -1813,9 +1802,9 @@ def get_loss( ) + Aht + Ahr if (self.model_params.polarization).lower() == "horizontal": - Lb[0, ii] = Lb_pol[0] + Lb[ii] = Lb_pol[0] elif (self.model_params.polarization).lower() == "vertical": - Lb[0, ii] = Lb_pol[1] + Lb[ii] = Lb_pol[1] else: error_message = "invalid polarization" raise ValueError(error_message) @@ -1824,7 +1813,7 @@ def get_loss( clutter_loss = self.clutter.get_loss( frequency=frequency * 1000, distance=distance * 1000, - station_type=StationType.FSS_ES, + clutter_scenario="terrestrial", # Always terrestrial for P.452 clutter_type=self.model_params.clutter_type ) else: diff --git a/sharc/propagation/propagation_clutter_loss.py b/sharc/propagation/propagation_clutter_loss.py index 43c9b4631..47a10c9f8 100644 --- a/sharc/propagation/propagation_clutter_loss.py +++ b/sharc/propagation/propagation_clutter_loss.py @@ -50,6 +50,19 @@ class PropagationClutterLoss(Propagation): estimate clutter loss. """ + def get_path_loss( + self, + params, + frequency: float, + path, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() + def get_loss(self, *args, **kwargs) -> np.array: """ Calculates clutter loss according to Recommendation P.2108-0 @@ -61,19 +74,24 @@ def get_loss(self, *args, **kwargs) -> np.array: elevation (np.array) : elevation angles [deg] loc_percentage (np.array) : Percentage locations range [0, 1[ "RANDOM" for random percentage (Default = RANDOM) - station_type (StationType) : if type is IMT or FSS_ES, assume terrestrial - terminal within the clutter (ref § 3.2); otherwise, assume that - one terminal is within the clutter and the other is a satellite, - aeroplane or other platform above the surface of the Earth. + clutter_scenario (str) : clutter scenario type: + "spatial" for Earth-space and aeronautical paths + "terrestrial" for terrestrial paths Returns ------- array with clutter loss values with dimensions of distance """ + # Check for required parameters + required_params = ["frequency", "clutter_scenario", "distance"] + missing_params = [p for p in required_params if p not in kwargs] + if missing_params: + raise ValueError(f"Missing required parameter(s): {', '.join(missing_params)}") + f = kwargs["frequency"] loc_per = kwargs.pop("loc_percentage", "RANDOM") - type = kwargs["station_type"] + clutter_scenario = kwargs["clutter_scenario"] d = kwargs["distance"] if f.size == 1: @@ -86,7 +104,7 @@ def get_loss(self, *args, **kwargs) -> np.array: 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: + if clutter_scenario == "terrestrial": clutter_type = kwargs["clutter_type"] if clutter_type == 'one_end': loss = self.get_terrestrial_clutter_loss(f, d, p1, True) @@ -94,20 +112,22 @@ def get_loss(self, *args, **kwargs) -> np.array: loss = self.get_terrestrial_clutter_loss(f, d, p1, True) + self.get_terrestrial_clutter_loss(f, d, p2, False) else: raise ValueError("Invalid type of Clutter-type. It can be either 'one_end' or 'both-ends'") - else: + elif clutter_scenario == "spatial": theta = kwargs["elevation"] 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) + loss = self.get_spatial_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 = self.random_number_gen.choice(mult_1.size, size=num_ones, replace=False) mult_1.flat[indices] = 1 loss *= mult_1 + else: + raise ValueError("Invalid type of Clutter-scenario. It can be either 'terrestrial' or 'spatial'") return loss - def get_spacial_clutter_loss( + def get_spatial_clutter_loss( self, frequency: float, elevation_angle: float, loc_percentage, @@ -323,7 +343,7 @@ def get_terrestrial_clutter_loss( clutter_loss = np.empty([len(elevation_angle), len(loc_percentage)]) for i in range(len(elevation_angle)): - clutter_loss[i, :] = cl.get_spacial_clutter_loss( + clutter_loss[i, :] = cl.get_spatial_clutter_loss( frequency, elevation_angle[i], loc_percentage, diff --git a/sharc/propagation/propagation_free_space.py b/sharc/propagation/propagation_free_space.py index 4ae886a5d..6ee536eb4 100644 --- a/sharc/propagation/propagation_free_space.py +++ b/sharc/propagation/propagation_free_space.py @@ -6,9 +6,12 @@ """ import numpy as np from multipledispatch import dispatch +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sharc.propagation.propagation_path import PropagationPath from sharc.propagation.propagation import Propagation -from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters @@ -19,14 +22,11 @@ class PropagationFreeSpace(Propagation): Frequency in MHz and distance are in meters """ - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: "PropagationPath", station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -47,9 +47,12 @@ def get_loss( 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) + distance_3d = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) + + mskd_dist3d = path.mtx_to_masked(distance_3d) + mskd_loss = self.get_free_space_loss( + frequency=frequency, distance=mskd_dist3d) + loss = path.from_masked_mtx(mskd_loss) return loss diff --git a/sharc/propagation/propagation_hdfss.py b/sharc/propagation/propagation_hdfss.py index a78451775..b8d3fed34 100644 --- a/sharc/propagation/propagation_hdfss.py +++ b/sharc/propagation/propagation_hdfss.py @@ -14,6 +14,7 @@ from sharc.parameters.parameters import Parameters from sharc.propagation.propagation_hdfss_roof_top import PropagationHDFSSRoofTop from sharc.propagation.propagation_hdfss_building_side import PropagationHDFSSBuildingSide +from sharc.propagation.propagation_path import PropagationPath class PropagationHDFSS(Propagation): @@ -38,12 +39,11 @@ def __init__( ) sys.exit(1) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: PropagationPath, station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -71,20 +71,34 @@ def get_loss( 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) # P.452 expects Kms - frequency_array = frequency * \ - np.ones(distance.shape) # P.452 expects GHz - elevation = station_b.get_elevation(station_a) + distance = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) # P.452 expects Kms + distance = path.mtx_to_masked(distance) - return self.propagation.get_loss( + elevation = path.sta_b.geom.get_local_elevation(path.sta_a.geom) + elevation = path.mtx_to_masked(elevation.T) + + frequency_array = frequency * np.ones(distance.shape) # P.452 expects GHz + + if path.sta_a.geom.uses_local_coords or path.sta_b.geom.uses_local_coords: + raise NotImplementedError( + "HDFSS currently assumes stations z == height. " + "If stations has local coords != global coords, this probably isn't true" + ) + + loss, build_loss, diff_loss = self.propagation.get_loss( distance_3D=distance, elevation=elevation, - imt_sta_type=station_b.station_type, + imt_sta_type=path.sta_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, + imt_x=path.sta_b_to_masked(path.sta_b.geom.x_global), + imt_y=path.sta_b_to_masked(path.sta_b.geom.y_global), + imt_z=path.sta_b_to_masked(path.sta_b.geom.z_global), + es_x=path.sta_a_to_masked(path.sta_a.geom.x_global), + es_y=path.sta_a_to_masked(path.sta_a.geom.y_global), + es_z=path.sta_a_to_masked(path.sta_a.geom.z_global), + ) + return ( + path.from_masked_mtx(loss), + path.from_masked_mtx(build_loss), + path.from_masked_mtx(diff_loss) ) diff --git a/sharc/propagation/propagation_hdfss_building_side.py b/sharc/propagation/propagation_hdfss_building_side.py index 0c9909d09..fe732b541 100644 --- a/sharc/propagation/propagation_hdfss_building_side.py +++ b/sharc/propagation/propagation_hdfss_building_side.py @@ -49,6 +49,12 @@ def __init__( self.random_number_gen, ) + def get_path_loss(self, *args, **kwargs): + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() + def get_loss(self, *args, **kwargs) -> np.array: """ diff --git a/sharc/propagation/propagation_hdfss_roof_top.py b/sharc/propagation/propagation_hdfss_roof_top.py index 5d62154c1..d9c94d8bc 100644 --- a/sharc/propagation/propagation_hdfss_roof_top.py +++ b/sharc/propagation/propagation_hdfss_roof_top.py @@ -58,6 +58,12 @@ def __init__( self.SPEED_OF_LIGHT = SPEED_OF_LIGHT + def get_path_loss(self, *args, **kwargs): + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() + def get_loss(self, *args, **kwargs) -> np.array: """ Calculates path loss for given distances and frequencies @@ -123,51 +129,46 @@ def get_loss(self, *args, **kwargs) -> np.array: # Define indexes same_build_idx = np.where(same_build)[0] - fspl_idx = np.where(fspl_bool)[1] - fspl_to_los_idx = np.where(fspl_to_los_bool)[1] - 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] + + fspl_idx = np.where(fspl_bool)[0] + fspl_to_los_idx = np.where(fspl_to_los_bool)[0] + los_idx = np.where(los_bool)[0] + los_to_nlos_idx = np.where(los_to_nlos_bool)[0] + nlos_idx = np.where(nlos_bool)[0] # 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( + loss[same_build_idx] += self.get_same_build_loss( imt_z[same_build_idx], - es_z, + es_z[same_build_idx], ) - loss[:, fspl_idx] += self.propagation_fspl.get_free_space_loss( - distance=d[:, fspl_idx], frequency=f[:, fspl_idx], + 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], + 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, - ], + 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], + 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, - ], + loss[nlos_idx] += self.propagation_p1411.get_loss( + distance_3D=d[nlos_idx], + frequency=f[nlos_idx], los=False, shadow=self.param.shadow_enabled, ) @@ -175,10 +176,10 @@ def get_loss(self, *args, **kwargs) -> np.array: # 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( + build_loss[not_same_build] = self.get_building_loss( imt_sta_type, - f[:, not_same_build], - elevation[:, not_same_build], + f[not_same_build], + elevation[not_same_build], ) else: build_loss = 0.0 @@ -191,11 +192,11 @@ def get_loss(self, *args, **kwargs) -> np.array: es_x, es_y, es_z, ) diff_loss = np.zeros_like(loss) - diff_loss[0, not_same_build] = self.get_diffraction_loss( + diff_loss[not_same_build] = self.get_diffraction_loss( h[not_same_build], d1[not_same_build], d2[not_same_build], - f[:, not_same_build], + f[not_same_build], ) # Compute final loss @@ -339,10 +340,10 @@ def is_same_building(self, imt_x, imt_y, es_x, es_y): Boolean array indicating if each IMT is in the same building as the earth station. """ - 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]) + building_x_range = es_x[np.newaxis, :] + (1 + self.b_tol) * \ + np.array([-self.b_w / 2, +self.b_w / 2])[:, np.newaxis] + building_y_range = es_y[np.newaxis, :] + (1 + self.b_tol) * \ + np.array([-self.b_d / 2, +self.b_d / 2])[:, np.newaxis] is_in_x = np.logical_and( imt_x >= building_x_range[0], imt_x <= building_x_range[1], diff --git a/sharc/propagation/propagation_indoor.py b/sharc/propagation/propagation_indoor.py index f80be46f6..3067902f6 100644 --- a/sharc/propagation/propagation_indoor.py +++ b/sharc/propagation/propagation_indoor.py @@ -15,6 +15,7 @@ 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 +from sharc.propagation.propagation_path import PropagationPath class PropagationIndoor(Propagation): @@ -55,14 +56,11 @@ def __init__( self.bs_per_building = 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( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: "PropagationPath", station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -100,17 +98,18 @@ def get_loss( if wrap_around_enabled: bs_to_ue_dist_2d, bs_to_ue_dist_3d, _, _ = \ - station_b.get_dist_angles_wrap_around(station_a) + path.sta_b.geom.get_global_dist_angles_wrap_around(path.sta_a.geom) 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) + bs_to_ue_dist_2d = path.sta_b.geom.get_local_distance_to(path.sta_a.geom) + bs_to_ue_dist_3d = path.sta_b.geom.get_3d_distance_to(path.sta_a.geom) frequency_array = frequency * np.ones(bs_to_ue_dist_2d.shape) indoor_stations = np.tile( - station_a.indoor, (station_b.num_stations, 1), + path.sta_a.indoor, (path.sta_b.num_stations, 1), ) - elevation = np.transpose(station_a.get_elevation(station_b)) - + elevation = np.transpose(path.sta_a.geom.get_local_elevation(path.sta_b.geom)) + # NOTE: we're not masking only selected paths since indoor implementation depends + # on being able to use UE axis separated from BS axis return self.get_loss( bs_to_ue_dist_3d, bs_to_ue_dist_2d, @@ -118,7 +117,7 @@ def get_loss( elevation, indoor_stations, params.imt.shadowing, - ) + ).T # pylint: disable=function-redefined # pylint: disable=arguments-renamed diff --git a/sharc/propagation/propagation_inh_office.py b/sharc/propagation/propagation_inh_office.py index 16c56761b..1f270dab6 100644 --- a/sharc/propagation/propagation_inh_office.py +++ b/sharc/propagation/propagation_inh_office.py @@ -18,6 +18,12 @@ class PropagationInhOffice(Propagation): according to 3GPP TR 38.900 v14.2.0. """ + def get_path_loss(self, *args, **kwargs): + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() + def get_loss(self, *args, **kwargs) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing diff --git a/sharc/propagation/propagation_p1411.py b/sharc/propagation/propagation_p1411.py index 30b8e1b53..5ddea5c52 100644 --- a/sharc/propagation/propagation_p1411.py +++ b/sharc/propagation/propagation_p1411.py @@ -44,6 +44,12 @@ def __init__( self.nlos_gamma = 2.36 self.nlos_sigma = 7.60 + def get_path_loss(self, *args, **kwargs): + """This class isn't supposed to be directly called in the simulation + loop, so this methods doesn't need to be implemented + """ + raise NotImplementedError() + def get_loss(self, *args, **kwargs) -> np.array: """Calculate the path loss using the ITU-R P.1411 model. diff --git a/sharc/propagation/propagation_p619.py b/sharc/propagation/propagation_p619.py index a5c4907da..1a7e03814 100644 --- a/sharc/propagation/propagation_p619.py +++ b/sharc/propagation/propagation_p619.py @@ -12,7 +12,7 @@ import numpy as np from multipledispatch import dispatch from warnings import warn -from sharc.station_manager import StationManager +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.parameters import Parameters from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_free_space import PropagationFreeSpace @@ -59,6 +59,7 @@ def __init__( """ super().__init__(random_number_gen) + self.needs_antenna_gains = True self.is_earth_space_model = True self.clutter = PropagationClutterLoss(self.random_number_gen) @@ -282,45 +283,41 @@ def _get_beam_spreading_att( def apparent_elevation_angle( cls, elevation_deg: np.array, - space_station_alt_m: float) -> np.array: + earth_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 + free-earth elevation angle + earth_station_alt_m : float + earth-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 + tau_fs1 = 1.728 + 0.5411 * elevation_deg + 0.03723 * elevation_deg**2 + tau_fs2 = 0.1815 + 0.06272 * elevation_deg + 0.01380 * elevation_deg**2 + tau_fs3 = 0.01727 + 0.008288 * elevation_deg + Ht_km = earth_station_alt_m / 1e3 # 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_fs1 + Ht_km * tau_fs2 + + Ht_km**2 * tau_fs3 ) - tau_fs = tau_fs_deg / 180. * np.pi - return np.degrees(elev_angles_rad + tau_fs) + return elevation_deg + tau_fs_deg - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, - station_a_gains=None, - station_b_gains=None, + path: PropagationPath, + station_a_gains, + station_b_gains, ) -> np.array: """Wrapper for the get_loss function that satisfies the ABS class interface @@ -341,36 +338,49 @@ def get_loss( 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), - ) + station_a = path.sta_a + station_b = path.sta_b + + distance = station_a.geom.get_3d_distance_to(station_b.geom) + masked_distance = path.mtx_to_masked(distance) + masked_frequency = frequency * np.ones_like(masked_distance) # Elevation angles seen from the station on Earth. - elevation_angles = {} + masked_elevation_angles = {} if station_a.is_space_station: - earth_station_height = station_b.height - 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, + if station_b.geom.uses_local_coords: + raise NotImplementedError( + "P619 currently assumes earth station z == height. " + "If ES has local coords != global coords, this probably isn't true" + ) + masked_indoor_stations = path.sta_b_to_masked(station_b.indoor) + masked_earth_station_height = path.sta_b_to_masked(station_b.geom.z_global) + masked_elevation_angles["free_space"] = station_b.geom.get_local_elevation(station_a.geom) + masked_earth_station_antenna_gain = path.mtx_to_masked(np.swapaxes(station_b_gains, 0, 1)) + masked_elevation_angles["apparent"] = self.apparent_elevation_angle( + masked_elevation_angles["free_space"], + self.earth_station_alt_m, ) # 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"]) + masked_elevation_angles["free_space"] = path.mtx_to_masked(np.transpose( + masked_elevation_angles["free_space"])) + masked_elevation_angles["apparent"] = path.mtx_to_masked(np.transpose( + masked_elevation_angles["apparent"])) elif station_b.is_space_station: - earth_station_height = station_a.height - 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, + if station_a.geom.uses_local_coords: + raise NotImplementedError( + "P619 currently assumes earth station z == height. " + "If ES has local coords != global coords, this probably isn't true" + ) + masked_indoor_stations = path.sta_a_to_masked(station_a.indoor) + masked_earth_station_height = path.sta_a_to_masked(station_a.geom.z_global) + masked_elevation_angles["free_space"] = path.mtx_to_masked( + station_a.geom.get_local_elevation(station_b.geom) + ) + masked_earth_station_antenna_gain = path.mtx_to_masked(station_a_gains) + masked_elevation_angles["apparent"] = self.apparent_elevation_angle( + masked_elevation_angles["free_space"], + self.earth_station_alt_m, ) else: raise ValueError( @@ -404,17 +414,19 @@ def get_loss( is_earth_to_space_link = False if imt_station.is_space_station else True is_single_entry_interf = False - loss = self.get_loss( - distance, - frequency, - indoor_stations, - elevation_angles, + masked_loss = self.get_loss( + masked_distance, + masked_frequency, + masked_indoor_stations, + masked_elevation_angles, is_earth_to_space_link, - earth_station_antenna_gain, + masked_earth_station_antenna_gain, is_single_entry_interf, - earth_station_height, + masked_earth_station_height, ) + loss = path.from_masked_mtx(masked_loss) + return loss @dispatch(np.ndarray, np.ndarray, np.ndarray, dict, bool, np.ndarray, bool, np.ndarray) @@ -460,6 +472,7 @@ def get_loss( atmospheric_gasses_loss = self._get_atmospheric_gasses_loss( frequency_MHz=freq_set, + # FIXME: mean of apparent elevation???? apparent_elevation=np.mean(elevation["apparent"]), ) beam_spreading_attenuation = self._get_beam_spreading_att( @@ -470,14 +483,25 @@ def get_loss( diffraction_loss = 0 if single_entry: + # if earth station is IMT BS with ue_k beams + elev = elevation["free_space"] + if earth_station_antenna_gain.ndim == 2: + elev = np.repeat( + elevation["free_space"][..., np.newaxis], + earth_station_antenna_gain.shape[1], + -1 + ) tropo_scintillation_loss = self.scintillation.get_tropospheric_attenuation( - elevation=elevation["free_space"], + elevation=elev, 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, ) + if tropo_scintillation_loss.ndim > 1: + # TODO: better fix for this + tropo_scintillation_loss = np.min(tropo_scintillation_loss, -1) loss = free_space_loss + self.depolarization_loss + atmospheric_gasses_loss + \ beam_spreading_attenuation + diffraction_loss + tropo_scintillation_loss @@ -487,7 +511,7 @@ def get_loss( frequency=frequency, distance=distance, elevation=elevation["free_space"], - station_type=StationType.FSS_SS, + clutter_scenario="spatial", earth_station_height=earth_station_height, mean_clutter_height=self.mean_clutter_height, below_rooftop=self.below_rooftop, diff --git a/sharc/propagation/propagation_path.py b/sharc/propagation/propagation_path.py new file mode 100644 index 000000000..8504b3a77 --- /dev/null +++ b/sharc/propagation/propagation_path.py @@ -0,0 +1,404 @@ +from sharc.station_manager import StationManager +import typing +import numpy as np + +from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation import Propagation + + +class PropagationPath(): + """This class defines which paths between stations actually + do exist and shall be calculated. The actual paths are calculated + right as get_path_loss is called, so there is no risk of being + out of date. + Paths are marked as disabled by masking functions + """ + # either + # - (sta_a == IMT UE) and (sta_b == IMT BS) + # OR + # - (sta_a == System) and (sta_b == IMT BS) + # OR + # - (sta_a == System) and (sta_b == IMT UE) + sta_a: StationManager + sta_b: StationManager + + # mask to know what paths are valid paths + _mask: np.ndarray # shape (sys_a.num_station, sys_b.num_station) + + # mask to remove duplicated calculations + _deduplicated_mask: np.ndarray # shape (sys_a.num_station, sys_b.num_station) + + # how to remove useless calculations + _mask_strategies: list[typing.Callable] = [] + + _orig_shape: tuple[int, int] + + _propagation: Propagation + + def __init__( + self, + sta_a: StationManager, + sta_b: StationManager, + mask_strategies: list[typing.Callable] = [] + ): + """Initializes PropagationPath between station_a and station_b + considering masking functions passed. By default stations at the same place + may or may not be masked, since some propagation models depend on more than + just station position. + """ + self.sta_a = sta_a + self.sta_b = sta_b + self._orig_shape = (self.sta_a.num_stations, self.sta_b.num_stations) + self._mask_strategies = mask_strategies + + def get_path_loss( + self, + propagation: Propagation, + parameters: Parameters, + frequency: float, + *, + sta_a_gains=None, + sta_b_gains=None, + ): + """Calculates mask depending on propagation, then calls the propagation + get_path_loss methods that may or may not consider the given paths. + """ + if propagation.needs_antenna_gains: + # NOTE: currently implemented propagation models that require gain + # DIFFERENTIATE between stations at the same position + # so we can't deduplicate solely based on (position, indoor, ...) + self.calc_mask(deduplicate=False) + else: + # NOTE: making sure the propagation has set needs_antenna_gains + # if it needs the gains + sta_a_gains = None + sta_b_gains = None + # NOTE: only propagation models that require gain + # differentiate between stations at the same position + self.calc_mask(deduplicate=True) + + return propagation.get_path_loss( + parameters, + frequency, + self, + sta_a_gains, + sta_b_gains, + ) + + @property + def mask(self): + """Getter for _mask property""" + return self._mask + + def calc_mask(self, *, deduplicate: bool): + """Calculates and updates current mask and paths + based on masking functions in instance. + """ + mask = np.ones(self._orig_shape, dtype=bool) + for mask_fn in self._mask_strategies: + mask = mask_fn(self.sta_a, self.sta_b, mask) + + if deduplicate: + self._set_mask_and_deduplicate(mask) + else: + self._set_mask(mask) + self._set_deduplication_info( + mask, + np.arange(len(self._paths_from_to)) + ) + + def mtx_to_masked(self, mtx: np.ndarray): + """Receives matrix occurring from operations of sta_a to sta_b + and returns 1d vector filtering only active paths + """ + assert mtx.shape[:2] == self._orig_shape + return mtx[self._deduplicated_mask] + + def sta_a_to_masked(self, vec: np.ndarray): + """Receives vector occurring from operations of sta_a + and returns elements for all active paths + """ + assert vec.shape[0] == self._orig_shape[0] + return vec[self._deduped_paths_from_to[:, 0]] + + def sta_b_to_masked(self, vec: np.ndarray): + """Receives vector occurring from operations of sta_b + and returns elements for all active paths + """ + assert vec.shape[0] == self._orig_shape[1] + return vec[self._deduped_paths_from_to[:, 1]] + + def from_masked_mtx(self, vec: np.ndarray): + """Receives 1d vector filtering that only contains active paths + and returns original matrix occurring from operations of sta_a to sta_b + """ + assert vec.shape == (self._n_deduped_paths,) + + mtx = np.full(self._orig_shape, np.nan) + expanded_vec = vec[self._deduped_paths_representative_node] + mtx[self.mask] = expanded_vec + + return mtx + + def _set_mask_and_deduplicate(self, mask): + self._mask = mask + self._paths_from_to = np.stack(np.where(mask), axis=-1) + self._n_paths = len(self._paths_from_to) + + dedupe_mask, representative_nodes = self._deduplicate( + self.sta_a, self.sta_b, self._paths_from_to + ) + self._set_deduplication_info(dedupe_mask, representative_nodes) + + def _set_mask(self, mask): + self._mask = mask + self._paths_from_to = np.stack(np.where(mask), axis=-1) + self._n_paths = len(self._paths_from_to) + + def _set_deduplication_info(self, deduplicated_mask, representative_nodes): + self._deduplicated_mask = deduplicated_mask + self._deduped_paths_from_to = np.stack(np.where(deduplicated_mask), axis=-1) + self._deduped_paths_representative_node = representative_nodes + self._n_deduped_paths = np.sum(deduplicated_mask) + + def _deduplicate( + self, sta_a, sta_b, paths_from_to, + ): + """Maps duplicated stations and new mask. + """ + duplicated_sta_a = self._get_representative_nodes(sta_a) + duplicated_sta_b = self._get_representative_nodes(sta_b) + + deduped_paths_from_to = np.copy(paths_from_to) + + deduped_paths_from_to[:, 0] = duplicated_sta_a[ + paths_from_to[:, 0] + ] + deduped_paths_from_to[:, 1] = duplicated_sta_b[ + paths_from_to[:, 1] + ] + + # since deduped_paths_from_to is already ordered + # np.unique's unstable sort doesn't eff it up + uniq_deduped_paths_from_to, adjacency_vec = np.unique( + deduped_paths_from_to, + axis=0, + return_inverse=True + ) + + deduplicated_mask = np.zeros((sta_a.num_stations, sta_b.num_stations), dtype=bool) + deduplicated_mask[ + uniq_deduped_paths_from_to[:, 0], uniq_deduped_paths_from_to[:, 1] + ] = True + + return deduplicated_mask, adjacency_vec + + @staticmethod + def _get_representative_nodes(sta: StationManager): + # tuples for what needs to be considered for differentiating + # stations for deduplication + representative_tuples = np.stack([ + sta.geom.x_global, + sta.geom.y_global, + sta.geom.z_global, + sta.indoor, + ], axis=-1) + + # like an adjacency matrix, but for tree-like struct + # e.g. graph where each node points to itself or its representative + adjacency_vec = np.zeros((len(representative_tuples),)) + repr_nodes = {} + + # NOTE: tried using numpy but np.unique unstable sorts + # and makes the code really ugly + for i in range(len(representative_tuples)): + tup = tuple(representative_tuples[i]) + if tup not in repr_nodes: + repr_nodes[tup] = i + adjacency_vec[i] = repr_nodes[tup] + + return adjacency_vec + + @staticmethod + def create_default( + sta_a, + sta_b, + ) -> "PropagationPath": + """Creates a PropagationPath between stations considering + which ones should exist. + Check default behavior to understand what paths "should exist" + by default + """ + mask_fns = [ + path_mask_inactive_stations, + ] + + if sta_a.is_space_station or sta_b.is_space_station: + # care for through earth interference + mask_fns.append( + create_path_mask_low_elevation_sat_from_es(0.0) + ) + + # if one of the stations considers a maximum earth path distance + # for interference, we remove those paths + max_interf_dist = np.inf + if sta_a.max_earth_sta_interf_distance is not None: + max_interf_dist = min( + max_interf_dist, + sta_a.max_earth_sta_interf_distance, + ) + if sta_b.max_earth_sta_interf_distance is not None: + max_interf_dist = min( + max_interf_dist, + sta_b.max_earth_sta_interf_distance, + ) + + if max_interf_dist < np.inf: + mask_fns.append( + create_path_mask_distant_earth_stations(max_interf_dist), + ) + + return PropagationPath(sta_a, sta_b, mask_fns) + + +def create_path_mask_distant_earth_stations(distance_m: float): + """Receives a distance in meters and returns a function + deactivate paths between distant earth stations + """ + def path_mask_distant_earth_stations( + sta_a: StationManager, + sta_b: StationManager, + acc_mask: np.ndarray[np.ndarray[bool]] + ): + """Marks paths with distance between earth stations + greater than distance_m as disabled + """ + if sta_a.is_space_station or sta_b.is_space_station: + return acc_mask + + dists = sta_a.geom.get_3d_distance_to(sta_b.geom) + acc_mask[dists > distance_m] = False + + return acc_mask + + return path_mask_distant_earth_stations + + +def create_path_mask_low_elevation_sat_from_es(min_elev_deg: float = 0.0): + """Receives a distance in meters and returns a function + deactivate paths between distant earth stations + """ + def path_mask_low_elevation_sat_from_es( + sta_a: StationManager, + sta_b: StationManager, + acc_mask: np.ndarray[np.ndarray[bool]] + ): + """Marks paths with elevation between earth station and space station + less than min_elev_deg as disabled + """ + es: StationManager = None + ss: StationManager = None + if sta_a.is_space_station and not sta_b.is_space_station: + transpose = True + es = sta_b + ss = sta_a + elif not sta_a.is_space_station and sta_b.is_space_station: + transpose = False + es = sta_a + ss = sta_b + else: + return acc_mask + + elevs = es.geom.get_local_elevation(ss.geom) + if transpose: + elevs = elevs.T + acc_mask[elevs < min_elev_deg] = False + + return acc_mask + + return path_mask_low_elevation_sat_from_es + + +def path_mask_inactive_stations( + sta_a: StationManager, + sta_b: StationManager, + acc_mask: np.ndarray[np.ndarray[bool]] +): + """Marks paths with inactive stations as disabled + """ + acc_mask[~sta_a.active, :] = False + acc_mask[:, ~sta_b.active] = False + + return acc_mask + + +if __name__ == "__main__": + bs = StationManager(2) + ue = StationManager(6) + bs.geom.set_global_coords( + np.repeat(0., 2), + np.repeat(0., 2), + np.repeat(1.5, 2), + ) + + ue.geom.set_global_coords( + np.linspace(10., 60., 6), + np.linspace(10., 60., 6), + np.repeat(1.5, 6), + ) + path = PropagationPath(bs, ue) + path._set_mask_and_deduplicate( + np.array([ + [ + True, True, True, False, False, False + ], + [ + False, False, False, True, True, True + ], + ]) + ) + + # print("path.deduplicated_mask", path.deduplicated_mask) + # print("path.mask", path.mask) + print("path._paths_from_to", path._paths_from_to) + print("path.deduplicated_mask", path._deduplicated_mask) + dist2d = bs.geom.get_local_distance_to(ue.geom) + print("dist2d", dist2d) + masked_dist2d = path.to_masked(dist2d) + print("masked_dist2d", masked_dist2d) + unmasked_dist2d = path.from_masked(masked_dist2d) + print("unmasked_dist2d", unmasked_dist2d) + + print() + print() + print("#####################3") + print() + + # bs.active[0] = False + ue.active[2] = False + path = PropagationPath.create_default(bs, ue) + path.calc_mask(deduplicate=True) + print("path._paths_from_to", path._paths_from_to) + print("path.deduplicated_mask", path._deduplicated_mask) + dist2d = path.sta_a.geom.get_local_distance_to(path.sta_b.geom) + print("dist2d", dist2d) + masked_dist2d = path.to_masked(dist2d) + print("masked_dist2d", masked_dist2d) + unmasked_dist2d = path.from_masked(masked_dist2d) + print("unmasked_dist2d", unmasked_dist2d) + + print() + print() + print("#####################3") + print() + + path = PropagationPath.create_default(bs, ue) + path.calc_mask(deduplicate=False) + print("path._paths_from_to", path._paths_from_to) + print("path.deduplicated_mask", path._deduplicated_mask) + dist2d = path.sta_a.geom.get_local_distance_to(path.sta_b.geom) + print("dist2d", dist2d) + masked_dist2d = path.to_masked(dist2d) + print("masked_dist2d", masked_dist2d) + unmasked_dist2d = path.from_masked(masked_dist2d) + print("unmasked_dist2d", unmasked_dist2d) diff --git a/sharc/propagation/propagation_sat_simple.py b/sharc/propagation/propagation_sat_simple.py index e83712914..d90cb4b82 100644 --- a/sharc/propagation/propagation_sat_simple.py +++ b/sharc/propagation/propagation_sat_simple.py @@ -15,6 +15,7 @@ from sharc.support.enumerations import StationType from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation_path import PropagationPath class PropagationSatSimple(Propagation): @@ -37,14 +38,11 @@ def __init__( ) self.atmospheric_loss = 0.75 - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: PropagationPath, station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -66,38 +64,63 @@ def get_loss( 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), - ) + station_a = path.sta_a + station_b = path.sta_b + + distance = station_a.geom.get_3d_distance_to(station_b.geom) + masked_distance = path.mtx_to_masked(distance) + masked_frequency = frequency * np.ones_like(masked_distance) # Elevation angles seen from the station on Earth. - elevation_angles = {} + raise NotImplementedError( + "FIXME: apparent_elevation_angle should receive earth station altitude..." + ) + masked_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, ) + if station_b.geom.uses_local_coords: + raise NotImplementedError( + "P619 currently assumes earth station z == height. " + "If ES has local coords != global coords, this probably isn't true" + ) + masked_indoor_stations = path.sta_b_to_masked(station_b.indoor) + masked_elevation_angles["free_space"] = station_b.geom.get_local_elevation(station_a.geom) + masked_elevation_angles["apparent"] = PropagationP619.apparent_elevation_angle( + masked_elevation_angles["free_space"], + # FIXME + # self.earth_station_alt_m, + ) # 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"]) + masked_elevation_angles["free_space"] = path.mtx_to_masked(np.transpose( + masked_elevation_angles["free_space"])) + masked_elevation_angles["apparent"] = path.mtx_to_masked(np.transpose( + masked_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, ) + if station_a.geom.uses_local_coords: + raise NotImplementedError( + "P619 currently assumes earth station z == height. " + "If ES has local coords != global coords, this probably isn't true" + ) + masked_indoor_stations = path.sta_a_to_masked(station_a.indoor) + masked_elevation_angles["free_space"] = path.mtx_to_masked( + station_a.geom.get_local_elevation(station_b.geom) + ) + masked_elevation_angles["apparent"] = PropagationP619.apparent_elevation_angle( + masked_elevation_angles["free_space"], + # FIXME + # self.earth_station_alt_m, + ) else: raise ValueError( "PropagationP619: At least one station must be an space station", ) - return self.get_loss( - distance_3d, - frequency, - indoor_stations, - elevation_angles) + masked_loss = self.get_loss( + masked_distance, + masked_frequency, + masked_indoor_stations, + masked_elevation_angles, + ) + + return path.from_masked_mtx(masked_loss) @dispatch(np.ndarray, np.ndarray, np.ndarray, dict) def get_loss( diff --git a/sharc/propagation/propagation_ter_simple.py b/sharc/propagation/propagation_ter_simple.py index bd20234ad..6d8a81426 100644 --- a/sharc/propagation/propagation_ter_simple.py +++ b/sharc/propagation/propagation_ter_simple.py @@ -13,6 +13,7 @@ from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss from sharc.support.enumerations import StationType +from sharc.propagation.propagation_path import PropagationPath class PropagationTerSimple(Propagation): @@ -28,17 +29,16 @@ def __init__(self, random_number_gen: np.random.RandomState): self.clutter = PropagationClutterLoss(np.random.RandomState(101)) self.free_space = PropagationFreeSpace(np.random.RandomState(101)) self.building_loss = 20 + self.clutter_type = "one_end" + self.clutter_scenario = "terrestrial" - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, - station_a_gains=None, - station_b_gains=None, + path: PropagationPath, + station_a_gains, + station_b_gains, ) -> 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 @@ -64,13 +64,11 @@ def get_loss( 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) + distance = path.mtx_to_masked(path.sta_a.geom.get_3d_distance_to(path.sta_b.geom)) frequency_array = frequency * np.ones(distance.shape) - indoor_stations = np.tile( - station_b.indoor, (station_a.num_stations, 1), - ) + indoor_stations = path.sta_b_to_masked(path.sta_b.indoor) - return self.get_loss(distance, frequency_array, indoor_stations, -1.0) + return path.from_masked_mtx(self.get_loss(distance, frequency_array, indoor_stations, -1.0)) # pylint: disable=arguments-differ @dispatch(np.ndarray, np.ndarray, np.ndarray, float) @@ -114,6 +112,8 @@ def get_loss( distance=distance, loc_percentage=loc_percentage, station_type=StationType.FSS_ES, + clutter_type=self.clutter_type, + clutter_scenario=self.clutter_scenario, ) building_loss = self.building_loss * indoor_stations diff --git a/sharc/propagation/propagation_tvro.py b/sharc/propagation/propagation_tvro.py index 4a501e37d..62089017b 100644 --- a/sharc/propagation/propagation_tvro.py +++ b/sharc/propagation/propagation_tvro.py @@ -14,6 +14,7 @@ from sharc.propagation.propagation import Propagation from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation_path import PropagationPath class PropagationTvro(Propagation): @@ -42,12 +43,11 @@ def __init__( self.free_space_path_loss = PropagationFreeSpace(random_number_gen) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: "PropagationPath", station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -79,46 +79,58 @@ def get_loss( and params.imt.topology.hotspot.num_clusters == 1 if wrap_around_enabled and ( - station_a.is_imt_station() and station_b.is_imt_station()): + path.sta_a.is_imt_station() and path.sta_b.is_imt_station()): distances_2d, distances_3d, _, _ = \ - station_a.get_dist_angles_wrap_around(station_b) + path.sta_a.geom.get_global_dist_angles_wrap_around(path.sta_b.geom) else: - distances_2d = station_a.get_distance_to(station_b) - distances_3d = station_a.get_3d_distance_to(station_b) + distances_2d = path.sta_a.geom.get_local_distance_to(path.sta_b.geom) + distances_3d = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) - indoor_stations = np.tile( - station_a.indoor, (station_b.num_stations, 1)).transpose() + indoor_stations = path.sta_a.indoor # 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(): + if path.sta_a.is_imt_station() and path.sta_b.is_imt_station(): + if path.sta_a.geom.uses_local_coords: + raise NotImplementedError( + "TVRO currently assumes UE z == height. " + "If UE has local coords != global coords, this probably isn't true" + ) + mskd_distance_3d = path.mtx_to_masked(distances_3d) 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 + distance_3D=mskd_distance_3d, + distance_2D=path.mtx_to_masked(distances_2d), + frequency=frequency * np.ones(mskd_distance_3d.shape), + ue_height=path.sta_a_to_masked(path.sta_a.geom.z_local), + indoor_stations=path.sta_a_to_masked(indoor_stations) ) else: - imt_station, sys_station = (station_a, station_b) \ - if station_a.is_imt_station() else (station_b, station_a) + imt_station, sys_station = (path.sta_a, path.sta_b) \ + if path.sta_a.is_imt_station() else (path.sta_b, path.sta_a) + + if sys_station.geom.uses_local_coords: + raise NotImplementedError( + "TVRO currently assumes System z == height. " + "If System has local coords != global coords, this probably isn't true" + ) + + es_z = sys_station.geom.z_local + if sys_station == path.sta_a: + es_z = path.sta_a_to_masked(es_z) + else: + es_z = path.sta_b_to_masked(es_z) + + mskd_distance_3d = path.mtx_to_masked(distances_3d) loss = self._get_loss( - distance_3D=distances_3d, - distance_2D=distances_2d, - frequency=frequency * np.ones(distances_2d.shape), - bs_height=station_b.height, + distance_3D=mskd_distance_3d, + distance_2D=path.mtx_to_masked(distances_2d), + frequency=frequency * np.ones(mskd_distance_3d.shape), 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 + es_z=es_z, + indoor_stations=path.sta_a_to_masked(indoor_stations) ) - return loss + return path.from_masked_mtx(loss) def _get_loss(self, *args, **kwargs) -> np.array: """ @@ -129,7 +141,6 @@ def _get_loss(self, *args, **kwargs) -> np.array: distance_3D (np.array) : 3D distances between stations distance_2D (np.array) : 2D distances between stations frequency (np.array) : center frequencie [MHz] - bs_height (np.array) : base station antenna heights Returns ------- array with path loss values with dimensions of distance_2D @@ -310,7 +321,7 @@ def get_los_probability( 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 + h_tvro = 6 * np.ones(len(distance_2D[0, :])) 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 diff --git a/sharc/propagation/propagation_uma.py b/sharc/propagation/propagation_uma.py index 1758e53e1..e6bcdab3f 100644 --- a/sharc/propagation/propagation_uma.py +++ b/sharc/propagation/propagation_uma.py @@ -11,7 +11,7 @@ from multipledispatch import dispatch from sharc.propagation.propagation import Propagation -from sharc.station_manager import StationManager +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.parameters import Parameters @@ -21,14 +21,11 @@ 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( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: PropagationPath, station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -57,27 +54,35 @@ def get_loss( if params.imt.topology.type == "HOTSPOT": wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ and params.imt.topology.hotspot.num_clusters == 1 + is_intra_imt = path.sta_a.is_imt_station() and path.sta_b.is_imt_station() - if wrap_around_enabled and ( - station_a.is_imt_station() and station_b.is_imt_station()): + if wrap_around_enabled and is_intra_imt: distances_2d, distances_3d, _, _ = \ - station_a.get_dist_angles_wrap_around(station_b) + path.sta_a.geom.get_global_dist_angles_wrap_around(path.sta_b.geom) 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, + distances_2d = path.sta_a.geom.get_local_distance_to(path.sta_b.geom) + distances_3d = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) + + if path.sta_a.geom.uses_local_coords or path.sta_b.geom.uses_local_coords: + raise NotImplementedError( + "UMa currently assumes stations z == height. " + "If stations has local coords != global coords, this probably isn't true" + ) + + mskd_distances_3d = path.mtx_to_masked(distances_3d) + mskd_distances_2d = path.mtx_to_masked(distances_2d) + mskd_loss = self.get_loss( + mskd_distances_3d, + mskd_distances_2d, + frequency * np.ones(mskd_distances_2d.shape), + path.sta_b_to_masked(path.sta_b.geom.z_local), + path.sta_a_to_masked(path.sta_a.geom.z_local), params.imt.shadowing, ) # the interface expects station_a.num_stations x station_b.num_stations # array - return loss + return path.from_masked_mtx(mskd_loss) @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool) def get_loss( @@ -176,7 +181,7 @@ def get_loss_los( idl = np.where(distance_2D < breakpoint_distance) # get index where distance if greater than breakpoint - idg = np.where(distance_2D >= breakpoint_distance) + idg = np.asarray(distance_2D >= breakpoint_distance).nonzero() loss = np.empty(distance_2D.shape) @@ -188,7 +193,7 @@ def get_loss_los( fitting_term = -10 * \ np.log10( breakpoint_distance**2 + - (h_bs - h_ue[:, np.newaxis])**2, + (h_bs - h_ue)**2, ) loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * \ np.log10(frequency[idg]) - 27.55 + fitting_term[idg] @@ -221,7 +226,7 @@ def get_loss_nlos( 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[:, np.newaxis] - 1.5) + - 0.6 * (h_ue - 1.5) idl = np.where(distance_2D < 5000) if len(idl[0]): @@ -262,7 +267,7 @@ def get_breakpoint_distance( """ # calculate the effective antenna heights h_bs_eff = h_bs - h_e - h_ue_eff = h_ue[:, np.newaxis] - h_e + h_ue_eff = h_ue - h_e # calculate the breakpoint distance breakpoint_distance = 4 * h_bs_eff * \ @@ -309,7 +314,7 @@ def get_los_probability( 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) @@ -324,11 +329,9 @@ def get_los_probability( ########################################################################### # 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], + np.linspace(1, 10000, num=10000), + np.linspace(1, 10000, num=10000), + np.linspace(1, 10000, num=10000), )) h_ue = np.array([1.5, 17, 23]) uma = PropagationUMa(np.random.RandomState(101)) diff --git a/sharc/propagation/propagation_umi.py b/sharc/propagation/propagation_umi.py index ef90623bb..f1c2654a2 100644 --- a/sharc/propagation/propagation_umi.py +++ b/sharc/propagation/propagation_umi.py @@ -10,6 +10,7 @@ from sharc.propagation.propagation import Propagation from sharc.station_manager import StationManager from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation_path import PropagationPath class PropagationUMi(Propagation): @@ -27,14 +28,11 @@ def __init__( super().__init__(random_number_gen) self.los_adjustment_factor = los_adjustment_factor - @dispatch(Parameters, float, StationManager, - StationManager, np.ndarray, np.ndarray) - def get_loss( + def get_path_loss( self, params: Parameters, frequency: float, - station_a: StationManager, - station_b: StationManager, + path: PropagationPath, station_a_gains=None, station_b_gains=None, ) -> np.array: @@ -65,23 +63,31 @@ def get_loss( and params.imt.topology.hotspot.num_clusters == 1 if wrap_around_enabled and ( - station_a.is_imt_station() and station_b.is_imt_station()): + path.sta_a.is_imt_station() and path.sta_b.is_imt_station()): distance_2d, distance_3d, _, _ = \ - station_a.get_dist_angles_wrap_around(station_b) + path.sta_a.geom.get_global_dist_angles_wrap_around(path.sta_b.geom) 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, + distance_2d = path.sta_a.geom.get_local_distance_to(path.sta_b.geom) + distance_3d = path.sta_a.geom.get_3d_distance_to(path.sta_b.geom) + + if path.sta_a.geom.uses_local_coords or path.sta_b.geom.uses_local_coords: + raise NotImplementedError( + "UMi currently assumes stations z == height. " + "If stations has local coords != global coords, this probably isn't true" + ) + + mskd_distances_3d = path.mtx_to_masked(distance_3d) + mskd_distances_2d = path.mtx_to_masked(distance_2d) + mskd_loss = self.get_loss( + mskd_distances_3d, + mskd_distances_2d, + frequency * np.ones(mskd_distances_2d.shape), + path.sta_b_to_masked(path.sta_b.geom.z_local), + path.sta_a_to_masked(path.sta_a.geom.z_local), params.imt.shadowing, ) - return loss + return path.from_masked_mtx(mskd_loss) # pylint: disable=function-redefined # pylint: disable=arguments-renamed @@ -201,7 +207,7 @@ def get_loss_los( fitting_term = -9.5 * \ np.log10( breakpoint_distance**2 + - (h_bs - h_ue[:, np.newaxis])**2, + (h_bs - h_ue)**2, ) loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * \ np.log10(frequency[idg]) - 27.55 + fitting_term[idg] @@ -236,7 +242,7 @@ def get_loss_nlos( """ # option 1 for UMi NLOS loss_nlos = -37.55 + 35.3 * np.log10(distance_3D) + 21.3 * np.log10(frequency) \ - - 0.3 * (h_ue[:, np.newaxis] - 1.5) + - 0.3 * (h_ue - 1.5) loss_los = self.get_loss_los( distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, 0, @@ -277,7 +283,7 @@ def get_breakpoint_distance( """ # calculate the effective antenna heights h_bs_eff = h_bs - h_e - h_ue_eff = h_ue[:, np.newaxis] - h_e + h_ue_eff = h_ue - h_e # calculate the breakpoint distance breakpoint_distance = 4 * h_bs_eff * \ @@ -340,7 +346,7 @@ def get_los_probability( ########################################################################### # Print LOS probability # h_ue = np.array([1.5, 17, 23]) - distance_2D = np.linspace(1, 1000, num=1000)[:, np.newaxis] + distance_2D = np.linspace(1, 1000, num=1000) umi = PropagationUMi(np.random.RandomState(101), 18) los_probability = np.empty(distance_2D.shape) diff --git a/sharc/propagation/scintillation.py b/sharc/propagation/scintillation.py index 042e8c884..3cb25425a 100644 --- a/sharc/propagation/scintillation.py +++ b/sharc/propagation/scintillation.py @@ -167,14 +167,14 @@ 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, + elevation=np.full(percentage_fading_exceeded.shape, elevation), + frequency_MHz=np.full(percentage_fading_exceeded.shape, frequency_MHz), + antenna_gain_dB=np.full(percentage_fading_exceeded.shape, antenna_gain), time_ratio=1 - (percentage_fading_exceeded / 100), wet_refractivity=wet_refractivity, ) - plt.semilogx( + plt.loglog( percentage_fading_exceeded, attenuation, label="{} deg".format(elevation), ) @@ -182,9 +182,9 @@ def get_tropospheric_attenuation(self, *args, **kwargs) -> np.array: 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, + elevation=np.full(percentage_fading_exceeded.shape, elevation), + frequency_MHz=np.full(percentage_fading_exceeded.shape, frequency_MHz), + antenna_gain_dB=np.full(percentage_fading_exceeded.shape, antenna_gain), time_ratio=percentage_gain_exceeded / 100, wet_refractivity=wet_refractivity, ) @@ -195,6 +195,9 @@ def get_tropospheric_attenuation(self, *args, **kwargs) -> np.array: plt.legend(title='elevation') plt.grid(True) + plt.grid(which='minor', color='gray', linestyle=':', linewidth=0.5) + plt.xlim((0.01, 10)) + plt.ylim((0.1, 10)) plt.title("troposcatter scintillation attenuation") plt.xlabel("Percentage time fades and enhancements exceeded") diff --git a/sharc/satellite/ngso/orbit_model.py b/sharc/satellite/ngso/orbit_model.py index 92f598394..841a30e4b 100644 --- a/sharc/satellite/ngso/orbit_model.py +++ b/sharc/satellite/ngso/orbit_model.py @@ -341,9 +341,32 @@ def main(): print("Orbit parameters:") print(orbit_params) + param_file = ( + "/home/artistreak/projects/Radio-Spectrum/SHARC/sharc/campaigns/multiple_mss_dc_to_imt/input/parameter_multiple_mss_dc_to_imt_fr_40exclusion_0.1load_downlink_imt.upto-1GHz.single-bs.urban-macro-bs_system-4.698-960MHz-block2.690km.yaml" + ) + from sharc.parameters.parameters import Parameters + parameters = Parameters() + parameters.set_file_name(param_file) + parameters.read_params() # Instantiate the OrbitModel - orbit_model = OrbitModel(**orbit_params) + # orbit_model = OrbitModel(**orbit_params) + orbit_model_params = parameters.mss_d2d.orbits[0] + orbit_model = OrbitModel( + Nsp=orbit_model_params.sats_per_plane, # Satellites per plane + Np=orbit_model_params.n_planes, # Number of orbital planes + phasing=orbit_model_params.phasing_deg, # Phasing angle in degrees + long_asc=orbit_model_params.long_asc_deg, # Longitude of ascending node in degrees + omega=orbit_model_params.omega_deg, # Argument of perigee in degrees + delta=orbit_model_params.inclination_deg, # Orbital inclination in degrees + hp=orbit_model_params.perigee_alt_km, # Perigee altitude in kilometers + ha=orbit_model_params.apogee_alt_km, # Apogee altitude in kilometers + Mo=orbit_model_params.initial_mean_anomaly, # Initial mean anomaly in degrees + # whether to use only time as random variable + model_time_as_random_variable=orbit_model_params.model_time_as_random_variable, + t_min=orbit_model_params.t_min, + t_max=orbit_model_params.t_max, + ) # Get satellite positions over time positions = orbit_model.get_satellite_positions_time_interval(n_periods=1) diff --git a/sharc/satellite/scripts/plot_footprints.py b/sharc/satellite/scripts/plot_footprints.py index 258a2d359..52b067be5 100644 --- a/sharc/satellite/scripts/plot_footprints.py +++ b/sharc/satellite/scripts/plot_footprints.py @@ -6,10 +6,11 @@ import numpy as np import plotly.graph_objects as go from dataclasses import dataclass, field +from functools import reduce from sharc.station_factory import StationFactory from sharc.parameters.parameters_mss_d2d import ParametersOrbit, ParametersMssD2d -from sharc.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem from sharc.satellite.utils.sat_utils import ecef2lla from sharc.station_manager import StationManager from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 @@ -50,7 +51,7 @@ class FootPrintOpts: def plot_fp( params, - geoconv, + coord_sys, opts=FootPrintOpts(seed=32) ): """ @@ -58,7 +59,7 @@ def plot_fp( Args: params: Parameters for the MSS D2D system. - geoconv: GeometryConverter instance for coordinate transformations. + coord_sys: CoordinateSystem instance for coordinate transformations. opts: FootPrintOpts instance with plotting options. Returns: @@ -81,15 +82,17 @@ def plot_fp( 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()]) + center_of_earth.geom.set_global_coords( + np.array([0.0]), + np.array([0.0]), + np.array([-coord_sys.get_translation()]), + ) - mss_d2d_manager = StationFactory.generate_mss_d2d(params, rng, geoconv) + mss_d2d_manager = StationFactory.generate_mss_d2d(params, rng, coord_sys) # Plot the globe with satellite positions fig = plot_globe_with_borders( - True, geoconv, True + True, coord_sys, True ) # Set the camera position in Plotly @@ -107,7 +110,7 @@ def plot_fp( range=(-show_range / 2, show_range / 2) ), camera=dict( - center=dict(x=0, y=0, z=center_of_earth.z[0] / show_range / 1e3), + center=dict(x=0, y=0, z=center_of_earth.geom.z_global[0] / show_range / 1e3), eye=dict(x=0, y=0, z=0.7), # Eye position (above the center) up=dict(x=0, y=1, z=0) # "Up" is along +y (default is usually +z) ) @@ -127,10 +130,10 @@ def plot_fp( # 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], + orx, ory, orz = coord_sys.enu2ecef( + station_1.geom.x_global[center_fp_at_sat], + station_1.geom.y_global[center_fp_at_sat], + station_1.geom.z_global[center_fp_at_sat], ) sat_lat, sat_long, sat_alt = ecef2lla(orx, ory, orz) @@ -138,8 +141,8 @@ def plot_fp( # lon_vals = np.linspace(sat_long - 10.0, sat_long + 10.0, resolution) # # Ground station as fp center - # lat_vals = np.linspace(geoconv.ref_lat, geoconv.ref_lat + 10.0, resolution) - # lon_vals = np.linspace(geoconv.ref_long - 5.0, geoconv.ref_long + 5.0, resolution) + # lat_vals = np.linspace(coord_sys.ref_lat, coord_sys.ref_lat + 10.0, resolution) + # lon_vals = np.linspace(coord_sys.ref_long - 5.0, coord_sys.ref_long + 5.0, resolution) # Arbitrary range for fp calulation lat_vals = np.linspace(-33.69111, 4, resolution) @@ -154,14 +157,15 @@ def plot_fp( # 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) + x_flat, y_flat, z_flat = coord_sys.lla2enu(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 + surf_manager.geom.set_global_coords( + x_flat, + y_flat, + z_flat, + ) station_1 = mss_d2d_manager mss_active = np.where(station_1.active)[0] @@ -171,8 +175,8 @@ def plot_fp( print("Calculating gains (memory intensive)") # Calculate vector and apointment off_axis gains = np.zeros((len(mss_active), len(station_2_active))) - off_axis_angle = station_1.get_off_axis_angle(station_2) - phi, theta = station_1.get_pointing_vector_to(station_2) + off_axis_angle = station_1.geom.get_off_axis_angle(station_2.geom) + phi, theta = station_1.geom.get_global_pointing_vector_to(station_2.geom) for k in range(len(mss_active)): gains[k, :] = \ station_1.antenna[k].calculate_gain( @@ -243,9 +247,9 @@ def plot_fp( # 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, + x=mss_d2d_manager.geom.x_global / 1e3, + y=mss_d2d_manager.geom.y_global / 1e3, + z=mss_d2d_manager.geom.z_global / 1e3, mode='markers', marker=dict(size=2, color='red', opacity=0.5), showlegend=False @@ -254,9 +258,9 @@ def plot_fp( # 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, + x=mss_d2d_manager.geom.x_global[mss_d2d_manager.active] / 1e3, + y=mss_d2d_manager.geom.y_global[mss_d2d_manager.active] / 1e3, + z=mss_d2d_manager.geom.z_global[mss_d2d_manager.active] / 1e3, mode='markers', marker=dict(size=3, color='green', opacity=0.8), name="MSS D2D sat", @@ -272,8 +276,9 @@ def plot_fp( )) polygons_lim = plot_mult_polygon( + # params.beam_positioning.service_grid.eligibility_polygon, params.sat_is_active_if.lat_long_inside_country.filter_polygon, - geoconv, + coord_sys, True, 2 ) @@ -301,7 +306,7 @@ def plot_fp( lon = np.array(xy_coords[0]) lat = np.array(xy_coords[1]) - x, y, z = geoconv.convert_lla_to_transformed_cartesian(lat, lon, 1e3) + x, y, z = coord_sys.lla2enu(lat, lon, 1e3) fig.add_trace(go.Scatter3d( x=x / 1e3, @@ -311,10 +316,33 @@ def plot_fp( marker=dict(size=1, color='blue', opacity=1.0), name="Service Point" )) + + if params.beam_positioning.service_grid.grid_exclusion_zone._polygon is not None: + polygons_lim = plot_mult_polygon( + params.beam_positioning.service_grid.grid_exclusion_zone._polygon, + coord_sys, + True, + 2 + ) + + 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(255, 0, 0)'), + name="Exclusion Zone" + )) # fig.add_trace(go.Scatter3d( - # x=center_of_earth.x / 1e3, - # y=center_of_earth.y / 1e3, - # z=center_of_earth.z / 1e3, + # x=center_of_earth.geom.x_global / 1e3, + # y=center_of_earth.geom.y_global / 1e3, + # z=center_of_earth.geom.z_global / 1e3, # mode='markers', # marker=dict(size=5, color='black', opacity=1.0), # showlegend=False @@ -354,19 +382,35 @@ def plot_fp( 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, cell_radius=spotbeam_radius, orbits=[orbit_1] ) + params.antenna.itu_r_s_1528 = antenna_params 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.sat_is_active_if.lat_long_inside_country.country_names = ["Brazil", "Paraguay"] + params.sat_is_active_if.lat_long_inside_country.margin_from_border = 111 # params.beams_load_factor = 0.1 params.beam_positioning.type = "SERVICE_GRID" + + grid_exclusion_zone = params.beam_positioning.service_grid.grid_exclusion_zone + grid_exclusion_zone.type = "CIRCLE" + # at frienship bridge, so should affect more than 1 grid + grid_exclusion_zone.circle.center_lat = -25.5094741 + grid_exclusion_zone.circle.center_lon = -54.6007197 + grid_exclusion_zone.circle.radius_km = 5 * spotbeam_radius / 1e3 + + # grid_exclusion_zone.type = "CIRCLE" + # # at frienship bridge, so should affect more than 1 grid + # grid_exclusion_zone.circle.center_lat = -25.5094741 + # grid_exclusion_zone.circle.center_lon = -54.6007197 + # grid_exclusion_zone.circle.radius_km = 0.00001 + # grid_exclusion_zone.circle.radius_km = 2 * spotbeam_radius / 1e3 + # params.beam_positioning.type = "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE" # params.beam_positioning.angle_from_subsatellite_phi.type = "~U(MIN,MAX)" # params.beam_positioning.angle_from_subsatellite_phi.distribution.min = -60.0 @@ -376,13 +420,13 @@ def plot_fp( params.propagate_parameters() params.validate("opa") - geoconv = GeometryConverter() + coord_sys = CoordinateSystem() sys_lat = -14.5 sys_long = -52 sys_alt = 1200 - geoconv.set_reference( + coord_sys.set_reference( sys_lat, sys_long, sys_alt ) @@ -396,6 +440,6 @@ def plot_fp( ] for opt in opts: - fig = plot_fp(params, geoconv, opt) + fig = plot_fp(params, coord_sys, opt) # fig.write_image(f"fp.png") fig.show() diff --git a/sharc/satellite/scripts/plot_globe.py b/sharc/satellite/scripts/plot_globe.py index 61a034786..81d8fda9e 100644 --- a/sharc/satellite/scripts/plot_globe.py +++ b/sharc/satellite/scripts/plot_globe.py @@ -6,7 +6,7 @@ from sharc.satellite.ngso.constants import EARTH_RADIUS_KM -def plot_back(fig, geoconv, in_km): +def plot_back(fig, coord_sys, in_km): """back half of sphere""" clor = 'rgb(220, 220, 220)' # Create a mesh grid for latitude and longitude. @@ -22,7 +22,7 @@ def plot_back(fig, geoconv, in_km): lat_flat = lat.flatten() lon_flat = lon.flatten() - if geoconv is None: + if coord_sys is None: lon = lon * np.pi / 180 lat = lat * np.pi / 180 @@ -36,7 +36,7 @@ def plot_back(fig, geoconv, in_km): # 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( + x_flat, y_flat, z_flat = coord_sys.lla2enu( lat_flat, lon_flat, 0) if in_km: x_flat, y_flat, z_flat = x_flat / 1e3, y_flat / 1e3, z_flat / 1e3 @@ -58,7 +58,7 @@ def plot_back(fig, geoconv, in_km): ) -def plot_front(fig, geoconv, in_km): +def plot_front(fig, coord_sys, in_km): """front half of sphere""" clor = 'rgb(220, 220, 220)' @@ -75,7 +75,7 @@ def plot_front(fig, geoconv, in_km): lat_flat = lat.flatten() lon_flat = lon.flatten() - if geoconv is None: + if coord_sys is None: lon = lon * np.pi / 180 lat = lat * np.pi / 180 @@ -89,7 +89,7 @@ def plot_front(fig, geoconv, in_km): # 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( + x_flat, y_flat, z_flat = coord_sys.lla2enu( lat_flat, lon_flat, 0) if in_km: x_flat, y_flat, z_flat = x_flat / 1e3, y_flat / 1e3, z_flat / 1e3 @@ -111,13 +111,13 @@ def plot_front(fig, geoconv, in_km): ) -def plot_polygon(poly, geoconv, in_km, alt=0): +def plot_polygon(poly, coord_sys, in_km, alt=0): """ Convert a polygon's coordinates to 3D Cartesian coordinates for plotting on a globe. Args: poly: The polygon object with exterior coordinates. - geoconv: Geodetic converter object or None. + coord_sys: Geodetic converter object or None. in_km (bool): Whether to use kilometers for the output coordinates. alt (float, optional): Altitude to add to the radius. Defaults to 0. @@ -129,7 +129,7 @@ def plot_polygon(poly, geoconv, in_km, alt=0): lon = np.array(xy_coords[0]) lat = np.array(xy_coords[1]) - if geoconv is None: + if coord_sys is None: lon = lon * np.pi / 180 lat = lat * np.pi / 180 @@ -140,7 +140,7 @@ def plot_polygon(poly, geoconv, in_km, alt=0): y = R * np.cos(lat) * np.sin(lon) z = R * np.sin(lat) else: - x, y, z = geoconv.convert_lla_to_transformed_cartesian( + x, y, z = coord_sys.lla2enu( lat, lon, alt * (1e3 if in_km else 1)) if in_km: x, y, z = x / 1e3, y / 1e3, z / 1e3 @@ -148,13 +148,13 @@ def plot_polygon(poly, geoconv, in_km, alt=0): return x, y, z -def plot_mult_polygon(mult_poly, geoconv, in_km: bool, alt=0): +def plot_mult_polygon(mult_poly, coord_sys, in_km: bool, alt=0): """ Convert a MultiPolygon or Polygon to a list of 3D Cartesian coordinate arrays for plotting. Args: mult_poly: The MultiPolygon or Polygon object. - geoconv: Geodetic converter object or None. + coord_sys: Geodetic converter object or None. in_km (bool): Whether to use kilometers for the output coordinates. alt (float, optional): Altitude to add to the radius. Defaults to 0. @@ -162,19 +162,19 @@ def plot_mult_polygon(mult_poly, geoconv, in_km: bool, alt=0): list: A list of tuples containing arrays of x, y, z coordinates for each polygon. """ if mult_poly.geom_type == 'Polygon': - return [plot_polygon(mult_poly, geoconv, in_km, alt)] + return [plot_polygon(mult_poly, coord_sys, in_km, alt)] elif mult_poly.geom_type == 'MultiPolygon': - return [plot_polygon(poly, geoconv, in_km, alt) + return [plot_polygon(poly, coord_sys, in_km, alt) for poly in mult_poly.geoms] -def plot_globe_with_borders(opaque_globe: bool, geoconv, in_km: bool): +def plot_globe_with_borders(opaque_globe: bool, coord_sys, in_km: bool): """ Plot a 3D globe with country borders using Plotly. Args: opaque_globe (bool): Whether to plot the globe as opaque. - geoconv: Geodetic converter object or None. + coord_sys: Geodetic converter object or None. in_km (bool): Whether to use kilometers for the output coordinates. Returns: @@ -196,8 +196,8 @@ def plot_globe_with_borders(opaque_globe: bool, geoconv, in_km: bool): # margin=dict(l=0, r=0, b=0, t=0) # ) if opaque_globe: - plot_front(fig, geoconv, in_km) - plot_back(fig, geoconv, in_km) + plot_front(fig, coord_sys, in_km) + plot_back(fig, coord_sys, in_km) x_all, y_all, z_all = [], [], [] for i in gdf.index: @@ -206,7 +206,7 @@ def plot_globe_with_borders(opaque_globe: bool, geoconv, in_km: bool): polys = gdf.loc[i].geometry # Polygons or MultiPolygons if polys.geom_type == 'Polygon': - x, y, z = plot_polygon(polys, geoconv, in_km, 1 if in_km else 1e3) + x, y, z = plot_polygon(polys, coord_sys, in_km, 1 if in_km else 1e3) x_all.extend(x) x_all.extend([None]) # None separates different polygons y_all.extend(y) @@ -218,7 +218,7 @@ def plot_globe_with_borders(opaque_globe: bool, geoconv, in_km: bool): for poly in polys.geoms: x, y, z = plot_polygon( - poly, geoconv, in_km, 1 if in_km else 1e3) + poly, coord_sys, in_km, 1 if in_km else 1e3) x_all.extend(x) x_all.extend([None]) # None separates different polygons y_all.extend(y) diff --git a/sharc/satellite/scripts/plot_orbits_single_color_3d.py b/sharc/satellite/scripts/plot_orbits_single_color_3d.py index 33bda2100..f41c56a2a 100644 --- a/sharc/satellite/scripts/plot_orbits_single_color_3d.py +++ b/sharc/satellite/scripts/plot_orbits_single_color_3d.py @@ -6,15 +6,15 @@ import numpy as np import plotly.graph_objects as go -from sharc.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem from sharc.station_manager import StationManager -geoconv = GeometryConverter() +coord_sys = CoordinateSystem() sys_lat = -14.5 sys_long = -45 sys_alt = 1200 -geoconv.set_reference( +coord_sys.set_reference( sys_lat, sys_long, sys_alt ) @@ -72,24 +72,26 @@ # 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( + ground_sta_pos = coord_sys.lla2enu( 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()]) + center_of_earth.geom.set_global_coords( + np.array([0.0]), + np.array([0.0]), + np.array([-coord_sys.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) + mss_d2d_manager = StationFactory.generate_mss_d2d(params, rng, coord_sys) # 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) + x_vec = mss_d2d_manager.geom.x_global / 1e3 # (Km) + y_vec = mss_d2d_manager.geom.y_global / 1e3 # (Km) + z_vec = mss_d2d_manager.geom.z_global / 1e3 # (Km) # Store all positions all_positions['x'].extend(x_vec) all_positions['y'].extend(y_vec) @@ -99,12 +101,12 @@ 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) + off_axis = mss_d2d_manager.geom.get_off_axis_angle(center_of_earth.geom) 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]) + vis_elevation.extend(mss_d2d_manager.geom.pointn_elev_global[vis_sat_idxs]) # Flatten arrays all_positions['x'] = np.concatenate([all_positions['x']]) @@ -117,7 +119,7 @@ # Plot the globe with satellite positions fig = plot_globe_with_borders( - True, geoconv, True + True, coord_sys, True ) # Plot all satellites (red markers) @@ -153,9 +155,9 @@ )) # fig.add_trace(go.Scatter3d( - # x=center_of_earth.x / 1e3, - # y=center_of_earth.y / 1e3, - # z=center_of_earth.z / 1e3, + # x=center_of_earth.geom.x_global / 1e3, + # y=center_of_earth.geom.y_global / 1e3, + # z=center_of_earth.geom.z_global / 1e3, # mode='markers', # marker=dict(size=5, color='black', opacity=1.0), # showlegend=False diff --git a/sharc/satellite/utils/sat_utils.py b/sharc/satellite/utils/sat_utils.py index 49d86ed23..8cbb0abcf 100644 --- a/sharc/satellite/utils/sat_utils.py +++ b/sharc/satellite/utils/sat_utils.py @@ -19,9 +19,11 @@ def ecef2lla(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple: tuple (lat, long, alt) lat long and altitude in spherical earth model """ - x = np.atleast_1d(x) - y = np.atleast_1d(y) - z = np.atleast_1d(z) + # Transform to kilometers to prevent overflow + x = np.atleast_1d(x).astype(float) + y = np.atleast_1d(y).astype(float) + z = np.atleast_1d(z).astype(float) + xy = np.sqrt(x**2 + y**2) lon = np.arccos(x / xy) @@ -134,8 +136,120 @@ def haversine( return 2 * R * np.arcsin(np.sqrt(a)) +def sat_elevation_to_offaxis(elevation_angle_deg: float | np.ndarray, sat_altitude_m: float | np.ndarray) -> float: + """ + Convert satellite elevation angle to off-axis angle (angle from nadir). + + Parameters + ---------- + elevation_angle_deg : float + Elevation angle in degrees. + sat_altitude_m : float + Satellite altitude above Earth's surface in meters. + + Returns + ------- + float + Off-axis angle in degrees. + """ + if np.isscalar(elevation_angle_deg): + if elevation_angle_deg < 0 or elevation_angle_deg > 90: + raise ValueError("Elevation angle must be between 0 and 90 degrees.") + else: + if (elevation_angle_deg < 0).any() or (elevation_angle_deg > 90).any(): + raise ValueError("Elevation angle must be between 0 and 90 degrees.") + + # Convert elevation angle to radians + elevation_angle_rad = np.deg2rad(elevation_angle_deg) + + # Calculate the distance from the Earth's center to the satellite + r_sat = EARTH_RADIUS_M + sat_altitude_m + + # Calculate the off-axis angle using the spherical triangle relationship + offaxis_angle_rad = np.arcsin( + (EARTH_RADIUS_M / r_sat) * np.sin(elevation_angle_rad + np.pi / 2) + ) + + # Convert off-axis angle back to degrees + offaxis_angle_deg = np.rad2deg(offaxis_angle_rad) + + return offaxis_angle_deg + + +def offaxis_to_sat_elevation(offaxis_angle_deg: float | np.ndarray, sat_altitude_m: float | np.ndarray) -> float: + """ + Convert off-axis angle (angle from nadir) to satellite elevation angle. + + Parameters + ---------- + offaxis_angle_deg : float + Off-axis angle in degrees. + sat_altitude_km : float + Satellite altitude above Earth's surface in meters. + + Returns + ------- + float + Elevation angle in degrees. + """ + # We know that 90 deg offaxis is physically impossible, so raise ValueError + if np.isscalar(offaxis_angle_deg): + if offaxis_angle_deg < 0 or offaxis_angle_deg >= 90: + raise ValueError("Elevation angle must be between 0 and 90 degrees.") + else: + if (offaxis_angle_deg < 0).any() or (offaxis_angle_deg >= 90).any(): + raise ValueError("Elevation angle must be between 0 and 90 degrees.") + # Convert off-axis angle to radians + offaxis_angle_rad = np.deg2rad(offaxis_angle_deg) + + # Calculate the distance from the Earth's center to the satellite + r_sat = EARTH_RADIUS_M + sat_altitude_m + + # Calculate the elevation angle using the sine law. + elevation_angle_rad = np.pi / 2 - np.arcsin((r_sat) / EARTH_RADIUS_M * np.sin(offaxis_angle_rad)) + + # Convert elevation angle back to degrees + elevation_angle_deg = np.rad2deg(elevation_angle_rad) + + return elevation_angle_deg + + +def earth_arc_length_from_nadir(offaxis_angle_deg: float | np.ndarray, sat_altitude_m: float | np.ndarray) -> float: + """ + Calculate the Earth's surface arc length from nadir to the point + corresponding to the given off-axis angle. + + Parameters + ---------- + offaxis_angle_deg : float + Off-axis angle in degrees. + sat_altitude_km : float + Satellite altitude above Earth's surface in meters. + + Returns + ------- + float + Arc length on Earth's surface in meters. + """ + # Convert off-axis angle to radians + offaxis_angle_rad = np.deg2rad(offaxis_angle_deg) + + phi_rad = np.deg2rad(offaxis_to_sat_elevation(offaxis_angle_deg, sat_altitude_m) + 90.0) + + central_angle_rad = np.pi - phi_rad - offaxis_angle_rad + + # Calculate the arc length on Earth's surface + arc_length_m = EARTH_RADIUS_M * central_angle_rad + + return arc_length_m + + if __name__ == "__main__": r1 = ecef2lla(7792.1450, 0, 0) print(r1) r2 = lla2ecef(r1[0], r1[1], r1[2]) print(r2) + + print(earth_arc_length_from_nadir(2.19, 520.0 * 1e3)) + + print(sat_elevation_to_offaxis(50.0, 520e3)) diff --git a/sharc/simulation.py b/sharc/simulation.py index 120807227..07e44a800 100644 --- a/sharc/simulation.py +++ b/sharc/simulation.py @@ -12,14 +12,16 @@ import math import sys import matplotlib.pyplot as plt +import typing from sharc.support.enumerations import StationType from sharc.topology.topology_factory import TopologyFactory -from sharc.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem from sharc.parameters.parameters import Parameters from sharc.station_manager import StationManager from sharc.results import Results from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath from sharc.support.sharc_utils import wrap2_180, clip_angle @@ -81,20 +83,20 @@ def __init__(self, parameters: Parameters, parameter_file: str): self.co_channel = self.parameters.general.enable_cochannel self.adjacent_channel = self.parameters.general.enable_adjacent_channel - geometry_converter = GeometryConverter() + coordinate_system = CoordinateSystem() if self.parameters.imt.topology.central_latitude is not None: - geometry_converter.set_reference( + coordinate_system.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.coordinate_system = coordinate_system self.topology = TopologyFactory.createTopology( self.parameters, - geometry_converter + coordinate_system ) self.bs_power_gain = 0 @@ -124,6 +126,9 @@ def __init__(self, parameters: Parameters, parameter_file: str): self.bs = np.empty(0) self.system = np.empty(0) + self.paths_between_imt_and_sys: PropagationPath = None + self.intra_imt_paths: PropagationPath = None + self.link = dict() self.num_rb_per_bs = 0 @@ -303,9 +308,12 @@ def calculate_coupling_loss_system_imt( # Calculate the antenna gains of the IMT station with respect to the # system's station + use_separated_beams = False if imt_station.station_type is StationType.IMT_UE: # define antenna gains + # shape: (n_sys, n_imt) gain_sys_to_imt = self.calculate_gains(system_station, imt_station) + # shape: (n_sys, n_imt) gain_imt_to_sys = np.transpose( self.calculate_gains( imt_station, @@ -315,17 +323,35 @@ def calculate_coupling_loss_system_imt( + self.parameters.imt.ue.body_loss \ + self.polarization_loss elif imt_station.station_type is StationType.IMT_BS: + use_separated_beams = True # define antenna gains # repeat for each BS beam + # shape: (n_sys, n_imt * ue_k) gain_sys_to_imt = np.repeat( self.calculate_gains(system_station, imt_station), self.parameters.imt.ue.k, 1, ) + # shape: (n_sys, n_imt * ue_k) gain_imt_to_sys = np.transpose( self.calculate_gains( imt_station, system_station, is_co_channel, ), ) + + # shape: (n_sys, n_bs, ue_k) + gain_sys_to_imt_separated_beams = gain_sys_to_imt.reshape( + gain_sys_to_imt.shape[0], + gain_sys_to_imt.shape[1] // self.parameters.imt.ue.k, + self.parameters.imt.ue.k + ) + # shape: (n_bs, n_sys, ue_k) + # reshape makes (n_sys, n_bs, ue_k), transpose changes axis + gain_imt_to_sys_separated_beams_imt_frst = gain_imt_to_sys.reshape( + gain_imt_to_sys.shape[0], + gain_imt_to_sys.shape[1] // self.parameters.imt.ue.k, + self.parameters.imt.ue.k + ).transpose(1, 0, 2) + additional_loss = self.parameters.imt.bs.ohmic_loss \ + self.polarization_loss else: @@ -334,44 +360,37 @@ def calculate_coupling_loss_system_imt( 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( + if use_separated_beams: + sta_a_gains = gain_sys_to_imt_separated_beams + sta_b_gains = gain_imt_to_sys_separated_beams_imt_frst + else: + sta_a_gains = gain_sys_to_imt + sta_b_gains = gain_imt_to_sys.T + + path_loss = self.paths_between_imt_and_sys.get_path_loss( + self.propagation_system, self.parameters, freq, - system_station, - imt_station, - gain_sys_to_imt, - gain_imt_to_sys, + sta_a_gains=sta_a_gains, + sta_b_gains=sta_b_gains, ) # Store antenna gains and path loss samples if self.param_system.channel_model == "HDFSS": + # TODO: standardized way of storing these results + # instead of just for 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, - ) + if use_separated_beams: + if path_loss.ndim == 3: + # (n_sys, n_imt * ue_k) + path_loss = path_loss.reshape(path_loss.shape[0], -1) + elif path_loss.shape[1] == imt_station.num_stations: + path_loss = np.repeat(path_loss, self.parameters.imt.ue.k, -1) + + self.imt_system_path_loss = path_loss self.system_imt_antenna_gain = gain_sys_to_imt @@ -429,13 +448,12 @@ def calculate_intra_imt_coupling_loss( # 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( + path_loss = self.intra_imt_paths.get_path_loss( + self.propagation_imt, self.parameters, self.parameters.imt.frequency, - imt_ue_station, - imt_bs_station, - ant_gain_ue_to_bs, - ant_gain_bs_to_ue, + sta_a_gains=ant_gain_ue_to_bs, + sta_b_gains=ant_gain_bs_to_ue, ) # Collect IMT BS and UE antenna gain samples @@ -475,16 +493,16 @@ def select_ue(self, random_number_gen: np.random.RandomState): """ if self.wrap_around_enabled: self.bs_to_ue_d_2D, self.bs_to_ue_d_3D, self.bs_to_ue_phi, self.bs_to_ue_theta = \ - self.bs.get_dist_angles_wrap_around(self.ue) + self.bs.geom.get_global_dist_angles_wrap_around(self.ue.geom) 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_d_2D = self.bs.geom.get_local_distance_to(self.ue.geom) + self.bs_to_ue_d_3D = self.bs.geom.get_3d_distance_to(self.ue.geom) + self.bs_to_ue_phi, self.bs_to_ue_theta = self.bs.geom.get_global_pointing_vector_to( + self.ue.geom, ) bs_active = np.where(self.bs.active)[0] - assert np.all((-180 <= self.bs.azimuth) & (self.bs.azimuth <= 180)), "BS azimuth angles should be in [-180, 180] range" + assert np.all((-180 <= self.bs.geom.pointn_azim_global) & (self.bs.geom.pointn_azim_global <= 180)), "BS azimuth angles should be in [-180, 180] range" for bs in bs_active: # select K UE's among the ones that are connected to BS random_number_gen.shuffle(self.link[bs]) @@ -498,7 +516,7 @@ def select_ue(self, random_number_gen: np.random.RandomState): # limit beamforming angle beam_h_min, beam_h_max = wrap2_180( - self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range + self.bs.azimuth[bs] + self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range + self.bs.geom.pointn_azim_global[bs] ) bs_beam_phi = clip_angle( @@ -576,7 +594,7 @@ def calculate_gains( theta = self.bs_to_ue_theta beams_idx = self.bs_to_ue_beam_rbs[station_2_active] elif not station_2.is_imt_station(): - phi, theta = station_1.get_pointing_vector_to(station_2) + phi, theta = station_1.geom.get_global_pointing_vector_to(station_2.geom) phi = np.repeat(phi, self.parameters.imt.ue.k, 0) theta = np.repeat(theta, self.parameters.imt.ue.k, 0) beams_idx = np.tile( @@ -584,17 +602,17 @@ def calculate_gains( ) elif (station_1.station_type is StationType.IMT_UE): - phi, theta = station_1.get_pointing_vector_to(station_2) + phi, theta = station_1.geom.get_global_pointing_vector_to(station_2.geom) 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) + phi, theta = station_1.geom.get_global_pointing_vector_to(station_2.geom) 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 not station_2.is_imt_station(): - off_axis_angle = station_1.get_off_axis_angle(station_2) + off_axis_angle = station_1.geom.get_off_axis_angle(station_2.geom) for k in station_1_active: for b in range( k * self.parameters.imt.ue.k, @@ -612,7 +630,7 @@ def calculate_gains( 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) + off_axis_angle = station_1.geom.get_off_axis_angle(station_2.geom) 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], @@ -633,8 +651,8 @@ def calculate_gains( elif not station_1.is_imt_station(): - off_axis_angle = station_1.get_off_axis_angle(station_2) - phi, theta = station_1.get_pointing_vector_to(station_2) + off_axis_angle = station_1.geom.get_off_axis_angle(station_2.geom) + phi, theta = station_1.geom.get_global_pointing_vector_to(station_2.geom) for k in station_1_active: gains[k, station_2_active] = \ station_1.antenna[k].calculate_gain( @@ -643,7 +661,7 @@ def calculate_gains( phi_vec=phi[k, station_2_active], ) else: # for IMT <-> IMT - off_axis_angle = station_1.get_off_axis_angle(station_2) + off_axis_angle = station_1.geom.get_off_axis_angle(station_2.geom) 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], @@ -739,7 +757,7 @@ def plot_scenario(self): # Plot user equipments ax.scatter( - self.ue.x, self.ue.y, color='r', + self.ue.geom.x_global, self.ue.geom.y_global, color='r', edgecolor="w", linewidth=0.5, label="UE", ) @@ -748,13 +766,13 @@ def plot_scenario(self): # Plot UE's azimuth d = 0.1 * self.topology.cell_radius - for i in range(len(self.ue.x)): + for i in range(len(self.ue.geom.x_global)): plt.plot( - [self.ue.x[i], self.ue.x[i] + d * - math.cos(math.radians(self.ue.azimuth[i]))], + [self.ue.geom.x_global[i], self.ue.geom.x_global[i] + d * + math.cos(math.radians(self.ue.geom.pointn_azim_global[i]))], [ - self.ue.y[i], self.ue.y[i] + d * - math.sin(math.radians(self.ue.azimuth[i])), + self.ue.geom.y_global[i], self.ue.geom.y_global[i] + d * + math.sin(math.radians(self.ue.geom.pointn_azim_global[i])), ], 'r-', ) @@ -776,7 +794,7 @@ def plot_scenario(self): # Plot user equipments ax.scatter( - self.ue.x, self.ue.height, color='r', + self.ue.geom.x_global, self.ue.geom.z_global, color='r', edgecolor="w", linewidth=0.5, label="UE", ) @@ -789,6 +807,45 @@ def plot_scenario(self): # sys.exit(0) + def add_system_imt_interaction_attr_to_results( + self, + link: typing.Literal["DL", "UL"], + attr: str | object, + result_attr: str | None = None, + result_obj: typing.Any = None, + ): + """Adds attribute (self.attr if attr is str else attr if attr is object) + to the instance results object. + Works with system<>imt interaction, getting results based on existing + paths_between_imt_and_sys + """ + if isinstance(attr, str): + v = np.array(getattr(self, attr)) + else: + v = attr + + if result_obj is None: + result_obj = self.results + if result_attr is None: + result_attr = attr + + sys_to_imt_paths_mask = self.paths_between_imt_and_sys.mask + n_sys = self.system.num_stations + + assert v.shape[0] == n_sys + if ( + not self.parameters.imt.interfered_with and link == "DL" + or self.parameters.imt.interfered_with and link == "UL" + ): + n_bs = self.bs.num_stations + ue_k = self.parameters.imt.ue.k + v = v.reshape( + n_sys, n_bs, ue_k + ) + v = v[sys_to_imt_paths_mask] + + getattr(result_obj, result_attr).extend(v.flatten()) + @abstractmethod def snapshot(self, *args, **kwargs): """ diff --git a/sharc/simulation_downlink.py b/sharc/simulation_downlink.py index bbe977a9f..58e9231be 100644 --- a/sharc/simulation_downlink.py +++ b/sharc/simulation_downlink.py @@ -13,6 +13,7 @@ from sharc.parameters.parameters import Parameters from sharc.station_factory import StationFactory from sharc.parameters.constants import BOLTZMANN_CONSTANT +from sharc.propagation.propagation_path import PropagationPath warn = warnings.warn @@ -72,7 +73,7 @@ def snapshot(self, *args, **kwargs): # Create the other system (FSS, HAPS, etc...) self.system = StationFactory.generate_system( self.parameters, self.topology, random_number_gen, - geometry_converter=self.geometry_converter + coordinate_system=self.coordinate_system ) # Create IMT user equipments @@ -83,6 +84,22 @@ def snapshot(self, *args, **kwargs): self.topology, random_number_gen, ) + if self.parameters.imt.interfered_with: + self.paths_between_imt_and_sys = PropagationPath.create_default( + self.system, + self.ue, + ) + else: + self.paths_between_imt_and_sys = PropagationPath.create_default( + self.system, + self.bs, + ) + + self.intra_imt_paths = PropagationPath.create_default( + self.ue, + self.bs, + ) + # self.plot_scenario() self.connect_ue_to_bs() @@ -155,22 +172,40 @@ def calculate_sinr(self): Calculates the downlink SINR for each UE. """ bs_active = np.where(self.bs.active)[0] + ue_k = self.parameters.imt.ue.k + + # paths that actually exist and should make signal or interf + ue_to_bs_path_mask = self.intra_imt_paths.mask + # since bs tx_power is dict, we get it to array for easier use + bs_tx_power_per_beam_array = np.array([ + self.bs.tx_power[k] if k in self.bs.tx_power + else np.full(ue_k, -np.inf) + for k in range(self.bs.num_stations) + ]) 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] - - # 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] - - self.ue.rx_interference[ue] = 10 * np.log10(np.power( - 10, 0.1 * self.ue.rx_interference[ue]) + np.power(10, 0.1 * interference), ) + for ui in ue: + # base stations that have links/paths to ue + bs_interf = np.where(ue_to_bs_path_mask[ui])[0] + # remove serving bs to get interferrers + bs_interf = bs_interf[bs_interf != bs] + # get each base station power per beam + interf_tx_pow = bs_tx_power_per_beam_array[bs_interf] + # get UE RB index + ue_rb_beam_idx = int(self.bs_to_ue_beam_rbs[ui]) + # only overlapping RB interf power is considered + interf_tx_pow = interf_tx_pow[:, ue_rb_beam_idx] + # coupling loss already considers only the overlapping beam + # from BS + interference = interf_tx_pow - \ + self.coupling_loss_imt[bs_interf, ui] + + # sum 1e-50 so that rx_interference >= -500 + self.ue.rx_interference[ui] = 10 * np.log10( + np.sum(np.power(10, 0.1 * interference)) + 1e-50 + ) # Thermal noise in dBm self.ue.thermal_noise = \ @@ -210,176 +245,174 @@ def calculate_sinr_ext(self): # applying a bandwidth scaling factor since UE transmits on a portion # of the satellite's bandwidth - active_sys = np.where(self.system.active)[0] + ue_interferer_paths = self.paths_between_imt_and_sys.mask.T # 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), - ) + for ue in self.link[bs]: + # NOTE: ue is a scalar + system_interfering = np.where(ue_interferer_paths[ue])[0] + + # 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), + ) - 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 - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - category=RuntimeWarning, - message="divide by zero encountered in log10", - ) - 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 = np.resize(-500., (len(ue), 1)) - 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 ^ - - if self.parameters.imt.adjacent_ch_reception == "ACS": - if self.overlapping_bandwidth: - if getattr(self, "_acs_warned"): - warn( - "You're trying to use ACS on a partially overlapping band " - "with UEs.\n\tVerify the code implements the behavior you expect!!" + in_band_interf_power = np.array(-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 + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=RuntimeWarning, + message="divide by zero encountered in log10", ) - self._acs_warned = True - non_overlap_sys_bw = self.param_system.bandwidth - self.overlapping_bandwidth - acs_dB = self.parameters.imt.ue.adjacent_ch_selectivity - - # NOTE: only the power not overlapping is attenuated by ACS - # tx_pow_adj_lin = PSD * non_overlap_imt_bw - # rx_oob = tx_pow_adj_lin / acs - rx_oob[::] = self.param_system.tx_power_density + 10 * np.log10(non_overlap_sys_bw * 1e6) - acs_dB - 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] - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", - category=RuntimeWarning, - message="divide by zero encountered in log10") - for i, center_freq, bw in zip( - range(len(center_freqs)), center_freqs, ue_bws): - # calculate tx emissions in UE in use bandwidth only - # [dB] - tx_oob[i] = self.system.spectral_mask.power_calc( - center_freq, - bw + in_band_interf_power = \ + self.param_system.tx_power_density + 10 * np.log10( + self.ue.bandwidth[ue] * 1e6 + ) + 10 * np.log10(weights) - \ + self.coupling_loss_imt_system[ue, :][system_interfering] + + oob_power = np.resize(-500., 1) + 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., 1) + + # 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., 1) + + # 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 ^ + + if self.parameters.imt.adjacent_ch_reception == "ACS": + if self.overlapping_bandwidth: + if getattr(self, "_acs_warned"): + warn( + "You're trying to use ACS on a partially overlapping band " + "with UEs.\n\tVerify the code implements the behavior you expect!!" + ) + self._acs_warned = True + non_overlap_sys_bw = self.param_system.bandwidth - self.overlapping_bandwidth + acs_dB = self.parameters.imt.ue.adjacent_ch_selectivity + + # NOTE: only the power not overlapping is attenuated by ACS + # tx_pow_adj_lin = PSD * non_overlap_imt_bw + # rx_oob = tx_pow_adj_lin / acs + rx_oob[::] = self.param_system.tx_power_density + 10 * np.log10(non_overlap_sys_bw * 1e6) - acs_dB + 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] + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", + category=RuntimeWarning, + message="divide by zero encountered in log10") + tx_oob[0] = self.system.spectral_mask.power_calc( + center_freqs, + ue_bws ) - 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 - non_overlap_imt_bw = self.ue.bandwidth[ue] * (1. - weights) - # NOTE: approximated equal to IMT bw - measurement_bw = self.param_system.bandwidth - aclr_dB = self.param_system.adjacent_ch_leak_ratio - - if self.parameters.imt.bandwidth - self.overlapping_bandwidth > measurement_bw: - # NOTE: ACLR defines total leaked power over a fixed measurement bandwidth. - # If the victim bandwidth is wider, you’re assuming the same leakage - # profile extends beyond the ACLR-defined region, which may overestimate interference - # FIXME: if the victim bw fully contains tx bw, then - # EACH region should be <= measurement_bw - warn( - "Using System ACLR into IMT, but ACLR measurement bw is " - f"{measurement_bw} while the IMT bw is bigger ({self.parameters.imt.bandwidth}).\n" - "Are you sure you intend to apply the same ACLR to the entire IMT bw?" - ) - - # tx_oob_in_measurement = (tx_pow_lin / aclr) - # => approx. PSD = (tx_pow_lin / aclr) / measurement_bw - # approximated received tx_oob = PSD * non_overlap_imt_bw - tx_oob[::] = self.param_system.tx_power_density + \ - 10 * np.log10(1e6) - \ - aclr_dB + 10 * np.log10( - non_overlap_imt_bw) - 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}") - - if self.param_system.adjacent_ch_emissions != "OFF": - tx_oob = tx_oob[:, np.newaxis] - self.coupling_loss_imt_system[ue, :][:, active_sys] - - rx_oob = rx_oob[:, np.newaxis] - self.coupling_loss_imt_system_adjacent[ue, :][:, active_sys] + 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 + non_overlap_imt_bw = self.ue.bandwidth[ue] * (1. - weights) + # NOTE: approximated equal to IMT bw + measurement_bw = self.param_system.bandwidth + aclr_dB = self.param_system.adjacent_ch_leak_ratio + + if self.parameters.imt.bandwidth - self.overlapping_bandwidth > measurement_bw: + # NOTE: ACLR defines total leaked power over a fixed measurement bandwidth. + # If the victim bandwidth is wider, you’re assuming the same leakage + # profile extends beyond the ACLR-defined region, which may overestimate interference + # FIXME: if the victim bw fully contains tx bw, then + # EACH region should be <= measurement_bw + warn( + "Using System ACLR into IMT, but ACLR measurement bw is " + f"{measurement_bw} while the IMT bw is bigger ({self.parameters.imt.bandwidth}).\n" + "Are you sure you intend to apply the same ACLR to the entire IMT bw?" + ) - # 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) - ) - # 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)) + # tx_oob_in_measurement = (tx_pow_lin / aclr) + # => approx. PSD = (tx_pow_lin / aclr) / measurement_bw + # approximated received tx_oob = PSD * non_overlap_imt_bw + tx_oob[::] = self.param_system.tx_power_density + \ + 10 * np.log10(1e6) - \ + aclr_dB + 10 * np.log10( + non_overlap_imt_bw) + 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}") + + if self.param_system.adjacent_ch_emissions != "OFF": + tx_oob = tx_oob[:] - self.coupling_loss_imt_system[ue, :][system_interfering] + + rx_oob = rx_oob[:] - self.coupling_loss_imt_system_adjacent[ue, :][system_interfering] + + # 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) + ) + # 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)) - # 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 + # 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=0)) + 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])))) + 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 + # 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] + 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] + dist_sys_to_imt = self.system.geom.get_3d_distance_to( + self.ue.geom) # shape: [n_tx, n_ue] # EIRP in dBW/MHz per transmitter eirp_dBW_MHz = self.param_system.tx_power_density + \ @@ -396,7 +429,9 @@ def calculate_sinr_ext(self): 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], axis=0) + sys_active = np.where(self.system.active)[0] + # FIXME: consider only correct paths here + pfd_agg_linear = np.sum(pfd_linear[sys_active], axis=0) # Convert back to dBW self.ue.pfd_external_aggregated = 10 * np.log10(pfd_agg_linear) @@ -512,14 +547,15 @@ def calculate_external_interference(self): f"No implementation for self.param_system.adjacent_ch_reception == { self.param_system.adjacent_ch_reception}") - sys_active = np.where(self.system.active)[0] - if len(sys_active) > 1: + if self.system.num_stations > 1: raise NotImplementedError( "Implementation does not support victim system with more than 1 active station" ) rx_interference = 0 + bs_interferer_paths = self.paths_between_imt_and_sys.mask.T for bs in bs_active: + system_interfering = np.where(bs_interferer_paths[bs])[0] active_beams = [ i for i in range( bs * @@ -529,7 +565,7 @@ def calculate_external_interference(self): ] if self.co_channel: rx_interference += np.sum( - 10 ** (0.1 * (pow_coch - self.coupling_loss_imt_system[active_beams, sys_active])) + 10 ** (0.1 * (pow_coch - self.coupling_loss_imt_system[active_beams, system_interfering])) ) if self.adjacent_channel: @@ -537,7 +573,7 @@ def calculate_external_interference(self): # NOTE: we only consider one beam since all beams should have gain # of a single element for IMT, and as such the coupling loss should be the # same for all beams - adj_loss = self.coupling_loss_imt_system_adjacent[np.ix_(active_beams, sys_active)] + adj_loss = self.coupling_loss_imt_system_adjacent[np.ix_(active_beams, system_interfering)] # FIXME: for more than 1 sys # NOTE: sharc impl already doesn't really work with n_sys > 1 @@ -546,7 +582,7 @@ def calculate_external_interference(self): tx_oob_s = tx_oob - adj_loss[0, :] if self.param_system.adjacent_ch_reception != "OFF": - rx_oob_s = rx_oob - self.coupling_loss_imt_system[active_beams, sys_active] + rx_oob_s = rx_oob - self.coupling_loss_imt_system[active_beams, system_interfering] else: rx_oob_s = -np.inf @@ -608,8 +644,32 @@ def collect_results(self, write_to_file: bool, snapshot_number: int): "effective_area") and self.system.num_stations == 1: self.results.system_pfd.extend([self.system.pfd]) + if self.parameters.imt.interfered_with: + self.add_system_imt_interaction_attr_to_results( + "DL", + self.coupling_loss_imt_system.T, + "sys_to_imt_coupling_loss", + ) + self.add_system_imt_interaction_attr_to_results( + "DL", + self.ue.pfd_external, + "imt_dl_pfd_external", + ) + + self.add_system_imt_interaction_attr_to_results("DL", "system_imt_antenna_gain") + if len(self.imt_system_antenna_gain): + self.add_system_imt_interaction_attr_to_results("DL", "imt_system_antenna_gain") + + if len(self.imt_system_antenna_gain_adjacent): + self.add_system_imt_interaction_attr_to_results("DL", "imt_system_antenna_gain_adjacent") + + self.add_system_imt_interaction_attr_to_results("DL", "imt_system_path_loss") + if self.param_system.channel_model == "HDFSS": + self.add_system_imt_interaction_attr_to_results("DL", "imt_system_build_entry_loss") + self.add_system_imt_interaction_attr_to_results("DL", "imt_system_diffraction_loss") + 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] @@ -648,58 +708,9 @@ def collect_results(self, write_to_file: bool, snapshot_number: int): ) self.results.imt_dl_inr.extend(self.ue.inr[ue].tolist()) - 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(), - ) - if len(self.imt_system_antenna_gain): - self.results.imt_system_antenna_gain.extend( - self.imt_system_antenna_gain[sys_active[:, np.newaxis], ue].flatten(), - ) - if len(self.imt_system_antenna_gain_adjacent): - self.results.imt_system_antenna_gain_adjacent.extend( - self.imt_system_antenna_gain_adjacent[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[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], ue].flatten(), - ) - if len(self.imt_system_antenna_gain): - self.results.imt_system_antenna_gain.extend( - self.imt_system_antenna_gain[sys_active[:, np.newaxis], ue].flatten(), - ) - if len(self.imt_system_antenna_gain_adjacent): - self.results.imt_system_antenna_gain_adjacent.extend( - self.imt_system_antenna_gain_adjacent[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[:, 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()) if not self.parameters.imt.imt_dl_intra_sinr_calculation_disabled: diff --git a/sharc/simulation_uplink.py b/sharc/simulation_uplink.py index 2a4e48bbb..ebb307189 100644 --- a/sharc/simulation_uplink.py +++ b/sharc/simulation_uplink.py @@ -13,6 +13,7 @@ from sharc.parameters.parameters import Parameters from sharc.station_factory import StationFactory from sharc.parameters.constants import BOLTZMANN_CONSTANT +from sharc.propagation.propagation_path import PropagationPath warn = warnings.warn @@ -61,7 +62,7 @@ def snapshot(self, *args, **kwargs): # Create the other system (FSS, HAPS, etc...) self.system = StationFactory.generate_system( self.parameters, self.topology, random_number_gen, - geometry_converter=self.geometry_converter + coordinate_system=self.coordinate_system ) # Create IMT user equipments @@ -71,6 +72,22 @@ def snapshot(self, *args, **kwargs): self.parameters.imt.ue.antenna.array, self.topology, random_number_gen, ) + + if self.parameters.imt.interfered_with: + self.paths_between_imt_and_sys = PropagationPath.create_default( + self.system, + self.bs, + ) + else: + self.paths_between_imt_and_sys = PropagationPath.create_default( + self.system, + self.ue, + ) + + self.intra_imt_paths = PropagationPath.create_default( + self.ue, + self.bs, + ) # self.plot_scenario() self.connect_ue_to_bs() @@ -131,23 +148,22 @@ def calculate_sinr(self): """ # calculate uplink received power for each active BS bs_active = np.where(self.bs.active)[0] + bs_to_ue_path_mask = self.intra_imt_paths.mask.T 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] # create a list of BSs that serve the interfering UEs - bs_interf = [b for b in bs_active if b not in [bs]] + ue_interf = np.where(bs_to_ue_path_mask[bs])[0] + ue_interf = [ui for ui in ue_interf if ui not in ue] # 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_per_beam = self.ue.tx_power[ue_interf] - \ + self.coupling_loss_imt[bs, ue_interf] + self.bs.rx_interference[bs] = 10 * np.log10( + np.power(10, 0.1 * interference_per_beam), + ) # calculate N # thermal noise in dBm @@ -192,9 +208,10 @@ def calculate_sinr_ext(self): ) bs_active = np.where(self.bs.active)[0] - sys_active = np.where(self.system.active)[0] + bs_interferer_paths = self.paths_between_imt_and_sys.mask.T for bs in bs_active: + system_interfering = np.where(bs_interferer_paths[bs])[0] active_beams = [ i for i in range( bs * self.parameters.imt.ue.k, @@ -223,7 +240,7 @@ def calculate_sinr_ext(self): in_band_interf = self.param_system.tx_power_density + \ 10 * np.log10(beams_bw[:, np.newaxis] * 1e6) + \ 10 * np.log10(weights)[:, np.newaxis] - \ - self.coupling_loss_imt_system[active_beams, :][:, sys_active] + self.coupling_loss_imt_system[active_beams, :][:, system_interfering] in_band_interf_lin = 10 ** (in_band_interf / 10) oob_interf_lin = 0 @@ -306,10 +323,10 @@ def calculate_sinr_ext(self): if self.param_system.adjacent_ch_emissions != "OFF": # oob for system is inband for IMT - tx_oob = tx_oob[:, np.newaxis] - self.coupling_loss_imt_system[active_beams, :][:, sys_active] + tx_oob = tx_oob[:, np.newaxis] - self.coupling_loss_imt_system[active_beams, :][:, system_interfering] # oob for IMT - rx_oob = rx_oob[:, np.newaxis] - self.coupling_loss_imt_system_adjacent[active_beams, :][:, sys_active] + rx_oob = rx_oob[:, np.newaxis] - self.coupling_loss_imt_system_adjacent[active_beams, :][:, system_interfering] # Out of band power # sum linearly power leaked into band and power received in the @@ -357,111 +374,114 @@ def calculate_external_interference(self): rx_interference = 0 bs_active = np.where(self.bs.active)[0] - sys_active = np.where(self.system.active)[0] + ue_interf_from_sys_paths_mask = self.paths_between_imt_and_sys.mask.T for bs in bs_active: - ue = self.link[bs] - - if self.co_channel: - # TODO: test this in integration testing - weights = self.calculate_bw_weights( - self.ue.bandwidth[ue], - self.ue.center_freq[ue], - self.param_system.bandwidth, - self.param_system.frequency, - ) + ues = self.link[bs] + for ue in ues: + sys_victim = np.where(ue_interf_from_sys_paths_mask[ue])[0] + if len(sys_victim) == 0: + continue + + if self.co_channel: + # TODO: test this in integration testing + weights = self.calculate_bw_weights( + self.ue.bandwidth[ue], + self.ue.center_freq[ue], + self.param_system.bandwidth, + self.param_system.frequency, + ) - 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, - ), - ) + interference_ue = self.ue.tx_power[ue] - \ + self.coupling_loss_imt_system[ue, sys_victim] + rx_interference += np.sum( + weights * np.power( + 10, + 0.1 * interference_ue, + ), + ) - if self.adjacent_channel: - # These are in dB. Turn to zero linear. - tx_oob = -np.inf - rx_oob = -np.inf - # Calculate how much power is emitted in the adjacent channel: - if self.parameters.imt.adjacent_ch_emissions == "SPECTRAL_MASK": - # 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. - # TODO?: what is ue_power_diff - tx_oob = self.ue.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth) \ - - self.ue_power_diff[ue] \ - + self.parameters.imt.ue.ohmic_loss - - elif self.parameters.imt.adjacent_ch_emissions == "ACLR": - non_overlap_sys_bw = self.param_system.bandwidth - self.overlapping_bandwidth - # NOTE: approximated equal to IMT bw - measurement_bw = self.parameters.imt.bandwidth - aclr_dB = self.parameters.imt.ue.adjacent_ch_leak_ratio + if self.adjacent_channel: + # These are in dB. Turn to zero linear. + tx_oob = -np.inf + rx_oob = -np.inf + # Calculate how much power is emitted in the adjacent channel: + if self.parameters.imt.adjacent_ch_emissions == "SPECTRAL_MASK": + # 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. + # TODO?: what is ue_power_diff + tx_oob = self.ue.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth) \ + - self.ue_power_diff[ue] \ + + self.parameters.imt.ue.ohmic_loss + + elif self.parameters.imt.adjacent_ch_emissions == "ACLR": + non_overlap_sys_bw = self.param_system.bandwidth - self.overlapping_bandwidth + # NOTE: approximated equal to IMT bw + measurement_bw = self.parameters.imt.bandwidth + aclr_dB = self.parameters.imt.ue.adjacent_ch_leak_ratio + + if non_overlap_sys_bw > measurement_bw: + # NOTE: ACLR defines total leaked power over a fixed measurement bandwidth. + # If the victim bandwidth is wider, you’re assuming the same leakage + # profile extends beyond the ACLR-defined region, which may overestimate interference + # FIXME: if the victim bw fully contains tx bw, then + # EACH region should be <= measurement_bw + warn( + "Using IMT ACLR into system, but ACLR measurement bw is " + f"{measurement_bw} while the system bw is bigger ({non_overlap_sys_bw}).\n" + "Are you sure you intend to apply ACLR to the entire system bw?" + ) - if non_overlap_sys_bw > measurement_bw: - # NOTE: ACLR defines total leaked power over a fixed measurement bandwidth. - # If the victim bandwidth is wider, you’re assuming the same leakage - # profile extends beyond the ACLR-defined region, which may overestimate interference - # FIXME: if the victim bw fully contains tx bw, then - # EACH region should be <= measurement_bw - warn( - "Using IMT ACLR into system, but ACLR measurement bw is " - f"{measurement_bw} while the system bw is bigger ({non_overlap_sys_bw}).\n" - "Are you sure you intend to apply ACLR to the entire system bw?" + # tx_oob_in_measurement = (tx_pow_lin / aclr) + # => approx. PSD = (tx_pow_lin / aclr) / measurement_bw + # approximated received tx_oob = PSD * non_overlap_sys_bw + # NOTE: we don't get total power, but power per beam + # because later broadcast will sum this tx_oob `k` times + tx_oob = self.ue.tx_power[ue] - aclr_dB + 10 * np.log10( + non_overlap_sys_bw / measurement_bw + ) + elif self.parameters.imt.adjacent_ch_emissions == "OFF": + pass + else: + raise ValueError( + f"No implementation for self.parameters.imt.adjacent_ch_emissions == {self.parameters.imt.adjacent_ch_emissions}" ) - # tx_oob_in_measurement = (tx_pow_lin / aclr) - # => approx. PSD = (tx_pow_lin / aclr) / measurement_bw - # approximated received tx_oob = PSD * non_overlap_sys_bw - # NOTE: we don't get total power, but power per beam - # because later broadcast will sum this tx_oob `k` times - tx_oob = self.ue.tx_power[ue] - aclr_dB + 10 * np.log10( - non_overlap_sys_bw / measurement_bw - ) - elif self.parameters.imt.adjacent_ch_emissions == "OFF": - pass - else: - raise ValueError( - f"No implementation for self.parameters.imt.adjacent_ch_emissions == {self.parameters.imt.adjacent_ch_emissions}" - ) - - # Calculate how much power is received in the adjacent channel - if self.param_system.adjacent_ch_reception == "ACS": - non_overlap_imt_bw = self.parameters.imt.bandwidth - self.overlapping_bandwidth - tx_bw = self.parameters.imt.bandwidth - acs_dB = self.param_system.adjacent_ch_selectivity - - # NOTE: only the power not overlapping is attenuated by ACS - # PSD = tx_pow_lin / tx_bw - # tx_pow_adj_lin = PSD * non_overlap_imt_bw - # rx_oob = tx_pow_adj_lin / acs - rx_oob = self.ue.tx_power[ue] + 10 * np.log10( - non_overlap_imt_bw / tx_bw - ) - acs_dB - elif self.param_system.adjacent_ch_reception == "OFF": - if self.parameters.imt.adjacent_ch_emissions == "OFF": - raise ValueError("parameters.imt.adjacent_ch_emissions and parameters.imt.adjacent_ch_reception" - " cannot be both set to \"OFF\"") - pass - else: - raise ValueError( - f"No implementation for self.param_system.adjacent_ch_reception == {self.param_system.adjacent_ch_reception}" - ) + # Calculate how much power is received in the adjacent channel + if self.param_system.adjacent_ch_reception == "ACS": + non_overlap_imt_bw = self.parameters.imt.bandwidth - self.overlapping_bandwidth + tx_bw = self.parameters.imt.bandwidth + acs_dB = self.param_system.adjacent_ch_selectivity + + # NOTE: only the power not overlapping is attenuated by ACS + # PSD = tx_pow_lin / tx_bw + # tx_pow_adj_lin = PSD * non_overlap_imt_bw + # rx_oob = tx_pow_adj_lin / acs + rx_oob = self.ue.tx_power[ue] + 10 * np.log10( + non_overlap_imt_bw / tx_bw + ) - acs_dB + elif self.param_system.adjacent_ch_reception == "OFF": + if self.parameters.imt.adjacent_ch_emissions == "OFF": + raise ValueError("parameters.imt.adjacent_ch_emissions and parameters.imt.adjacent_ch_reception" + " cannot be both set to \"OFF\"") + else: + raise ValueError( + f"No implementation for self.param_system.adjacent_ch_reception == {self.param_system.adjacent_ch_reception}" + ) - # Out of band power - tx_oob -= self.coupling_loss_imt_system_adjacent[ue, sys_active] + # Out of band power + tx_oob -= self.coupling_loss_imt_system_adjacent[ue, sys_victim] - if self.param_system.adjacent_ch_reception != "OFF": - rx_oob -= self.coupling_loss_imt_system[ue, sys_active] - # Out of band power - # sum linearly power leaked into band and power received in the adjacent band - oob_power_lin = 10 ** (0.1 * tx_oob) + 10 ** (0.1 * rx_oob) + if self.param_system.adjacent_ch_reception != "OFF": + rx_oob -= self.coupling_loss_imt_system[ue, sys_victim] + # Out of band power + # sum linearly power leaked into band and power received in the adjacent band + oob_power_lin = 10 ** (0.1 * tx_oob) + 10 ** (0.1 * rx_oob) - rx_interference += np.sum( - oob_power_lin - ) + rx_interference += np.sum( + oob_power_lin + ) self.system.rx_interference = 10 * np.log10(rx_interference) # calculate N @@ -496,21 +516,36 @@ def collect_results(self, write_to_file: bool, snapshot_number: int): write_to_file (bool): Whether to write results to file. snapshot_number (int): The current snapshot number. """ + sys_active = np.where(self.system.active)[0] 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_inr.extend(self.system.inr[sys_active].tolist()) + # FIXME: self.system.rx_interference should not be a scalar 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)], + [self.system.rx_interference + - 10 * math.log10(self.system.bandwidth[sys_active])], ) # 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]) + self.results.system_pfd.extend([self.system.pfd[sys_active]]) + + self.add_system_imt_interaction_attr_to_results("UL", "system_imt_antenna_gain") + if len(self.imt_system_antenna_gain): + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_antenna_gain") + + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_path_loss") + + if len(self.imt_system_antenna_gain_adjacent): + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_antenna_gain_adjacent") + if self.param_system.channel_model == "HDFSS": + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_path_loss") + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_build_entry_loss") + self.add_system_imt_interaction_attr_to_results("UL", "imt_system_diffraction_loss") - 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] @@ -547,53 +582,6 @@ def collect_results(self, write_to_file: bool, snapshot_number: int): ) self.results.imt_ul_inr.extend(self.bs.inr[bs].tolist()) - 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(), - ) - if len(self.imt_system_antenna_gain_adjacent): - self.results.imt_system_antenna_gain_adjacent.extend( - self.imt_system_antenna_gain_adjacent[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[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)].flatten(), - ) - if len(self.imt_system_antenna_gain): - self.results.imt_system_antenna_gain.extend( - self.imt_system_antenna_gain[np.ix_(sys_active, ue)].flatten(), - ) - if len(self.imt_system_antenna_gain_adjacent): - self.results.imt_system_antenna_gain_adjacent.extend( - self.imt_system_antenna_gain_adjacent[np.ix_(sys_active, ue)].flatten(), - ) - self.results.imt_system_path_loss.extend( - self.imt_system_path_loss[np.ix_(sys_active, ue)].flatten(), - ) - if self.param_system.channel_model == "HDFSS": - 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]) / ( diff --git a/sharc/station.py b/sharc/station.py index 38c2b3940..b6c0936f7 100644 --- a/sharc/station.py +++ b/sharc/station.py @@ -17,9 +17,9 @@ def __init__(self): self.id = -1 self.x = 0 self.y = 0 + self.z = 0 self.azimuth = 0 self.elevation = 0 - self.height = 0 self.indoor = False self.active = False self.tx_power = 0 @@ -44,23 +44,14 @@ def __eq__(self, other): self.id == other.id and self.x == other.x and self.y == other.y and - self.height == other.height + self.z == other.z ) return equal else: return NotImplemented def __ne__(self, other): - if isinstance(other, self.__class__): - not_equal = ( - self.id != other.id or - self.x != other.x or - self.y != other.y or - self.height != other.height - ) - return not_equal - else: - return NotImplemented + return not self.__eq__(other) def __hash__(self): - return hash(self.id, self.x, self.y, self.height) + return hash(self.id, self.x, self.y, self.z) diff --git a/sharc/station_factory.py b/sharc/station_factory.py index 22c0bae14..44a11a8c4 100644 --- a/sharc/station_factory.py +++ b/sharc/station_factory.py @@ -10,6 +10,9 @@ import sys import math +from sharc.support.geometry import ( + DWNReferenceFrame, ENUReferenceFrame +) from sharc.support.enumerations import StationType from sharc.parameters.parameters import Parameters from sharc.parameters.imt.parameters_imt import ParametersImt @@ -44,12 +47,14 @@ 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_array import AntennaArray 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_f1245_fs import Antenna_f1245_fs from sharc.antenna.antenna_s1528 import AntennaS1528, AntennaS1528Leo, AntennaS1528Taylor from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt from sharc.topology.topology import Topology @@ -58,7 +63,7 @@ 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.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem from sharc.support.sharc_utils import wrap2_180 @@ -97,30 +102,38 @@ def generate_imt_base_stations( imt_base_stations = StationManager(num_bs) imt_base_stations.station_type = StationType.IMT_BS 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.geom.set_global_coords( + topology.space_station_x * np.ones(num_bs), + topology.space_station_y * np.ones(num_bs), + topology.space_station_z * np.ones(num_bs), + elev=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.geom.set_global_coords( + topology.space_station_x * np.ones(num_bs), + topology.space_station_y * np.ones(num_bs), + topology.space_station_z * np.ones(num_bs), + elev=topology.elevation + ) imt_base_stations.is_space_station = True else: - 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 topology.determines_local_geometry: + imt_base_stations.geom = topology.get_bs_geometry() + + imt_base_stations.geom.set_local_coords( + topology.x * np.ones(num_bs), + topology.y * np.ones(num_bs), + param.bs.height * np.ones(num_bs), + elev=-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.azimuth = wrap2_180(topology.azimuth) + imt_base_stations.geom.set_local_coords( + z=topology.height + ) + imt_base_stations.geom.set_local_coords( + azim=wrap2_180(topology.azimuth) + ) imt_base_stations.active = random_number_gen.rand( num_bs, ) < param.bs.load_probability @@ -155,10 +168,12 @@ def generate_imt_base_stations( num_bs, dtype=Antenna, ) + # TODO: transform BS to local coord system before creating antenna + imt_base_stations.antenna = AntennaFactory.create_n_antennas( param.bs.antenna, - imt_base_stations.azimuth, - imt_base_stations.elevation, + imt_base_stations.geom.pointn_azim_global, + imt_base_stations.geom.pointn_elev_global, num_bs ) @@ -195,9 +210,9 @@ def generate_imt_base_stations( f"Invalid IMT-BS spectral mask {param.spectral_mask}") if param.topology.type == 'MACROCELL': - imt_base_stations.intersite_dist = param.topology.macrocell.intersite_distance + imt_base_stations.geom.intersite_dist = param.topology.macrocell.intersite_distance elif param.topology.type == 'HOTSPOT': - imt_base_stations.intersite_dist = param.topology.hotspot.intersite_distance + imt_base_stations.geom.intersite_dist = param.topology.hotspot.intersite_distance return imt_base_stations @@ -291,13 +306,17 @@ def generate_imt_ue_outdoor( num_ue = num_bs * num_ue_per_bs imt_ue = StationManager(num_ue) + if topology.determines_local_geometry: + imt_ue.geom = topology.get_ue_geometry(param.ue.k) imt_ue.station_type = StationType.IMT_UE ue_x = list() ue_y = list() ue_z = list() - imt_ue.height = param.ue.height * np.ones(num_ue) + imt_ue.geom.set_local_coords( + z=param.ue.height * np.ones(num_ue) + ) # TODO: Sanitaze the azimuth_range parameter azimuth_range = param.ue.azimuth_range @@ -340,8 +359,10 @@ def generate_imt_ue_outdoor( np.arctan((param.bs.height - param.ue.height) / distance), ) - imt_ue.azimuth = (azimuth + theta + np.pi / 2) - imt_ue.elevation = elevation + psi + imt_ue.geom.set_local_coords( + azim=(azimuth + theta + np.pi / 2), + elev=elevation + psi, + ) elif param.ue.distribution_type.upper() == "ANGLE_AND_DISTANCE": # The Rayleigh and Normal distribution parameters (mean, scale and cutoff) @@ -407,6 +428,9 @@ def generate_imt_ue_outdoor( ) sys.exit(1) + ue_azims = np.zeros_like(imt_ue.geom.pointn_azim_global) + ue_elevs = np.zeros_like(imt_ue.geom.pointn_elev_global) + for bs in range(num_bs): idx = [ i for i in range( @@ -419,6 +443,7 @@ def generate_imt_ue_outdoor( x = radius[idx] * np.cos(np.radians(theta)) y = radius[idx] * np.sin(np.radians(theta)) z = np.zeros_like(x) + # TODO: move this to local coordinate system calc x, y, z = topology.transform_ue_xyz( bs, x, y, z ) @@ -427,7 +452,7 @@ def generate_imt_ue_outdoor( ue_z.extend(z) # calculate UE azimuth wrt serving BS - imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180) % 360 + ue_azims[idx] = (azimuth[idx] + theta + 180) % 360 # calculate elevation angle # psi is the vertical angle of the UE wrt the serving BS @@ -437,7 +462,11 @@ def generate_imt_ue_outdoor( psi = np.degrees( np.arctan((param.bs.height - param.ue.height) / distance), ) - imt_ue.elevation[idx] = elevation[idx] + psi + ue_elevs[idx] = elevation[idx] + psi + imt_ue.geom.set_local_coords( + azim=ue_azims, + elev=ue_elevs, + ) else: sys.stderr.write( @@ -446,9 +475,11 @@ def generate_imt_ue_outdoor( ) 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.geom.set_local_coords( + np.array(ue_x), + np.array(ue_y), + np.array(ue_z) + param.ue.height, + ) imt_ue.active = np.zeros(num_ue, dtype=bool) imt_ue.indoor = random_number_gen.random_sample( @@ -461,8 +492,8 @@ def generate_imt_ue_outdoor( ue_param_ant.get_antenna_parameters() imt_ue.antenna = AntennaFactory.create_n_antennas( param.ue.antenna, - imt_ue.azimuth, - imt_ue.elevation, + imt_ue.geom.pointn_azim_global, + imt_ue.geom.pointn_elev_global, num_ue, ) @@ -495,9 +526,9 @@ def generate_imt_ue_outdoor( imt_ue.spectral_mask.set_mask() if param.topology.type == 'MACROCELL': - imt_ue.intersite_dist = param.topology.macrocell.intersite_distance + imt_ue.geom.intersite_dist = param.topology.macrocell.intersite_distance elif param.topology.type == 'HOTSPOT': - imt_ue.intersite_dist = param.topology.hotspot.intersite_distance + imt_ue.geom.intersite_dist = param.topology.hotspot.intersite_distance return imt_ue @@ -556,6 +587,8 @@ def generate_imt_ue_indoor( topology.b_d / math.sqrt(topology.ue_indoor_percent) - topology.b_d ) / 2 + ue_azims = np.zeros_like(imt_ue.geom.pointn_azim_global) + ue_elevs = np.zeros_like(imt_ue.geom.pointn_elev_global) for bs in range(num_bs): idx = [ i for i in range( @@ -603,7 +636,7 @@ def generate_imt_ue_indoor( ), ) # calculate UE azimuth wrt serving BS - imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180) % 360 + ue_azims[idx] = (azimuth[idx] + theta + 180) % 360 # calculate elevation angle # psi is the vertical angle of the UE wrt the serving BS @@ -613,7 +646,7 @@ def generate_imt_ue_indoor( psi = np.degrees( np.arctan((param.bs.height - param.ue.height) / distance), ) - imt_ue.elevation[idx] = elevation[idx] + psi + ue_elevs[idx] = elevation[idx] + psi # check if UE is indoor if bs % topology.num_cells == 0: @@ -629,9 +662,13 @@ def generate_imt_ue_indoor( (y < topology.y[bs] - topology.b_d / 2) imt_ue.indoor[idx] = ~ out - imt_ue.x = np.array(ue_x) - imt_ue.y = np.array(ue_y) - imt_ue.height = np.array(ue_z) + imt_ue.geom.set_local_coords( + np.array(ue_x), + np.array(ue_y), + np.array(ue_z), + np.array(ue_azims), + np.array(ue_elevs), + ) imt_ue.active = np.zeros(num_ue, dtype=bool) imt_ue.rx_interference = -500 * np.ones(num_ue) @@ -641,8 +678,8 @@ def generate_imt_ue_indoor( 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], + par, imt_ue.geom.pointn_azim_global[i], + imt_ue.geom.pointn_elev_global[i], ) # imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)] @@ -676,7 +713,7 @@ def generate_system( parameters: Parameters, topology: Topology, random_number_gen: np.random.RandomState, - geometry_converter=GeometryConverter() + coordinate_system=CoordinateSystem() ): """Generate the system based on the provided parameters and topology. @@ -688,8 +725,8 @@ def generate_system( Topology object containing station positions. random_number_gen : np.random.RandomState Random number generator instance. - geometry_converter : GeometryConverter, optional - Converter for coordinate transformations (default is GeometryConverter()). + coordinate_system : CoordinateSystem, optional + Converter for coordinate transformations (default is CoordinateSystem()). Returns ------- @@ -735,7 +772,7 @@ def generate_system( 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) + parameters.mss_d2d, random_number_gen, coordinate_system) else: sys.stderr.write( "ERROR\nInvalid system: " + @@ -746,15 +783,13 @@ def generate_system( @staticmethod def generate_single_space_station( param: ParametersSingleSpaceStation, - simplify_dist_to_y=True): + ): """Create a single space station (satellite) based on the provided parameters. Parameters ---------- param : ParametersSingleSpaceStation Parameters for the single space station. - simplify_dist_to_y : bool, optional - If True (default), places the satellite only on the y axis. Returns ------- @@ -765,76 +800,79 @@ def generate_single_space_station( 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) + coord_sys = CoordinateSystem() + coord_sys.set_reference( + param.geometry.es_lat_deg, + param.geometry.es_long_deg, + param.geometry.es_altitude, + ) + x, y, z = coord_sys.lla2enu( + param.geometry.location.fixed.lat_deg, + param.geometry.location.fixed.long_deg, + param.geometry.altitude, + ) + space_station.geom.set_global_coords( + np.atleast_1d(x), + np.atleast_1d(y), + np.atleast_1d(z), + ) if param.geometry.azimuth.type == "POINTING_AT_IMT": - space_station.azimuth = np.rad2deg( - np.arctan2(-space_station.y, -space_station.x)) + azim = np.rad2deg( + np.arctan2(-space_station.geom.y_global, -space_station.geom.x_global)) + elif param.geometry.azimuth.type == "POINTING_AT_LAT_LONG_ALT": + px, py, pz = coord_sys.lla2enu( + param.geometry.pointing_at_lat, + param.geometry.pointing_at_long, + param.geometry.pointing_at_alt, + ) + + azim = np.rad2deg( + np.arctan2(py - space_station.geom.y_global, px - space_station.geom.x_global)) elif param.geometry.azimuth.type == "FIXED": - space_station.azimuth = param.geometry.azimuth.fixed + azim = 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": + if param.geometry.elevation.type == "POINTING_AT_IMT": gnd_elev = np.rad2deg( np.arctan2( - space_station.height, + space_station.geom.z_global, 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 + space_station.geom.y_global * + space_station.geom.y_global + + space_station.geom.x_global * + space_station.geom.x_global))) + elev = -gnd_elev + elif param.geometry.elevation.type == "POINTING_AT_LAT_LONG_ALT": + px, py, pz = coord_sys.lla2enu( + param.geometry.pointing_at_lat, + param.geometry.pointing_at_long, + param.geometry.pointing_at_alt, + ) + dy = py - space_station.geom.y_global + dx = px - space_station.geom.x_global + dz = pz - space_station.geom.z_global + + gnd_elev = np.rad2deg( + np.arctan2( + dz, + np.sqrt(dy * dy + dx * dx))) + elev = gnd_elev + elif param.geometry.elevation.type == "FIXED": + elev = param.geometry.elevation.fixed else: raise ValueError( f"Did not recognize elevation type of { param.geometry.elevation.type}") + space_station.geom.set_global_coords( + azim=np.atleast_1d(azim), + elev=np.atleast_1d(elev), + ) + space_station.active = np.array([True]) space_station.tx_power = np.array( [param.tx_power_density + 10 * @@ -843,11 +881,10 @@ def generate_single_space_station( space_station.rx_interference = -500 space_station.antenna = np.array([ - AntennaFactory.create_antenna(param.antenna, space_station.azimuth[0], - space_station.elevation[0]) + AntennaFactory.create_antenna(param.antenna, space_station.geom.pointn_azim_global[0], + space_station.geom.pointn_elev_global[0]) ]) - space_station.z = space_station.height space_station.bandwidth = param.bandwidth space_station.noise_temperature = param.noise_temperature space_station.thermal_noise = -500 @@ -894,20 +931,22 @@ def generate_fss_space_station(param: ParametersFssSs): # rotate axis and calculate coordinates with origin at IMT system imt_lat_rad = param.earth_station_lat_deg * np.pi / 180. - fss_space_station.x = np.array( + 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([ + y = np.array([y1]) * 1000 + z = 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 = np.array([param.azimuth]) - fss_space_station.elevation = np.array([param.elevation]) + azim = np.array([param.azimuth]) + elev = np.array([param.elevation]) + fss_space_station.geom.set_global_coords( + x, y, z, + azim, elev + ) fss_space_station.active = np.array([True]) fss_space_station.tx_power = np.array( @@ -978,22 +1017,22 @@ def generate_fss_earth_station( fss_earth_station.station_type = StationType.FSS_ES if param.location.upper() == "FIXED": - fss_earth_station.x = np.array([param.x]) - fss_earth_station.y = np.array([param.y]) + x = np.array([param.x]) + y = np.array([param.y]) elif param.location.upper() == "CELL": 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) + x = np.array(x) + y = np.array(y) elif param.location.upper() == "NETWORK": 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) + x = np.array(x) + 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 @@ -1013,8 +1052,8 @@ def generate_fss_earth_station( 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 + x = np.array(dist_x) + y = np.array(dist_y) else: sys.stderr.write( "ERROR\nFSS-ES location type {} not supported".format( @@ -1022,19 +1061,27 @@ def generate_fss_earth_station( ) sys.exit(1) - fss_earth_station.z = np.array([param.height]) - fss_earth_station.height = np.array([param.height]) + z = np.array([param.height]) + + fss_earth_station.geom.set_global_coords( + x, y, z + ) if param.azimuth.upper() == "RANDOM": - fss_earth_station.azimuth = np.array( + azim = np.array( [random_number_gen.uniform(-180., 180.)]) else: - fss_earth_station.azimuth = np.array([float(param.azimuth)]) + azim = np.array([float(param.azimuth)]) elevation = random_number_gen.uniform( param.elevation_min, param.elevation_max, ) - fss_earth_station.elevation = np.array([elevation]) + elev = np.array([elevation]) + + fss_earth_station.geom.set_global_coords( + azim=azim, + elev=elev, + ) fss_earth_station.active = np.array([True]) fss_earth_station.tx_power = np.array( @@ -1104,10 +1151,10 @@ def generate_single_earth_station( match param.geometry.location.type: case "FIXED": - single_earth_station.x = np.array( + x = np.array( [param.geometry.location.fixed.x], ) - single_earth_station.y = np.array( + y = np.array( [param.geometry.location.fixed.y], ) case "CELL": @@ -1115,15 +1162,15 @@ def generate_single_earth_station( 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) + x = np.array(x) + 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) + x = np.array(x) + y = np.array(y) case "UNIFORM_DIST": # ES is randomly (uniform) created inside a circle of radius # equal to param.max_dist_to_bs @@ -1145,8 +1192,8 @@ def generate_single_earth_station( 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 + x = np.array(dist_x) + y = np.array(dist_y) case _: sys.stderr.write( "ERROR\nSingle-ES location type {} not supported".format( @@ -1154,8 +1201,11 @@ def generate_single_earth_station( ) sys.exit(1) - single_earth_station.z = np.array([param.geometry.height]) - single_earth_station.height = np.array([param.geometry.height]) + z = np.array([param.geometry.height]) + + single_earth_station.geom.set_global_coords( + x, y, z + ) if param.geometry.azimuth.type == "UNIFORM_DIST": if param.geometry.azimuth.uniform_dist.min < -180: @@ -1171,30 +1221,37 @@ def generate_single_earth_station( ), ) sys.exit(1) - single_earth_station.azimuth = np.array([ + azim = 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( + azim = np.array( [param.geometry.azimuth.fixed], ) if param.geometry.elevation.type == "UNIFORM_DIST": - single_earth_station.elevation = np.array([ + elev = 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( + elev = np.array( [param.geometry.elevation.fixed], ) + single_earth_station.geom.set_global_coords( + azim=azim, + elev=elev, + ) + single_earth_station.antenna = np.array([ AntennaFactory.create_antenna( - param.antenna, single_earth_station.azimuth, single_earth_station.elevation + param.antenna, + single_earth_station.geom.pointn_azim_global, + single_earth_station.geom.pointn_elev_global ) ]) @@ -1256,13 +1313,13 @@ def generate_fs_station(param: ParametersFs): 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.geom.set_global_coords( + np.array([param.x]), + np.array([param.y]), + np.array([param.height]), + np.array([param.azimuth]), + np.array([param.elevation]), + ) fs_station.active = np.array([True]) fs_station.tx_power = np.array( @@ -1275,6 +1332,8 @@ def generate_fs_station(param: ParametersFs): fs_station.antenna = np.array([AntennaOmni(param.antenna_gain)]) elif param.antenna_pattern == "ITU-R F.699": fs_station.antenna = np.array([AntennaF699(param)]) + elif param.antenna_pattern == "ITU-R F.1245_fs": + fs_station.antenna == np.array([Antenna_f1245_fs(param)]) else: sys.stderr.write( "ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern, @@ -1316,17 +1375,20 @@ def generate_haps( # h = (d/3)*math.sqrt(3)/2 # haps.x = np.array([0, 7*d/2, -d/2, -4*d, -7*d/2, d/2, 4*d]) # 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 = haps.z 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)) * \ + azim = 360 * random_number_gen.random_sample(num_haps) + elev = ((270 + elev_max) - (270 - elev_max)) * \ random_number_gen.random_sample(num_haps) + (270 - elev_max) + haps.geom.set_global_coords( + np.zeros(num_haps), + np.zeros(num_haps), + param.altitude * np.ones(num_haps), + azim, + elev, + ) + haps.active = np.ones(num_haps, dtype=bool) haps.antenna = np.empty(num_haps, dtype=Antenna) @@ -1371,27 +1433,31 @@ def generate_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]) + x = np.array([param.x]) + y = np.array([param.y]) + z = 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]) * \ + azim = 90 + (azimuth[1] - azimuth[0]) * \ random_number_gen.random_sample(num_rns) + azimuth[0] - rns.elevation = (elevation[1] - elevation[0]) * \ + elev = (elevation[1] - elevation[0]) * \ random_number_gen.random_sample(num_rns) + elevation[0] + rns.geom.set_global_coords( + x, y, z, + azim, elev, + ) + 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)], + [AntennaM1466(param.antenna_gain, rns.geom.pointn_azim_global, rns.geom.pointn_elev_global)], ) else: sys.stderr.write( @@ -1489,19 +1555,23 @@ def generate_space_station( # Elevation at ground (centre of the footprint) theta_grd_elev = 90 - incidence_angle - space_station.x = np.array([0]) - space_station.y = np.array( + x = np.array([0]) + y = np.array( [distance * math.cos(math.radians(theta_grd_elev))], ) - space_station.height = np.array( + z = 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 - space_station.azimuth = np.array([270]) - space_station.elevation = np.array([-theta_grd_elev]) + azim = np.array([270]) + elev = np.array([-theta_grd_elev]) + + space_station.geom.set_global_coords( + x, y, z, + azim, elev + ) space_station.active = np.array([True]) space_station.rx_interference = np.array([-500]) @@ -1560,13 +1630,17 @@ def generate_mss_ss(param_mss: ParametersMssSs): 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 + x = ntn_topology.space_station_x * np.ones(num_bs) + param_mss.x + y = ntn_topology.space_station_y * np.ones(num_bs) + param_mss.y + z = ntn_topology.space_station_z * np.ones(num_bs) + elev = ntn_topology.elevation + azim = ntn_topology.azimuth + mss_ss.geom.set_global_coords( + x, y, z, + azim, elev + ) + 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( @@ -1598,10 +1672,10 @@ def generate_mss_ss(param_mss: ParametersMssSs): 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) + elif param_mss.spectral_mask == "MSS": + mss_ss.spectral_mask = SpectralMaskMSS(param_mss.frequency, + param_mss.bandwidth, + param_mss.spurious_emissions) else: raise ValueError( f"Invalid or not implemented spectral mask - {param_mss.spectral_mask}") @@ -1618,7 +1692,7 @@ def generate_mss_ss(param_mss: ParametersMssSs): def generate_mss_d2d( params: ParametersMssD2d, random_number_gen: np.random.RandomState, - geometry_converter: GeometryConverter, + coordinate_system: CoordinateSystem, ): """ Generate the MSS D2D constellation with support for multiple orbits and base station visibility. @@ -1629,7 +1703,7 @@ def generate_mss_d2d( 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 + coordinate_system : CoordinateSystem A converter that has already set a reference for coordinates transformation Returns @@ -1637,11 +1711,11 @@ def generate_mss_d2d( StationManager A StationManager object containing satellite configurations and positions. """ - geometry_converter.validate() + coordinate_system.validate() # Initialize the StationManager for the MSS D2D system mss_d2d_values = TopologyImtMssDc.get_coordinates( - geometry_converter, + coordinate_system, params, random_number_gen, ) @@ -1679,45 +1753,86 @@ def generate_mss_d2d( 1e6) + 30 ) - # 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"] + # Configure satellite positions in the StationManager + x = mss_d2d_values["sat_x"] + y = mss_d2d_values["sat_y"] + z = mss_d2d_values["sat_z"] + elev = mss_d2d_values["sat_antenna_elev"] + azim = mss_d2d_values["sat_antenna_azim"] + global_ref = ENUReferenceFrame( + lat=coordinate_system.ref_lat, + lon=coordinate_system.ref_long, + alt=coordinate_system.ref_alt, + ) + mss_d2d.geom.setup( + mss_d2d.num_stations, True, + global_ref, + ) + mss_d2d.geom.set_local_reference_frame( + DWNReferenceFrame( + lat=mss_d2d_values["sat_lat"], + lon=mss_d2d_values["sat_lon"], + alt=mss_d2d_values["sat_alt"], + ) + ) + + mss_d2d.geom.set_global_coords( + x, y, z, + azim, elev, + ) 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 + while np.sum(mss_d2d.active) == 0: + 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 + while np.sum(mss_d2d.active) == 0: + # 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) - elif params.antenna_pattern == "MSS Adjacent": + if params.antenna.pattern == "ITU-R-S.1528-LEO": + antenna_pattern = AntennaS1528Leo(params.antenna.itu_r_s_1528) + elif params.antenna.pattern == "ITU-R-S.1528-Section1.2": + antenna_pattern = AntennaS1528(params.antenna.itu_r_s_1528) + elif params.antenna.pattern == "ITU-R-S.1528-Taylor": + antenna_pattern = AntennaS1528Taylor(params.antenna.itu_r_s_1528) + elif params.antenna.pattern == "ARRAY2": + pass + elif params.antenna.pattern == "MSS Adjacent": antenna_pattern = AntennaMSSAdjacent(params.frequency) else: raise ValueError( - "generate_mss_ss: Invalid antenna type: {param_mss.antenna_pattern}") + f"generate_mss_ss: Invalid antenna type: {params.antenna.pattern}") for i in range(mss_d2d.num_stations): + if params.antenna.pattern == "ARRAY2": + antenna_pattern = AntennaArray( + params.antenna.array, + mss_d2d.geom.global2local.take(i) + ) + antenna_pattern.add_beam( + mss_d2d.geom.pointn_azim_global[i], + 90. - mss_d2d.geom.pointn_elev_global[i], + ) + antenna_pattern.set_always_first_beam() + mss_d2d.antenna[i] = antenna_pattern + if params.antenna.pattern == "ARRAY2": + mss_d2d.geom.set_local_coords( + azim=np.zeros_like(mss_d2d.geom.pointn_azim_global), + elev=np.zeros_like(mss_d2d.geom.pointn_elev_global), + ) + return mss_d2d # Return the configured StationManager @staticmethod @@ -1856,13 +1971,13 @@ def get_random_position(num_stas: int, if __name__ == '__main__': rand_gen = np.random.RandomState(101) - geometry_converter = GeometryConverter() + coordinate_system = CoordinateSystem() # 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) + coordinate_system.set_reference(ref_lat, ref_long, ref_alt) from sharc.parameters.parameters_orbit import ParametersOrbit orbit = ParametersOrbit( @@ -1875,141 +1990,94 @@ def get_random_position(num_stas: int, apogee_alt_km=525 ) from sharc.parameters.imt.parameters_imt_mss_dc import ParametersImtMssDc - params = ParametersImtMssDc( + params_mss_dc = 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) + params_mss_dc.sat_is_active_if.conditions = ["MINIMUM_ELEVATION_FROM_ES"] + params_mss_dc.sat_is_active_if.minimum_elevation_from_es = 5.0 + + parameters = Parameters() + parameters.imt.topology.mss_dc = params_mss_dc + + parameters.imt.ue.k = 1 + parameters.imt.ue.k_m = 1 + parameters.imt.ue.azimuth_range = (-180, 180) + parameters.imt.ue.distribution_distance = "UNIFORM" + parameters.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + parameters.imt.ue.distribution_azimuth = "NORMAL" + parameters.imt.ue.height = 1.5 + parameters.imt.ue.indoor_percent = 0 + parameters.imt.bandwidth = 10 + parameters.imt.frequency = 10 + parameters.imt.ue.noise_figure = 0 + + parameters.imt.bs.antenna.array.downtilt = 0 + + parameters.imt.topology.sampling_from_spherical_grid.num_bs = 3 * 3 * 19 + # parameters.imt.topology.sampling_from_spherical_grid.num_bs = 3 * 3 * 19 * 7 + parameters.imt.topology.sampling_from_spherical_grid.max_ue_distance = 800 + parameters.imt.topology.sampling_from_spherical_grid.grid.transform_grid_randomly = True + parameters.imt.topology.sampling_from_spherical_grid.grid.cell_radius = 10e3 + parameters.imt.topology.sampling_from_spherical_grid.grid.grid_in_zone.type = "CIRCLE" + parameters.imt.topology.sampling_from_spherical_grid.grid.grid_in_zone.circle.center_lat = ref_lat + parameters.imt.topology.sampling_from_spherical_grid.grid.grid_in_zone.circle.center_lon = ref_long + parameters.imt.topology.sampling_from_spherical_grid.grid.grid_in_zone.circle.radius_km = 30 * 111 + + # parameters.imt.topology.type = "SAMPLING_FROM_SPHERICAL_GRID" + parameters.imt.topology.type = "MSS_DC" + parameters.imt.validate("station_factory_imt") + # print( + # "parameters.imt.topology.sampling_from_spherical_grid.grid.lon_lat_grid.shape", + # parameters.imt.topology.sampling_from_spherical_grid.grid.lon_lat_grid.shape + # ) + + from sharc.topology.topology_factory import TopologyFactory + topology = TopologyFactory.createTopology( + parameters, + coordinate_system + ) 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, + parameters.imt, + parameters.imt.ue.antenna.array, rand_gen, topology ) + imt_bs = StationFactory.generate_imt_base_stations( + parameters.imt, + parameters.imt.bs.antenna.array, + topology, + rand_gen, + ) + # imt_bs.geom.set_local_coords( + # azim=np.zeros_like(imt_bs.geom.pointn_azim_local) + # ) + # imt_bs.geom.set_global_coords( + # elev=np.zeros_like(imt_bs.geom.pointn_azim_local) + 90., + # z=np.zeros_like(imt_bs.geom.pointn_azim_local) + # ) from sharc.satellite.scripts.plot_globe import plot_globe_with_borders - fig = plot_globe_with_borders(True, geometry_converter, False) + fig = plot_globe_with_borders(True, coordinate_system, False) 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')) + from sharc.support.geometry import plot_geom + plot_geom(fig, imt_ue.geom) + plot_geom(fig, imt_bs.geom, {"marker": dict(size=2, color='blue', opacity=1)}, True) # 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 + # ref_x = imt_ue.geom.x_global[11] + # ref_y = imt_ue.geom.y_global[11] + # ref_z = imt_ue.geom.z_global[11] + # range_scale = 1000 range_scale = 5000 ref_x = 0 diff --git a/sharc/station_manager.py b/sharc/station_manager.py index 9ab7a9088..07ecd3ca6 100644 --- a/sharc/station_manager.py +++ b/sharc/station_manager.py @@ -11,6 +11,7 @@ from sharc.station import Station from sharc.antenna.antenna import Antenna from sharc.mask.spectral_mask import SpectralMask +from sharc.support.geometry import SimulatorGeometry class StationManager(object): @@ -22,12 +23,6 @@ class StationManager(object): def __init__(self, n): self.num_stations = 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) # station height above ground self.idx_orbit = np.empty(n) self.indoor = np.zeros(n, dtype=bool) self.active = np.ones(n, dtype=bool) @@ -53,7 +48,8 @@ def __init__(self, n): self.center_freq = np.empty(n) self.station_type = StationType.NONE self.is_space_station = False - self.intersite_dist = 0.0 + self.geom = SimulatorGeometry(n) + self.max_earth_sta_interf_distance = np.inf def get_station_list(self, id=None) -> list: """Return a list of Station objects for the given indices. @@ -90,12 +86,11 @@ def get_station(self, id) -> Station: """ station = 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] + station.x = self.geom.x_global[id] + station.y = self.geom.y_global[id] + station.z = self.geom.z_global[id] + station.azimuth = self.geom.pointn_azim_global[id] + station.elevation = self.geom.pointn_elev_global[id] station.indoor = self.indoor[id] station.active = self.active[id] station.tx_power = self.tx_power[id] @@ -115,229 +110,6 @@ def get_station(self, id) -> Station: station.station_type = self.station_type return station - def get_distance_to(self, station) -> np.array: - """Calculate the 2D distance between this manager's stations and another's. - - Parameters - ---------- - station : StationManager - StationManager to which the distance is calculated. - - Returns - ------- - np.array - 2D distance matrix 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), - ) - return distance - - def get_3d_distance_to(self, station) -> np.array: - """Calculate the 3D distance between this manager's stations and another's. - - Parameters - ---------- - station : StationManager - StationManager to which the distance is calculated. - - Returns - ------- - np.array - 3D distance matrix between stations. - """ - dx = np.subtract.outer(self.x, station.x).astype(np.float64) - dy = np.subtract.outer(self.y, station.y).astype(np.float64) - dz = np.subtract.outer(self.z, station.z).astype(np.float64) - np.square(dx, out=dx) - np.square(dy, out=dy) - np.square(dz, out=dz) - np.sqrt( - dx + dy + dz, - out=dx - ) - return dx - - def get_dist_angles_wrap_around(self, station) -> np.array: - """Calculate distances and angles using the wrap-around technique. - - Parameters - ---------- - station : StationManager - StationManager to which distances and angles are calculated. - - Returns - ------- - tuple - distance_2D (np.array): 2D distance between stations - distance_3D (np.array): 3D distance between stations - phi (np.array): azimuth of pointing vector to other stations - theta (np.array): elevation of pointing vector to other stations - """ - # Initialize variables - distance_3D = np.empty([self.num_stations, station.num_stations]) - 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, - ]) - - # 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), - ) - 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), - ) - - # 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)) - - return distance_2D, distance_3D, phi, theta - - def get_elevation(self, station) -> np.array: - """Calculate the elevation angle between this manager's stations and another's. - - Parameters - ---------- - station : StationManager - StationManager to which the elevation angle is calculated. - - Returns - ------- - np.array - Elevation angle matrix (degrees). - - Notes - ----- - This implementation is essentially the same as get_elevation_angle (free-space elevation angle), - despite the different matrix dimensions. The methods should be merged to reuse 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.z - self.z[i] - elevation[i] = np.degrees(np.arctan2(rel_z, distance)) - - return elevation - - def get_pointing_vector_to(self, station) -> tuple: - """Calculate the pointing vector (angles) with respect to another station. - - Parameters - ---------- - station : StationManager - The other StationManager to calculate the pointing vector to. - - Returns - ------- - tuple - phi, theta (phi is calculated with respect to x counter-clockwise and - theta is calculated with respect to z counter-clockwise). - """ - - # malloc - dx = (station.x - self.x[:, np.newaxis]).astype(np.float64) - dy = (station.y - self.y[:, np.newaxis]).astype(np.float64) - dz = (station.z - self.z[:, np.newaxis]).astype(np.float64) - - dist = self.get_3d_distance_to(station) - - # NOTE: doing in place calculations - phi = np.rad2deg(np.arctan2(dy, dx, out=dx), out=dx) - # delete reference dx - del dx - - # in place calculations - theta = np.rad2deg(np.arccos(np.clip(dz / dist, -1.0, 1.0, out=dz), out=dz), out=dz) - # delete reference dz - del dz - - return phi, theta - - def get_off_axis_angle(self, station) -> np.array: - """Calculate the off-axis angle between this manager's stations and another's. - - Parameters - ---------- - station : StationManager - The other StationManager to calculate the off-axis angle to. - - Returns - ------- - np.array - Off-axis angle matrix (degrees). - """ - Az, b = self.get_pointing_vector_to(station) - Az0 = self.azimuth - - a = 90 - self.elevation[:, np.newaxis] - C = Az0[:, np.newaxis] - Az - - 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: """Return whether this station manager represents IMT stations. @@ -372,7 +144,6 @@ def copy_active_stations(stations: StationManager) -> StationManager: 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] @@ -394,5 +165,5 @@ def copy_active_stations(stations: StationManager) -> StationManager: 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 + act_sta.intersite_dist = stations.geom.intersite_dist return act_sta diff --git a/sharc/support/geometry.py b/sharc/support/geometry.py new file mode 100644 index 000000000..98895f57f --- /dev/null +++ b/sharc/support/geometry.py @@ -0,0 +1,1104 @@ +# from sharc.support.sharc_geom import CoordinateSystem +# from sharc.satellite.utils.sat_utils import lla2ecef + +from sharc.satellite.ngso.constants import EARTH_RADIUS_M +from sharc.support.sharc_geom import cartesian_to_polar, polar_to_cartesian +import scipy +import numpy as np +from dataclasses import dataclass +from abc import ABC + + +def readonly_properties(*fields): + """ + Decorator to update 'field's to be readonly, + and creates the private '_field's for mutations + """ + def decorator(cls): + for field in fields: + private_name = f"_{field}" + + def getter(self, name=private_name): + return getattr(self, name) + setattr(cls, field, property(getter)) + return cls + return decorator + + +@readonly_properties( + "x_global", "y_global", "z_global", + "pointn_azim_global", "pointn_elev_global", + "num_geometries" +) +class GlobalGeometry(ABC): + """ + Abstract class defining global simulator geometry implementation. + """ + x_global: np.ndarray + y_global: np.ndarray + z_global: np.ndarray + pointn_azim_global: np.ndarray + pointn_elev_global: np.ndarray + + num_geometries: int + + intersite_dist: float + + def setup( + self, + num_geometries, + ): + """ + Initializes variables based on number of geometries + """ + self._x_global = np.empty(num_geometries) + self._y_global = np.empty(num_geometries) + self._z_global = np.empty(num_geometries) + self._pointn_azim_global = np.empty(num_geometries) + self._pointn_elev_global = np.empty(num_geometries) + + self._num_geometries = num_geometries + + def set_global_coords( + self, + x=None, + y=None, + z=None, + azim=None, + elev=None, + ): + """Set passed values to objects global coordinates. + If None is passed, attribute will not be changed. + """ + if x is not None: + self._x_global = x + if y is not None: + self._y_global = y + if z is not None: + self._z_global = z + if elev is not None: + self._pointn_elev_global = elev + if azim is not None: + self._pointn_azim_global = azim + + def get_global_distance_to(self, other: "GlobalGeometry") -> np.array: + """Calculate the 2D distance between this geometry and another + considering their global (x,y) + + Parameters + ---------- + other : GlobalGeometry + GlobalGeometry to which the distance is calculated. + + Returns + ------- + np.array + 2D distance matrix between others. + """ + distance = np.empty([self.num_geometries, other.num_geometries]) + for i in range(self.num_geometries): + distance[i] = np.sqrt( + np.power(self.x_global[i] - other.x_global, 2) + + np.power(self.y_global[i] - other.y_global, 2), + ) + return distance + + def get_3d_distance_to(self, other: "GlobalGeometry") -> np.array: + """Calculate the 3D distance between this manager's stations and another's. + + Parameters + ---------- + other : GlobalGeometry + GlobalGeometry to which the distance is calculated. + + Returns + ------- + np.array + 3D distance matrix between stations. + """ + dx = np.subtract.outer(self.x_global, other.x_global).astype(np.float64) + dy = np.subtract.outer(self.y_global, other.y_global).astype(np.float64) + dz = np.subtract.outer(self.z_global, other.z_global).astype(np.float64) + np.square(dx, out=dx) + np.square(dy, out=dy) + np.square(dz, out=dz) + np.sqrt( + dx + dy + dz, + out=dx + ) + return dx + + def get_global_dist_angles_wrap_around(self, other) -> np.array: + """Calculate distances and angles using the wrap-around technique. + + Parameters + ---------- + other : GlobalGeometry + GlobalGeometry to which distances and angles are calculated. + + Returns + ------- + tuple + distance_2D (np.array): 2D distance between stations + distance_3D (np.array): 3D distance between stations + phi (np.array): azimuth of pointing vector to other stations + theta (np.array): elevation of pointing vector to other stations + """ + if self.uses_local_coords or other.uses_local_coords: + raise NotImplementedError( + "Wrap around was not implemented for local coord sys" + ) + # Initialize variables + distance_3D = np.empty([self.num_geometries, other.num_geometries]) + distance_2D = np.inf * np.ones_like(distance_3D) + cluster_num = np.zeros_like(distance_3D, dtype=int) + + # Cluster coordinates + cluster_x = np.array([ + other.x_global, + other.x_global + 3.5 * self.intersite_dist, + other.x_global - 0.5 * self.intersite_dist, + other.x_global - 4.0 * self.intersite_dist, + other.x_global - 3.5 * self.intersite_dist, + other.x_global + 0.5 * self.intersite_dist, + other.x_global + 4.0 * self.intersite_dist, + ]) + + cluster_y = np.array([ + other.y_global, + other.y_global + 1.5 * + np.sqrt(3.0) * self.intersite_dist, + other.y_global + 2.5 * + np.sqrt(3.0) * self.intersite_dist, + other.y_global + 1.0 * + np.sqrt(3.0) * self.intersite_dist, + other.y_global - 1.5 * + np.sqrt(3.0) * self.intersite_dist, + other.y_global - 2.5 * + np.sqrt(3.0) * self.intersite_dist, + other.y_global - 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_global[:, np.newaxis], 2) + + np.power(y - self.y_global[:, 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(other.z_global - self.z_global[:, np.newaxis], 2), + ) + + # Calcualte pointing vector + point_vec_x = cluster_x[cluster_num, np.arange(other.num_geometries)] \ + - self.x_global[:, np.newaxis] + point_vec_y = cluster_y[cluster_num, np.arange(other.num_geometries)] \ + - self.y_global[:, np.newaxis] + point_vec_z = other.z_global - self.z_global[:, 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_global_elevation(self, other: "GlobalGeometry") -> np.array: + """Calculate the elevation angle between this manager's stations and another's. + + Parameters + ---------- + other : GlobalGeometry + GlobalGeometry to which the elevation angle is calculated. + + Returns + ------- + np.array + Elevation angle matrix (degrees). + """ + elevation = np.empty([self.num_geometries, other.num_geometries]) + + for i in range(self.num_geometries): + distance = np.sqrt( + np.power(self.x_global[i] - other.x_global, 2) + + np.power(self.y_global[i] - other.y_global, 2), + ) + rel_z = other.z_global - self.z_global[i] + elevation[i] = np.degrees(np.arctan2(rel_z, distance)) + + return elevation + + def get_global_pointing_vector_to(self, other: "GlobalGeometry") -> tuple: + """Calculate the pointing vector (angles) with respect to another other. + + Parameters + ---------- + other : GlobalGeometry + The other GlobalGeometry to calculate the pointing vector to. + + Returns + ------- + tuple + phi, theta (phi is calculated with respect to x counter-clockwise and + theta is calculated with respect to z counter-clockwise). + """ + + # malloc + dx = (other.x_global - self.x_global[:, np.newaxis]).astype(np.float64) + dy = (other.y_global - self.y_global[:, np.newaxis]).astype(np.float64) + dz = (other.z_global - self.z_global[:, np.newaxis]).astype(np.float64) + + dist = self.get_3d_distance_to(other) + + # NOTE: doing in place calculations + phi = np.rad2deg(np.arctan2(dy, dx, out=dx), out=dx) + # delete reference dx + del dx + + # in place calculations + theta = np.rad2deg(np.arccos(np.clip(dz / dist, -1.0, 1.0, out=dz), out=dz), out=dz) + # delete reference dz + del dz + + return phi, theta + + def get_off_axis_angle(self, other: "GlobalGeometry") -> np.array: + """Calculate the off-axis angle between this manager's stations and another's. + + Parameters + ---------- + other : GlobalGeometry + The other GlobalGeometry to calculate the off-axis angle to. + + Returns + ------- + np.array + Off-axis angle matrix (degrees). + """ + Az, b = self.get_global_pointing_vector_to(other) + Az0 = self.pointn_azim_global + + a = 90 - self.pointn_elev_global[:, np.newaxis] + C = Az0[:, np.newaxis] - Az + + 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 + + +@dataclass(frozen=True) +class RigidTransform: + """ + Defines a transformation of the type + A(X) = rot @ X + t + where it applies rotation to X and then translates the result. + """ + rot: scipy.spatial.transform.Rotation # N rotations + t: np.ndarray # (N, 3) + + def __post_init__(self): + if not isinstance(self.rot, scipy.spatial.transform.Rotation): + raise ValueError("rot must be a scipy Rotation") + + # NOTE: this throws if the rotation isn't defined as a batch rotation + n_rot = len(self.rot) + n_t = self.t.shape[0] + + if self.t.shape != (n_t, 3): + raise ValueError(f"Invalid transform shape t={n_t}") + + # if some is equal to one, broadcasting still works + if not (n_rot == n_t or n_rot == 1 or n_t == 1): + raise ValueError( + f"Incompatible RigidTransform shapes: rot={n_rot}, t={n_t}" + ) + self.t.flags.writeable = False + + @property + def N(self): + """Number of transforms represented""" + n_rot = len(self.rot) + n_t = self.t.shape[0] + return max(n_rot, n_t) + + def inv(self) -> "RigidTransform": + """Returns the inverse transform""" + rot_inv = self.rot.inv() + t_inv = -rot_inv.apply(self.t) + return RigidTransform(rot_inv, t_inv) + + def and_then(self, other: "RigidTransform") -> "RigidTransform": + """Apply self, then other (composition: other @ self).""" + return RigidTransform( + rot=other.rot * self.rot, + t=other.rot.apply(self.t) + other.t + ) + + def take(self, idx: int) -> "RigidTransform": + """Takes the idx-th transform from the batch""" + # NOTE: we slice instead of taking indice to maintain + # array shape structure for functional broadcasting + # and batch computing + return RigidTransform( + rot=self.rot[idx:idx + 1], + t=self.t[idx:idx + 1], + ) + + def apply_points(self, x: np.ndarray) -> np.ndarray: + """Applies transform to the points. + + Parameters + ---------- + x : np.ndarray + Array of input points of shape (N, 3). Each row is a 3D point in + Cartesian coordinates. + + Returns + ------- + np.ndarray + Array of transformed points of shape (N, 3), where ``N`` is the + number of transforms represented by this ``RigidTransform``. + """ + return self.apply_vectors(x) + self.t + + def apply_points_permutation(self, x: np.ndarray) -> np.ndarray: + """Applies transform to points with permutation broadcasting. + + Parameters + ---------- + x : np.ndarray + Array of input points of shape (M, 3). Each row is a 3D point in + Cartesian coordinates. + + Returns + ------- + np.ndarray + Array of transformed points of shape (N, M, 3), where ``N`` is the + number of transforms represented by this ``RigidTransform``. The + slice ``result[n, :, :]`` contains all ``M`` input points + transformed by the ``n``-th rigid transform. + """ + t = self.t # (N,3) + + return self.apply_vectors_permutation(x) + t[:, None, :] + + def apply_vectors(self, v: np.ndarray) -> np.ndarray: + """Applies transform rotation-only to the points. + + Parameters + ---------- + x : np.ndarray + Array of input points of shape (N, 3). Each row is a 3D point in + Cartesian coordinates. + + Returns + ------- + np.ndarray + Array of rotated points of shape (N, 3), where ``N`` is the + number of transforms represented by this ``RigidTransform``. + + Note + ---- + This only applies rotation, it ignores translation. It is useful for + transforming pointing vectors. + """ + v = np.atleast_2d(v) + assert v.ndim == 2 and v.shape[1] == 3 + M = v.shape[0] + assert self.N == 1 or M == 1 or M == self.N + + return self.rot.apply(v) + np.zeros_like(self.t) + + def apply_vectors_permutation(self, v: np.ndarray) -> np.ndarray: + """Applies rotation-only to points with permutation broadcasting. + + Parameters + ---------- + x : np.ndarray + Array of input points of shape (M, 3). Each row is a 3D point in + Cartesian coordinates. + + Returns + ------- + np.ndarray + Array of transformed points of shape (N, M, 3), where ``N`` is the + number of transforms represented by this ``RigidTransform``. The + slice ``result[n, :, :]`` contains all ``M`` input points + transformed by the ``n``-th rigid transform rotation. + + Note + ---- + This only applies rotation, it ignores translation. It is useful for + transforming pointing vectors. + """ + v = np.atleast_2d(v) + assert v.ndim == 2 and v.shape[1] == 3 + + # (N,3,3) + R = self.rot.as_matrix() + + # einsum: (N,3,3) . (M,3) -> (N,M,3) + return ( + np.einsum("nij,mj->nmi", R, v) + + np.zeros_like(self.t)[:, None, :] + ) + + +class ReferenceFrame(ABC): + """ + Defines a reference frame, which generates a rigid transform + between ecef and its own local coordinate system. + It should be noted that, while this may define axis position, + the bearing considered (azimuth, elevation) for calculations + CANNOT USE NAVIGATION CONVENTION. + """ + __slots__ = ('_from_ecef', '_to_ecef') + + @property + def from_ecef(self) -> RigidTransform: + """Returns the transform from ECEF to the local coordinate system.""" + return self._from_ecef + + @property + def to_ecef(self) -> RigidTransform: + """Returns the transform from the local coordinate system to ECEF.""" + return self._to_ecef + + +class ENUReferenceFrame(ReferenceFrame): + """ + Defines ENU reference frame. + + NOTE: this does not change bearing convention to azimuth being + angular distance from North. Azimuth is still from x axis towards y axis. + """ + __slots__ = ("_lat", "_lon", "_alt") + + def __init__( + self, + *, + lat: np.ndarray, + lon: np.ndarray, + alt: np.ndarray, + ): + lat = np.atleast_1d(lat) + lon = np.atleast_1d(lon) + alt = np.atleast_1d(alt) + if lat.shape != lon.shape or lat.shape != alt.shape: + raise ValueError("lat, lon, alt must have identical shapes") + + if lat.shape != (len(lat),): + raise ValueError( + "The lla shapes used must be one dimensional: (N,), but is" + f" {lat.shape}" + ) + + self._lat = np.copy(lat) + self._lon = np.copy(lon) + self._alt = np.copy(alt) + + self._from_ecef = self._compute_to_local() + self._to_ecef = self._from_ecef.inv() + + # freeze frame + self._lat.flags.writeable = False + self._lon.flags.writeable = False + self._alt.flags.writeable = False + + def _compute_to_local(self): + return self._compute_to_enu() + + def _compute_to_enu(self): + lat = self._lat + lon = self._lon + alt = self._alt + N = lat.shape[0] + + rotation_around_z = -lon - 90 + rotation_around_x = lat - 90 + + ecef2local_rot = scipy.spatial.transform.Rotation.from_euler( + 'zx', + np.stack([rotation_around_z, rotation_around_x], axis=-1), + degrees=True + ) + ecef2local_translation = np.zeros((N, 3)) + # NOTE: using constant earth radius works because of spherical Earth + ecef2local_translation[:, 2] = -(alt + EARTH_RADIUS_M) + ecef2local = RigidTransform( + ecef2local_rot, ecef2local_translation + ) + return ecef2local + + +class DWNReferenceFrame(ENUReferenceFrame): + """ + Defines DWN reference frame. x=Down, y=West, z=North + DWN is a custom reference frame for simplification of satellite's ref frame + useful for antenna usage. + """ + ENU2DWN_ROT = scipy.spatial.transform.Rotation.from_matrix( + np.array([[ + [0, 0, -1], + [-1, 0, 0], + [0, 1, 0], + ]]) + ) + + def _compute_to_local(self): + ecef2enu = self._compute_to_enu() + # basis change + # ENU <> x: east, y: north, z: up + # DWN <> x: down, y: west, z: north + # x2 = -z1, y2 = -x1, z2 = y + enu2dwn = RigidTransform(self.ENU2DWN_ROT, np.zeros((1, 3))) + + return ecef2enu.and_then(enu2dwn) + + +@readonly_properties( + "x_local", "y_local", "z_local", + "pointn_azim_local", "pointn_elev_local", + "global_reference_frame", "local_reference_frame" +) +class SimulatorGeometry(GlobalGeometry): + """ + Class with simplified coordinate system operations. + Just global and local conversion. + """ + # N = num of geometries + x_local: np.ndarray # (N,) + y_local: np.ndarray # (N,) + z_local: np.ndarray # (N,) + pointn_azim_local: np.ndarray # (N,) + pointn_elev_local: np.ndarray # (N,) + + local_reference_frame: ReferenceFrame + global_reference_frame: ReferenceFrame + + uses_local_coords: bool + + def __init__( + self, + num_geometries, + uses_local_coords=False, + global_cs: ReferenceFrame = None, + ): + """ + Initialize a geometry object with a global coordinate system + and defining how many local coordinate systems should exist + """ + self.setup( + num_geometries, + uses_local_coords, + global_cs, + ) + + def setup( + self, + num_geometries, + uses_local_coords=False, + global_cs: ReferenceFrame = None, + ): + """ + Initializes variables based on number of geometries considered + """ + super().setup(num_geometries) + + self._global_reference_frame = global_cs + self.uses_local_coords = True + + if not uses_local_coords: + self.uses_local_coords = False + self._x_local = self._x_global + self._y_local = self._y_global + self._z_local = self._z_global + self._pointn_azim_local = self._pointn_azim_global + self._pointn_elev_local = self._pointn_elev_global + return + elif global_cs is None: + raise ValueError( + "If there will be a local ref, global coord sys must be passed" + ) + + self._x_local = np.empty(num_geometries) + self._y_local = np.empty(num_geometries) + self._z_local = np.empty(num_geometries) + self._pointn_azim_local = np.empty(num_geometries) + self._pointn_elev_local = np.empty(num_geometries) + self._local_reference_frame = None + + def set_local_reference_frame( + self, frame: ReferenceFrame + ): + """ + Sets local coord system references and prepares + global<->local coordinate transformation + """ + if not self.uses_local_coords: + raise ValueError( + "Cannot set local reference frame when not using local coords" + ) + self._local_reference_frame = frame + + self._compute_global_local_transform() + + def set_global_coords( + self, + x=None, + y=None, + z=None, + azim=None, + elev=None, + ): + """Set passed values to objects global coordinates. If geometry does not have local reference + it will be assumed to be the same as global reference, with local == global + If None is passed, attribute will not be changed. + """ + super().set_global_coords( + x=x, + y=y, + z=z, + azim=azim, + elev=elev, + ) + + self._compute_local_from_global() + + def set_local_coords( + self, + x=None, + y=None, + z=None, + azim=None, + elev=None, + ): + """Set values to local coordinate values. If geometry does not have local reference + it will be assumed to be the same as global reference, with local == global + If None is passed, attribute will not be updated. + """ + if x is not None: + self._x_local = x + if y is not None: + self._y_local = y + if z is not None: + self._z_local = z + if elev is not None: + self._pointn_elev_local = elev + if azim is not None: + self._pointn_azim_local = azim + + self._compute_global_from_local() + + def _compute_global_from_local(self): + if not self.uses_local_coords: + self._x_global = self._x_local + self._y_global = self._y_local + self._z_global = self._z_local + self._pointn_elev_global = self._pointn_elev_local + self._pointn_azim_global = self._pointn_azim_local + return + + p_local = np.stack([self.x_local, self.y_local, self.z_local], axis=-1) + + p_global = self._local2global_points(p_local) + + # Store results + self._x_global = p_global[:, 0] + self._y_global = p_global[:, 1] + self._z_global = p_global[:, 2] + + r = 1 + # then get pointing vec + point_local = np.stack(polar_to_cartesian( + r, self.pointn_azim_local, self.pointn_elev_local), axis=-1) + + point_global_x, point_global_y, point_global_z = self._local2global_vectors( + point_local + ).T + + _, global_azimuth, global_elevation = cartesian_to_polar( + point_global_x, point_global_y, point_global_z) + + self._pointn_elev_global = global_elevation + self._pointn_azim_global = global_azimuth + + def _local2global_points( + self, p_local, + ): + """Receives points shaped as (N, 3) and applies local2global transform, + returning (N, 3), where N is the number of local coordinate systems. + """ + return self.local2global.apply_points(p_local) + + def _local2global_vectors( + self, p_local, + ): + """Receives a vector shaped as (N, 3) and applies local2global transform, + returning (N, 3), where N is the number of local coordinate systems. + Does not apply translations, just rotations + """ + return self.local2global.apply_vectors(p_local) + + def _local2global_points_permutation( + self, p_local, + ): + """Receives points as (M, 3), returning (N, M, 3), + where N is the number of local coordinate systems + """ + return self.local2global.apply_points_permutation(p_local) + + def _local2global_vectors_permutation( + self, p_local, + ): + """Receives points as (M, 3), returning (N, M, 3), + where N is the number of local coordinate systems. + Does not apply translations, just rotations + """ + return self.local2global.apply_vectors_permutation(p_local) + + def _global2local_points( + self, p_local, + ): + """Receives points shaped as (N, 3) and applies global2local transform, + returning (N, 3), where N is the number of local coordinate systems. + """ + return self.global2local.apply_points(p_local) + + def _global2local_vectors( + self, p_local, + ): + """Receives a vector shaped as (N, 3) and applies global2local transform, + returning (N, 3), where N is the number of local coordinate systems. + Does not apply translations, just rotations + """ + return self.global2local.apply_vectors(p_local) + + def _global2local_points_permutation( + self, p_local, + ): + """Receives points as (M, 3), returning (N, M, 3), + where N is the number of local coordinate systems + """ + return self.global2local.apply_points_permutation(p_local) + + def _global2local_vectors_permutation( + self, p_local, + ): + """Receives points as (M, 3), returning (N, M, 3), + where N is the number of local coordinate systems. + Does not apply translations, just rotations + """ + return self.global2local.apply_vectors_permutation(p_local) + + def _compute_local_from_global(self): + if not self.uses_local_coords: + self._x_local = self._x_global + self._y_local = self._y_global + self._z_local = self._z_global + self._pointn_elev_local = self._pointn_elev_global + self._pointn_azim_local = self._pointn_azim_global + return + + p_global = np.stack([self.x_global, self.y_global, self.z_global], axis=-1) + + p_local = self._global2local_points(p_global) + + # Store results + self._x_local = p_local[:, 0] + self._y_local = p_local[:, 1] + self._z_local = p_local[:, 2] + + r = 1 + # then get pointing vec + point_global = np.stack(polar_to_cartesian( + r, self.pointn_azim_global, self.pointn_elev_global), axis=-1) + + point_local_x, point_local_y, point_local_z = self._global2local_vectors( + point_global + ).T + + _, local_azimuth, local_elevation = cartesian_to_polar( + point_local_x, point_local_y, point_local_z) + + self._pointn_elev_local = local_elevation + self._pointn_azim_local = local_azimuth + + def _compute_global_local_transform(self): + # get ecef to local + self.global2local = ( + self.global_reference_frame + .to_ecef + .and_then( + self.local_reference_frame.from_ecef + ) + ) + self.local2global = ( + self.local_reference_frame + .to_ecef + .and_then( + self.global_reference_frame.from_ecef + ) + ) + + def get_local_distance_to( + self, + other: "SimulatorGeometry", + *, + return_z_dist=False + ) -> np.array: + """Calculate the 2D distance between this manager's stations and another's + considering this ones coordinate system + + Parameters + ---------- + station : StationManager + StationManager to which the distance is calculated. + + Returns + ------- + np.array + 2D distance matrix between stations. + """ + if not self.uses_local_coords: + return self.get_global_distance_to(other) + + p_global = np.stack([other.x_global, other.y_global, other.z_global], axis=-1) + other_local = self._global2local_points_permutation( + p_global, + ) + own_local = np.stack([self.x_local, self.y_local, np.zeros_like(self.z_local)], axis=-1) + + if return_z_dist: + z_dist = other_local[..., 2] - self.z_local[:, None] + other_local[..., 2] = 0. + + dist2d = np.sqrt(np.sum(np.power(other_local - own_local[:, None, :], 2), axis=-1)) + + if return_z_dist: + return dist2d, z_dist + return dist2d + + def get_local_elevation(self, other: "SimulatorGeometry") -> np.array: + """Calculate the elevation angle between this manager's stations and another's + considering this one's loca coordinate system + + Parameters + ---------- + other : GlobalAndLocalGeometry + GlobalAndLocalGeometry to which the elevation angle is calculated. + + Returns + ------- + np.array + Elevation angle matrix (degrees). + """ + if not self.uses_local_coords: + return self.get_global_elevation(other) + + dist2d, z_dist = self.get_local_distance_to(other, return_z_dist=True) + + return np.degrees(np.arctan2(z_dist, dist2d)) + + +def plot_geom( + fig: "go.Figure", + geom: SimulatorGeometry, + scatter_params: dict = {}, + plot_pointing=False, +): + """Adds a given SimulatorGeometry to a plotly figure + considering global coordinates + """ + import plotly.graph_objects as go + scatter_params = { + "mode": 'markers', + "marker": dict(size=1, color='red', opacity=1), + "showlegend": False, + **scatter_params + } + fig.add_trace( + go.Scatter3d( + x=geom.x_global, + y=geom.y_global, + z=geom.z_global, + **scatter_params + ) + ) + + if plot_pointing: + 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, + geom.pointn_azim_global, + geom.pointn_elev_global + ) + # Add arrow heads to the end of the boresight vectors + for x, y, z, bx, by, bz in zip(geom.x_global, + geom.y_global, + geom.z_global, + 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, + showlegend=False, + )) + for x, y, z, bx, by, bz in zip(geom.x_global, + geom.y_global, + geom.z_global, + 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), + showlegend=False, + )) + + +if __name__ == "__main__": + global_lla = (-14, -45, 1200) + global_reference_frame = ENUReferenceFrame( + lat=np.array([global_lla[0]]), + lon=np.array([global_lla[1]]), + alt=np.array([global_lla[2]]), + ) + # tg = SimulatorGeometry( + # 1, + # 1, + # global_reference_frame + # ) + # tg.set_local_reference_frame( + # np.array([-14]), + # np.array([-45]), + # np.array([1200]), + # ) + + from sharc.topology.topology_macrocell import TopologyMacrocell + rng = np.random.RandomState(seed=0xcaffe) + topology = TopologyMacrocell( + 3 * 111e3, 1 + ) + + topology.calculate_coordinates(rng) + + num_ue = 3 + + bs_geom = SimulatorGeometry( + topology.num_base_stations, + topology.num_base_stations, + global_reference_frame + ) + ue_geom = SimulatorGeometry( + num_ue * topology.num_base_stations, + num_ue * topology.num_base_stations, + global_reference_frame + ) + + bs_geom.set_local_reference_frame( + DWNReferenceFrame( + lat=np.repeat(11, topology.num_base_stations), + lon=np.repeat(-47, topology.num_base_stations), + alt=np.repeat(1200, topology.num_base_stations), + ) + ) + ue_geom.set_local_reference_frame( + DWNReferenceFrame( + lat=np.repeat(11, num_ue * topology.num_base_stations), + lon=np.repeat(-47, num_ue * topology.num_base_stations), + alt=np.repeat(1200, num_ue * topology.num_base_stations), + ) + ) + + bs_geom.set_local_coords( + topology.x, + topology.y, + topology.z, + ) + + from sharc.station_factory import StationFactory + ue_x, ue_y, ue_z, _, _ = StationFactory.get_random_position( + num_ue * topology.num_base_stations, + topology, + rng, + deterministic_cell=True + ) + ue_geom.set_local_coords( + np.array(ue_x), + np.array(ue_y), + np.array(ue_z), + ) + + from sharc.satellite.scripts.plot_globe import plot_globe_with_borders + from sharc.support.sharc_geom import CoordinateSystem + + # for plotting + global_cs = CoordinateSystem() + global_cs.set_reference( + *global_lla + ) + fig = plot_globe_with_borders( + False, global_cs, False + ) + + import plotly.graph_objects as go + + plot_geom( + fig, + bs_geom, + dict( + marker=dict( + size=2, + color='blue', + opacity=1.0 + ), + name='BS' + ) + ) + plot_geom( + fig, + ue_geom, + dict( + marker=dict( + size=1, + color='red', + opacity=1.0 + ), + name='UE' + ) + ) + + fig.show() diff --git a/sharc/support/logging.yaml b/sharc/support/logging.yaml deleted file mode 100644 index 0e04fc413..000000000 --- a/sharc/support/logging.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -version: 1 -disable_existing_loggers: False -formatters: - simple: - format: "%(message)s" -# format: "%(asctime)s - %(name)s - %(message)s" - -handlers: - console: - class: logging.StreamHandler - level: INFO - formatter: simple - stream: ext://sys.stdout - - file: - class: logging.handlers.RotatingFileHandler - formatter: simple - filename: output/logfile.log - maxBytes: 10485760 # 10MB - backupCount: 20 - encoding: utf8 - - -root: - level: INFO - handlers: [console, file] diff --git a/sharc/support/sharc_geom.py b/sharc/support/sharc_geom.py index 54317dae4..1863438d6 100644 --- a/sharc/support/sharc_geom.py +++ b/sharc/support/sharc_geom.py @@ -6,7 +6,6 @@ import typing from sharc.satellite.utils.sat_utils import lla2ecef -from sharc.station_manager import StationManager from sharc.support.sharc_utils import to_scalar from sharc.satellite.ngso.constants import EARTH_RADIUS_M, EARTH_DEFAULT_CRS, EARTH_SPHERICAL_CRS @@ -175,15 +174,14 @@ def rotate_angles_based_on_new_nadir(elev, azim, nadir_elev, nadir_azim): # NOTE: this works for both spherical an ellipsoidal Earth, # just need to change ecef2lla and lla2ecef implementations -# TODO: refactor class and method names -class GeometryConverter(): +class CoordinateSystem(): """Class for transforming coordinates to local ENU using a reference lat, lon, alt. This class receives a reference lat, lon, alt and may transform other coordinate types to local ENU. """ def __init__(self): - """Initialize GeometryConverter with unset reference coordinates.""" + """Initialize CoordinateSystem with unset reference coordinates.""" # geodesical self.ref_lat = None self.ref_long = None @@ -280,7 +278,11 @@ def set_reference(self, ref_lat: float, ref_long: float, ref_alt: float): # can also be confirmed comparing to here: # https://gssc.esa.int/navipedia/index.php/Transformations_between_ECEF_and_ENU_coordinates - def convert_cartesian_to_transformed_cartesian( + # # pre calculating rotation matrices + # self.rotation_mtx = self.rotation.as_matrix() + # self.inv_rotation_mtx = self.rotation.inv().as_matrix() + + def ecef2enu( self, x, y, z, *, translate=None ): """Transform points by the same transformation required to bring reference to (0,0,0). @@ -314,7 +316,7 @@ def convert_cartesian_to_transformed_cartesian( # rotate so axis are same as ENU return self.rotation.apply(xyz).T - def revert_transformed_cartesian_to_cartesian( + def enu2ecef( self, x2, y2, z2, *, translate=None ): """Reverse transformed points by the same transformation required to bring reference to (0,0,0). @@ -350,7 +352,7 @@ def revert_transformed_cartesian_to_cartesian( # translate earth reference back to its original ecef coord return (xyz + translate_val[np.newaxis, :]).T - def convert_lla_to_transformed_cartesian( + def lla2enu( self, lat: np.array, long: np.array, alt: np.array ): """Convert latitude, longitude, altitude to transformed cartesian coordinates. @@ -375,38 +377,14 @@ def convert_lla_to_transformed_cartesian( # 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). + return self.ecef2enu(x, y, z) - Stations end up in the same relative position according to each other, adapting their angles to the rotation. - If idx is specified, only stations[idx] will be converted. - - Parameters - ---------- - station : StationManager - The station manager whose stations will be transformed. - idx : array-like or None, optional - Indices of stations to convert (default: all). + def angle_ecef2enu( + self, azim: np.ndarray, elev: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + """ + Receives pointing angles in ecef coordinates and transforms to ENU """ - # transform positions - 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( @@ -414,57 +392,20 @@ def convert_station_3d_to_2d( # 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 = self.ecef2enu( 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) + _, azimuth, elevation = cartesian_to_polar( + pointing_vec_x, pointing_vec_y, pointing_vec_z) - station.azimuth[idx] = azimuth - station.elevation[idx] = elevation + return azimuth, 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). - - Stations end up in the same relative position according to each other, adapting their angles to the rotation. - If idx is specified, only stations[idx] will be converted. - - Parameters - ---------- - station : StationManager - The station manager whose stations will be transformed. - idx : array-like or None, optional - Indices of stations to convert (default: all). + def angle_enu2ecef( + self, azim: np.ndarray, elev: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + """ + Receives pointing angles in ENU coordinates and transforms to ECEF """ - # transform positions - 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( @@ -472,26 +413,13 @@ def revert_station_2d_to_3d( # 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 = self.enu2ecef( 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) + _, azimuth, elevation = cartesian_to_polar( + pointing_vec_x, pointing_vec_y, pointing_vec_z) - station.azimuth[idx] = azimuth - station.elevation[idx] = elevation + return azimuth, elevation def get_lambert_equal_area_crs(polygon: shp.geometry.Polygon): @@ -517,7 +445,7 @@ def get_lambert_equal_area_crs(polygon: shp.geometry.Polygon): ) -def shrink_country_polygon_by_km( +def shrink_lonlat_polygon_by_km( polygon: shp.geometry.Polygon, km: float ) -> shp.geometry.Polygon: """Project a Polygon to Lambert Azimuthal Equal Area, shrink by km, and reproject back. @@ -540,8 +468,11 @@ def shrink_country_polygon_by_km( Notes ----- 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 + if (not self._polygon.is_valid + or self._polygon.is_empty + or self._polygon.area <= 0 + ): + raise Exception("bad polygon") """ # Lambert is more precise, but could prob. get UTM projection # Didn't see any practical difference for current use cases @@ -586,10 +517,10 @@ def shrink_countries_by_km( for ext_poly in countries: if ext_poly.geom_type == 'Polygon': - polys.append(shrink_country_polygon_by_km(ext_poly, km)) + polys.append(shrink_lonlat_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 + shrink_lonlat_polygon_by_km(poly, km) for poly in ext_poly.geoms ])) for poly in polys: @@ -789,13 +720,13 @@ def generate_grid_in_multipolygon( # print(get_rotation_matrix_between_vecs(np.array([0,1,0]), np.array([0,0,1]))) - geoconv = GeometryConverter() + coord_sys = CoordinateSystem() sys_lat = 89 sys_long = 0 sys_alt = 1200 - # geoconv.set_reference( + # coord_sys.set_reference( # sys_lat, sys_long, sys_alt # ) # stat = StationManager(1) @@ -814,7 +745,7 @@ def generate_grid_in_multipolygon( # print("stat.azimuth", stat.azimuth) # print("stat.elevation", stat.elevation) # print("#########") - # geoconv.convert_station_3d_to_2d(stat) + # coord_sys.station_ecef2enu(stat) # print("#########") # print("stat.x", stat.x) diff --git a/sharc/support/sharc_logger.py b/sharc/support/sharc_logger.py index c4771ec54..d3f73c9c2 100644 --- a/sharc/support/sharc_logger.py +++ b/sharc/support/sharc_logger.py @@ -2,33 +2,41 @@ import os import sys import yaml -import logging +import logging.config import subprocess from pathlib import Path from datetime import datetime -from typing import Optional - - -class Logging: - """Logging utility class for configuring application logging.""" - - @staticmethod - def setup_logging( - default_path="support/logging.yaml", - default_level=logging.INFO, - env_key="LOG_CFG", - ): - """Set up logging configuration for the application.""" - path = default_path - value = os.getenv(env_key, None) - if value: - path = value - if os.path.exists(path): - with open(path, "rt") as f: - config = yaml.safe_load(f.read()) - logging.config.dictConfig(config) - else: - logging.basicConfig(level=default_level) +from typing import Optional, List + +level_mapping = logging.getLevelNamesMapping() + + +def setup_logging(log_file=None, default_level="INFO"): + """Setup logging configuration for the root logger. + + Run this function in the beginning of the simulation to setup the root logger. + """ + + try: + level = level_mapping[default_level] + except KeyError: + raise ValueError("Invalid log level option {}".format(default_level)) + + root_logger = logging.getLogger() + root_logger.setLevel(level) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + root_logger.handlers = [] + + # Stream to stdout + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + root_logger.addHandler(handler) + + # Stream to file if specified + if log_file is not None: + file_handler = logging.FileHandler(log_file) + file_handler.setFormatter(formatter) + root_logger.addHandler(file_handler) class SimulationLogger: @@ -102,7 +110,7 @@ def _find_root_dir(self, folder_name: str) -> Optional[Path]: return parent return None - def _run_git_cmd(self, args: list[str]) -> Optional[str]: + def _run_git_cmd(self, args: List[str]) -> Optional[str]: try: return ( subprocess.check_output(["git"] + args, stderr=subprocess.DEVNULL) @@ -132,7 +140,7 @@ def _get_invocation_command(self) -> str: def _get_python_version(self) -> str: return sys.version.replace("\n", " ") - def _get_installed_packages(self) -> list[str]: + def _get_installed_packages(self) -> List[str]: try: output = subprocess.check_output( [sys.executable, "-m", "pip", "freeze"], stderr=subprocess.DEVNULL diff --git a/sharc/topology/topology.py b/sharc/topology/topology.py index 2cc41751a..813bb7f9d 100644 --- a/sharc/topology/topology.py +++ b/sharc/topology/topology.py @@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod import numpy as np import matplotlib.axes +from sharc.support.geometry import SimulatorGeometry class Topology(object): @@ -32,6 +33,7 @@ def __init__( self.azimuth = np.empty(0) self.indoor = np.empty(0) self.is_space_station = False + self.determines_local_geometry = False self.num_base_stations = -1 self.static_base_stations = False @@ -39,6 +41,18 @@ def __init__( def calculate_coordinates(self, random_number_gen=np.random.RandomState()): """Calculate the coordinates of the stations according to class attributes.""" + def get_ue_geometry(self, ue_k: int) -> SimulatorGeometry: + """Returns UE pre-built SimulatorGeometry if implemented + """ + if not self.determines_local_geometry: + raise ValueError("cannot get local UE geom if topology doesn't determines_local_geometry") + + def get_bs_geometry(self) -> SimulatorGeometry: + """Returns BS pre-built SimulatorGeometry if implemented + """ + if not self.determines_local_geometry: + raise ValueError("cannot get local BS geom if topology doesn't determines_local_geometry") + # by default, a sharc topology will translate the UE distribution by the # BS position def transform_ue_xyz( diff --git a/sharc/topology/topology_factory.py b/sharc/topology/topology_factory.py index 646df3934..03da29d23 100644 --- a/sharc/topology/topology_factory.py +++ b/sharc/topology/topology_factory.py @@ -13,8 +13,9 @@ 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.topology.topology_spherical_sampling_from_grid import TopologySamplingFromSphericalGrid from sharc.parameters.parameters import Parameters -from sharc.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem class TopologyFactory(object): @@ -22,7 +23,7 @@ class TopologyFactory(object): @staticmethod def createTopology(parameters: Parameters, - geometry_converter: GeometryConverter) -> Topology: + coordinate_system: CoordinateSystem) -> Topology: """Create and return a topology object based on the provided parameters.""" if parameters.imt.topology.type == "SINGLE_BS": return TopologySingleBaseStation( @@ -55,7 +56,14 @@ def createTopology(parameters: Parameters, elif parameters.imt.topology.type == "MSS_DC": return TopologyImtMssDc( parameters.imt.topology.mss_dc, - geometry_converter + coordinate_system + ) + elif parameters.imt.topology.type == "SAMPLING_FROM_SPHERICAL_GRID": + return TopologySamplingFromSphericalGrid( + parameters.imt.topology.sampling_from_spherical_grid.max_ue_distance, + parameters.imt.topology.sampling_from_spherical_grid.num_bs, + (coordinate_system.ref_lat, coordinate_system.ref_long, coordinate_system.ref_alt), + parameters.imt.topology.sampling_from_spherical_grid.grid, ) else: sys.stderr.write( diff --git a/sharc/topology/topology_imt_mss_dc.py b/sharc/topology/topology_imt_mss_dc.py index 292f37b5f..337cbe43c 100644 --- a/sharc/topology/topology_imt_mss_dc.py +++ b/sharc/topology/topology_imt_mss_dc.py @@ -6,7 +6,7 @@ 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. +centered at the reference point defined in the CoordinateSystem 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. """ @@ -21,7 +21,7 @@ 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.support.sharc_geom import CoordinateSystem, 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 @@ -36,282 +36,286 @@ class TopologyImtMssDc(Topology): """ def __init__(self, params: ParametersImtMssDc, - geometry_converter: GeometryConverter): + coordinate_system: CoordinateSystem): """Initialize the IMT MSS-DC topology with parameters and geometry converter. Parameters ---------- params : ParametersImtMssDc Input parameters for the IMT MSS-DC topology. - geometry_converter : GeometryConverter - GeometryConverter object that converts the ECEF coordinate system to one - centered at GeometryConverter.reference. + coordinate_system : CoordinateSystem + CoordinateSystem object that converts the ECEF coordinate system to one + centered at CoordinateSystem.reference. """ + super().__init__( + # FIXME: generated UEs won't follow radius exactly + params.beam_radius * np.sqrt(3), + params.beam_radius + ) # 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.coordinate_system = coordinate_system 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 @staticmethod def get_coordinates( - geometry_converter: GeometryConverter, + coordinate_system: CoordinateSystem, orbit_params: ParametersImtMssDc, random_number_gen=np.random.RandomState(), ): """Compute the coordinates 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", "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", "beam_positioning"]') - - idx_orbit = np.zeros( - total_satellites, - dtype=int) # Add orbit index array - - # List to store indices of active satellites - active_satellite_idxs = [] + num_base_stations = 0 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": [], - "alt": []} - 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 - # whether to use only time as random variable - model_time_as_random_variable=param.model_time_as_random_variable, - t_min=param.t_min, - t_max=param.t_max, - ) - # 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'] - # Distance from Earth's center - r = np.sqrt(sx**2 + sy**2 + sz**2) - - # 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 - # Calculate elevation angles - elevations = np.degrees(np.arcsin(-sz / r)) - # Calculate azimuth angles - azimuths = np.degrees(np.arctan2(-sy, -sx)) - - # 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_positions["alt"].extend(pos_vec['alt']) - 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 - # Perigee altitude in kilometers - sat_height=pos_vec['alt'] * 1e3, - es_height=geometry_converter.ref_alt, + while num_base_stations == 0: + # 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", "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", "beam_positioning"]') + + idx_orbit = np.zeros( + total_satellites, + dtype=int) # Add orbit index array + + # List to store indices of active satellites + active_satellite_idxs = [] + + 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": [], + "alt": []} + 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 + # whether to use only time as random variable + model_time_as_random_variable=param.model_time_as_random_variable, + t_min=param.t_min, + t_max=param.t_max, ) - - # 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 "MINIMUM_ELEVATION_FROM_ES" not in orbit_params.sat_is_active_if.conditions: + # 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'] + # Distance from Earth's center + r = np.sqrt(sx**2 + sy**2 + sz**2) + + # 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 + # Calculate elevation angles + elevations = np.degrees(np.arcsin(-sz / r)) + # Calculate azimuth angles + azimuths = np.degrees(np.arctan2(-sy, -sx)) + + # 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_positions["alt"].extend(pos_vec['alt']) + 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 + coordinate_system.ref_lat, # Latitude of base station pos_vec['lat'], # Latitude of satellites - geometry_converter.ref_long, # Longitude of base station + coordinate_system.ref_long, # Longitude of base station pos_vec['lon'], # Longitude of satellites # Perigee altitude in kilometers sat_height=pos_vec['alt'] * 1e3, - es_height=geometry_converter.ref_alt, + es_height=coordinate_system.ref_alt, ) - # 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=EARTH_DEFAULT_CRS) - - # 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. - # Convert X-coordinates to meters - all_space_station_x = np.ravel(np.array(all_positions['sx'])) * 1e3 - # Convert Y-coordinates to meters - all_space_station_y = np.ravel(np.array(all_positions['sy'])) * 1e3 - # Convert Z-coordinates to meters - all_space_station_z = np.ravel(np.array(all_positions['sz'])) * 1e3 - all_elevation = np.ravel(np.array(all_elevations)) # Elevation angles - all_azimuth = np.ravel(np.array(all_azimuths)) # Azimuth angles - all_lat = np.ravel(np.array(all_positions['lat'])) - all_lon = np.ravel(np.array(all_positions['lon'])) - all_sat_altitude = np.ravel(np.array(all_positions['alt'])) * 1e3 - - total_active_satellites = len(active_satellite_idxs) - space_station_x = all_space_station_x[active_satellite_idxs] - space_station_y = all_space_station_y[active_satellite_idxs] - space_station_z = all_space_station_z[active_satellite_idxs] - elevation = all_elevation[active_satellite_idxs] - azimuth = all_azimuth[active_satellite_idxs] - lat = all_lat[active_satellite_idxs] - lon = all_lon[active_satellite_idxs] - sat_altitude = all_sat_altitude[active_satellite_idxs] - sat_altitude = all_sat_altitude[active_satellite_idxs] + # 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 "MINIMUM_ELEVATION_FROM_ES" not in orbit_params.sat_is_active_if.conditions: + # Calculate satellite visibility from base stations + elev_from_bs = calc_elevation( + coordinate_system.ref_lat, # Latitude of base station + pos_vec['lat'], # Latitude of satellites + coordinate_system.ref_long, # Longitude of base station + pos_vec['lon'], # Longitude of satellites + # Perigee altitude in kilometers + sat_height=pos_vec['alt'] * 1e3, + es_height=coordinate_system.ref_alt, + ) + + # 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=EARTH_DEFAULT_CRS) + + # 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. + # Convert X-coordinates to meters + all_space_station_x = np.ravel(np.array(all_positions['sx'])) * 1e3 + # Convert Y-coordinates to meters + all_space_station_y = np.ravel(np.array(all_positions['sy'])) * 1e3 + # Convert Z-coordinates to meters + all_space_station_z = np.ravel(np.array(all_positions['sz'])) * 1e3 + all_elevation = np.ravel(np.array(all_elevations)) # Elevation angles + all_azimuth = np.ravel(np.array(all_azimuths)) # Azimuth angles + all_lat = np.ravel(np.array(all_positions['lat'])) + all_lon = np.ravel(np.array(all_positions['lon'])) + all_sat_altitude = np.ravel(np.array(all_positions['alt'])) * 1e3 + + total_active_satellites = len(active_satellite_idxs) + space_station_x = all_space_station_x[active_satellite_idxs] + space_station_y = all_space_station_y[active_satellite_idxs] + space_station_z = all_space_station_z[active_satellite_idxs] + elevation = all_elevation[active_satellite_idxs] + azimuth = all_azimuth[active_satellite_idxs] + lat = all_lat[active_satellite_idxs] + lon = all_lon[active_satellite_idxs] + sat_altitude = all_sat_altitude[active_satellite_idxs] + sat_altitude = all_sat_altitude[active_satellite_idxs] + + # 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 = \ + coordinate_system.ecef2enu(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, all_azimuth, all_elevation) + pointing_vec_x, pointing_vec_y, pointing_vec_z = \ + coordinate_system.ecef2enu( + pointing_vec_x, pointing_vec_y, pointing_vec_z, translate=0) + _, all_azimuth, all_elevation = cartesian_to_polar( + pointing_vec_x, pointing_vec_y, pointing_vec_z) - # 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, all_azimuth, all_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) - _, all_azimuth, all_elevation = cartesian_to_polar( - pointing_vec_x, pointing_vec_y, pointing_vec_z) - - beams_elev, beams_azim, sx, sy = TopologyImtMssDc.get_satellite_pointing( - random_number_gen, - geometry_converter, - orbit_params, - total_active_satellites, - all_space_station_x, all_space_station_y, all_space_station_z, - all_azimuth, - all_elevation, - all_lat, all_lon, all_sat_altitude, - active_satellite_idxs - ) + beams_elev, beams_azim, sx, sy = TopologyImtMssDc.get_satellite_pointing( + random_number_gen, + coordinate_system, + orbit_params, + total_active_satellites, + all_space_station_x, all_space_station_y, all_space_station_z, + all_azimuth, + all_elevation, + all_lat, all_lon, all_sat_altitude, + active_satellite_idxs + ) - # In SHARC each sector is treated as a separate base station, so we need to repeat the satellite positions - # for each sector. - sat_ocurr = [len(x) for x in beams_elev] - - elevation = np.array( - functools.reduce( - lambda x, - y: list(x) + - list(y), - beams_elev)) - azimuth = np.array( - functools.reduce( - lambda x, - y: list(x) + - list(y), - beams_azim)) - - space_station_x = np.repeat(space_station_x, sat_ocurr) - space_station_y = np.repeat(space_station_y, sat_ocurr) - space_station_z = np.repeat(space_station_z, sat_ocurr) - - num_base_stations = np.sum(sat_ocurr) - lat = np.repeat(lat, sat_ocurr) - lon = np.repeat(lon, sat_ocurr) - - altitudes = np.repeat(sat_altitude, sat_ocurr) + # In SHARC each sector is treated as a separate base station, so we need to repeat the satellite positions + # for each sector. + sat_ocurr = [len(x) for x in beams_elev] + + elevation = np.array( + functools.reduce( + lambda x, + y: list(x) + + list(y), + beams_elev)) + azimuth = np.array( + functools.reduce( + lambda x, + y: list(x) + + list(y), + beams_azim)) + + space_station_x = np.repeat(space_station_x, sat_ocurr) + space_station_y = np.repeat(space_station_y, sat_ocurr) + space_station_z = np.repeat(space_station_z, sat_ocurr) + + num_base_stations = np.sum(sat_ocurr) + lat = np.repeat(lat, sat_ocurr) + lon = np.repeat(lon, sat_ocurr) + + altitudes = np.repeat(sat_altitude, sat_ocurr) assert (space_station_x.shape == (num_base_stations,)) assert (space_station_y.shape == (num_base_stations,)) @@ -353,7 +357,7 @@ def get_coordinates( @staticmethod def get_satellite_pointing( random_number_gen: np.random.RandomState, - geometry_converter: GeometryConverter, + coordinate_system: CoordinateSystem, orbit_params: ParametersImtMssDc, total_active_satellites: int, all_sat_x: np.ndarray, @@ -376,7 +380,7 @@ def get_satellite_pointing( # TODO: remove this reset_grid call from here. # Since it should be called once per drop, it shouldn't # be this deep in the program - orbit_params.beam_positioning.service_grid.reset_grid("service_grid", random_number_gen) + orbit_params.beam_positioning.service_grid.reset_grid("service_grid", random_number_gen, True) # 2xN, (lon, lat) grid = orbit_params.beam_positioning.service_grid.lon_lat_grid @@ -407,7 +411,7 @@ def get_satellite_pointing( eligible_sats_msk &= polygon_mask eligible_sats_idx = np.where(eligible_sats_msk)[0] - elev = calc_elevation( + all_elevations = calc_elevation( grid_lat[:, np.newaxis], all_sat_lat[eligible_sats_msk][np.newaxis, :], grid_lon[:, np.newaxis], @@ -416,7 +420,7 @@ def get_satellite_pointing( es_height=0, ) - best_sats = elev.argmax(axis=-1) + best_sats = all_elevations.argmax(axis=-1) best_sats_true = eligible_sats_idx[best_sats] @@ -442,15 +446,19 @@ def get_satellite_pointing( pointing_vec_x, pointing_vec_y, pointing_vec_z = polar_to_cartesian( r, azim, elev) pointing_vec_x, pointing_vec_y, pointing_vec_z = \ - geometry_converter.convert_cartesian_to_transformed_cartesian( + coordinate_system.ecef2enu( pointing_vec_x, pointing_vec_y, pointing_vec_z, translate=0) _, azim, elev = cartesian_to_polar( pointing_vec_x, pointing_vec_y, pointing_vec_z) sat_points_towards = defaultdict(list) - for i, sat in enumerate(best_sats_true): - sat_points_towards[sat].append(i) + min_service_angle = orbit_params.beam_positioning.service_grid.minimum_service_angle + for i, sat in enumerate(best_sats): + if all_elevations[i][sat] < min_service_angle: + continue + actual_best_sat_idx = eligible_sats_idx[sat] + sat_points_towards[actual_best_sat_idx].append(i) # now only return the angles that # the caller asked with the active_sat_idxs parameter @@ -529,7 +537,7 @@ def get_satellite_pointing( raise ValueError( f"mss_d2d_params.beam_positioning.angle_from_subsatellite_theta.type = \n" f"'{ orbit_params.beam_positioning.angle_from_subsatellite_theta.type}' is not recognized!") - subsatellite_distance_add = sat_altitude * np.tan(off_nadir_add) + subsatellite_distance_add = sat_altitude * np.tan(np.deg2rad(off_nadir_add)) azim_add = TopologyImtMssDc.get_distr( random_number_gen, @@ -632,10 +640,10 @@ def get_distr( def calculate_coordinates(self, random_number_gen=np.random.RandomState()): """Compute the coordinates of the visible space stations.""" - self.geometry_converter.validate() + self.coordinate_system.validate() sat_values = self.get_coordinates( - self.geometry_converter, + self.coordinate_system, self.orbit_params, random_number_gen) @@ -685,7 +693,7 @@ def transform_ue_xyz(self, bs_i, x, y, z): 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()) + self.coordinate_system.get_translation()) # get around z axis around_z = np.arctan2( @@ -706,7 +714,7 @@ def transform_ue_xyz(self, bs_i, x, y, z): y = ny # translate ue back so other system is in (0,0,0) - z -= self.geometry_converter.get_translation() + z -= self.coordinate_system.get_translation() if convert_to_scalar: return (to_scalar(x), to_scalar(y), to_scalar(z)) @@ -716,7 +724,7 @@ def transform_ue_xyz(self, bs_i, 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 + from sharc.support.sharc_geom import CoordinateSystem # Define the parameters for the IMT MSS-DC topology # SystemA Orbit parameters @@ -752,11 +760,11 @@ def transform_ue_xyz(self, bs_i, x, y, z): params.validate("validation_at_main") # Define the geometry converter - geometry_converter = GeometryConverter() - geometry_converter.set_reference(-15.0, -42.0, 1200) + coordinate_system = CoordinateSystem() + coordinate_system.set_reference(-15.0, -42.0, 1200) # Instantiate the IMT MSS-DC topology - imt_mss_dc_topology = TopologyImtMssDc(params, geometry_converter) + imt_mss_dc_topology = TopologyImtMssDc(params, coordinate_system) # Calculate the coordinates of the space stations rng = np.random.RandomState(101) diff --git a/sharc/topology/topology_spherical_sampling_from_grid.py b/sharc/topology/topology_spherical_sampling_from_grid.py new file mode 100644 index 000000000..552b0f437 --- /dev/null +++ b/sharc/topology/topology_spherical_sampling_from_grid.py @@ -0,0 +1,201 @@ +from sharc.topology.topology import Topology +import numpy as np +from sharc.support.sharc_geom import CoordinateSystem +from sharc.support.geometry import SimulatorGeometry, ENUReferenceFrame +from sharc.parameters.imt.parameters_grid import ParametersTerrestrialGrid +# from sharc.satellite.utils.sat_utils import lla2ecef +# 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 TopologySamplingFromSphericalGrid(Topology): + """ + Class to generate and manage terrestrial networks distributed on a + grid situated on a spherical Earth. + This IMT topology is not meant for IMT TN as victim studies, since distributed + IMT will not have intra-IMT interference from close BS. + """ + + def __init__( + self, + max_ue_distance: float, + num_base_stations: int, + global_sim_lla_reference: tuple[float, float, float], + grid: ParametersTerrestrialGrid | np.ndarray, + ): + """ + Initializes a spherical topology with specific network settings. + + Parameters: + max_ue_distance: Radius of the coverage area for each site in meters. + num_base_stations: Number of base stations to sample + global_sim_lla_reference: (3,) tuple for lla of global coordinate system + grid: ParametersTerrestrialGrid for determining grid + """ + # intersite distance is needed for UE distribution, so we calculate it + intersite_dist = max_ue_distance * 3 / 2 + super().__init__(intersite_dist, max_ue_distance) + + self.determines_local_geometry = True + + self.num_base_stations = num_base_stations + self.is_space_station = False + + self.grid = grid + + # sistema de coord global + self.global_cs = global_sim_lla_reference + self.bs_geometry: SimulatorGeometry = None + + self.calculate_coordinates() + + def calculate_coordinates(self, random_number_gen=np.random.RandomState()): + """Compute and set the coordinates and angles for each base station. + + Parameters + ---------- + random_number_gen : np.random.RandomState, optional + Random number generator (not used in this implementation). + """ + if not isinstance(self.grid, np.ndarray): + self.grid.reset_grid( + "calculate_coords", + random_number_gen, + True + ) + lla_grid_to_sample = self.grid.lon_lat_grid[::-1] + else: + lla_grid_to_sample = self.grid + + # print("self.grid.lon_lat_grid.shape", self.grid.lon_lat_grid.shape) + # print("self.grid.lon_lat_grid.shape", self.grid.lon_lat_grid.shape) + # print("self.num_base_stations", self.num_base_stations) + if lla_grid_to_sample.shape[0] == 2: + default_alts = np.zeros((1, lla_grid_to_sample.shape[1])) + lla_grid_to_sample = np.concatenate((lla_grid_to_sample, default_alts)) + + # print("np.arange(lla_grid_to_sample.shape[0]).shape", np.arange(lla_grid_to_sample.shape[0]).shape) + chosen_idxs = random_number_gen.choice( + np.arange(lla_grid_to_sample.shape[1]), + size=self.num_base_stations, + replace=False + ) + + # (3, N) + chosen_llas = lla_grid_to_sample.T[chosen_idxs].T + + geom = SimulatorGeometry( + self.num_base_stations, + self.num_base_stations, + ENUReferenceFrame( + lat=self.global_cs[0], + lon=self.global_cs[1], + alt=self.global_cs[2], + ), + ) + lat, lon, alt = chosen_llas + self.chosen_lat, self.chosen_lon, self.chosen_alt = lat, lon, alt + # coords locais para determinar + # transformação global <> local + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=lat, + lon=lon, + alt=alt, + ) + ) + self.x = np.zeros(self.num_base_stations) + self.y = np.zeros(self.num_base_stations) + self.z = np.zeros(self.num_base_stations) + self.azimuth = random_number_gen.uniform(-180., 180., size=self.num_base_stations) + geom.set_local_coords( + self.x, + self.y, + self.z, + self.azimuth, + ) + + # local x,y,z + # self.x = None + # self.y = None + # self.z = None + self.bs_geometry = geom + + def get_bs_geometry(self) -> SimulatorGeometry: + """Returns BS pre-built SimulatorGeometry if implemented + """ + return self.bs_geometry + + def get_ue_geometry(self, ue_k: int) -> SimulatorGeometry: + """Returns UE pre-built SimulatorGeometry if implemented + """ + ue_geom = SimulatorGeometry( + self.num_base_stations * ue_k, + self.num_base_stations * ue_k, + self.bs_geometry.global_reference_frame, + ) + ue_geom.set_local_reference_frame( + ENUReferenceFrame( + lat=np.repeat(self.chosen_lat, ue_k), + lon=np.repeat(self.chosen_lon, ue_k), + alt=np.repeat(self.chosen_alt, ue_k), + ) + ) + return ue_geom + + def transform_ue_xyz(self, bs, x, y, z): + """Do not make any changes to ue position, let SimulatorGeometry take care of it""" + return x, y, z + + +# Example usage +if __name__ == '__main__': + global_lla = (-14, -45, 1200) + topology = TopologySamplingFromSphericalGrid( + 0, 3, + global_lla, + np.array([ + # [-1, -47, 400], + # [-3, -47, 400], + # [-5, -47, 400], + # [-7, -47, 400], + # [-9, -47, 400], + [11, -47, 400], + [22, -47, 400], + [-14, -45, 1200] + ]).T, + ) + + topology.calculate_coordinates() + + from sharc.satellite.scripts.plot_globe import plot_globe_with_borders + from sharc.support.sharc_geom import CoordinateSystem + + global_cs = CoordinateSystem() + global_cs.set_reference( + *global_lla + ) + fig = plot_globe_with_borders( + False, global_cs, False + ) + + import plotly.graph_objects as go + + fig.add_trace(go.Scatter3d( + x=topology.bs_geometry.x_global, + y=topology.bs_geometry.y_global, + z=topology.bs_geometry.z_global, + mode='markers', + marker=dict( + size=2, + color='blue', + opacity=1.0 + ), + name='Reference' + )) + + fig.show() diff --git a/tests/e2e/test_integration_imt_victim.py b/tests/e2e/test_integration_imt_victim.py index d5bc39945..a7f70e2b1 100644 --- a/tests/e2e/test_integration_imt_victim.py +++ b/tests/e2e/test_integration_imt_victim.py @@ -278,7 +278,7 @@ def test_es_to_ue_aclr_and_acs_partial_overlap(self): ) p_loss_1k = self.fspl.get_loss( - simulation_1k.system.get_3d_distance_to(simulation_1k.ue), + simulation_1k.system.geom.get_3d_distance_to(simulation_1k.ue.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) @@ -287,7 +287,7 @@ def test_es_to_ue_aclr_and_acs_partial_overlap(self): simulation_1k.results.imt_system_path_loss ) p_loss_3k = self.fspl.get_loss( - simulation_3k.system.get_3d_distance_to(simulation_3k.ue), + simulation_3k.system.geom.get_3d_distance_to(simulation_3k.ue.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) @@ -298,7 +298,7 @@ def test_es_to_ue_aclr_and_acs_partial_overlap(self): g1_co_1k = np.zeros((1, 2)) g1_adj_1k = np.zeros((1, 2)) - phis, thetas = simulation_1k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_1k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(2), phis, thetas): g1_co_1k[0][i] = simulation_1k.ue.antenna[i].calculate_gain( @@ -321,7 +321,7 @@ def test_es_to_ue_aclr_and_acs_partial_overlap(self): g1_co_3k = np.zeros((1, 6)) g1_adj_3k = np.zeros((1, 6)) - phis, thetas = simulation_3k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_3k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(6), phis, thetas): g1_co_3k[0][i] = simulation_3k.ue.antenna[i].calculate_gain( @@ -523,7 +523,7 @@ def test_es_to_bs_aclr_and_acs_partial_overlap(self): ) p_loss = self.fspl.get_loss( - simulation_1k.bs.get_3d_distance_to(simulation_1k.system), + simulation_1k.bs.geom.get_3d_distance_to(simulation_1k.system.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) @@ -770,7 +770,7 @@ def test_es_to_ue_mask(self): ) p_loss_1k = self.fspl.get_loss( - simulation_1k.system.get_3d_distance_to(simulation_1k.ue), + simulation_1k.system.geom.get_3d_distance_to(simulation_1k.ue.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) @@ -780,7 +780,7 @@ def test_es_to_ue_mask(self): ) p_loss_3k = self.fspl.get_loss( - simulation_3k.system.get_3d_distance_to(simulation_3k.ue), + simulation_3k.system.geom.get_3d_distance_to(simulation_3k.ue.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) @@ -790,7 +790,7 @@ def test_es_to_ue_mask(self): ) g1_co_1k = np.zeros((1, 2)) - phis, thetas = simulation_1k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_1k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(2), phis, thetas): g1_co_1k[0][i] = simulation_1k.ue.antenna[i].calculate_gain( @@ -806,7 +806,7 @@ def test_es_to_ue_mask(self): ) g1_co_3k = np.zeros((1, 6)) - phis, thetas = simulation_3k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_3k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(6), phis, thetas): g1_co_3k[0][i] = simulation_3k.ue.antenna[i].calculate_gain( @@ -915,7 +915,7 @@ def test_es_to_bs_mask(self): ) p_loss_1k = self.fspl.get_loss( - simulation_1k.system.get_3d_distance_to(simulation_1k.bs), + simulation_1k.system.geom.get_3d_distance_to(simulation_1k.bs.geom), # TODO: maybe should change this? np.array([self.param.single_earth_station.frequency]) ) diff --git a/tests/e2e/test_integration_sys_victim.py b/tests/e2e/test_integration_sys_victim.py index 717cccb56..aff046402 100644 --- a/tests/e2e/test_integration_sys_victim.py +++ b/tests/e2e/test_integration_sys_victim.py @@ -225,7 +225,7 @@ def test_2bs_to_es_mask(self): ) p_loss = self.fspl.get_loss( - simulation_1k.bs.get_3d_distance_to(simulation_1k.system), + simulation_1k.bs.geom.get_3d_distance_to(simulation_1k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -329,7 +329,7 @@ def test_2bs_to_es_aclr_and_acs_partial_overlap(self): ) p_loss = self.fspl.get_loss( - simulation_1k.bs.get_3d_distance_to(simulation_1k.system), + simulation_1k.bs.geom.get_3d_distance_to(simulation_1k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -523,7 +523,7 @@ def test_ue_to_es_mask(self): ) p_loss_1k = self.fspl.get_loss( - simulation_1k.ue.get_3d_distance_to(simulation_1k.system), + simulation_1k.ue.geom.get_3d_distance_to(simulation_1k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -532,7 +532,7 @@ def test_ue_to_es_mask(self): simulation_1k.results.imt_system_path_loss ) p_loss_3k = self.fspl.get_loss( - simulation_3k.ue.get_3d_distance_to(simulation_3k.system), + simulation_3k.ue.geom.get_3d_distance_to(simulation_3k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -640,7 +640,7 @@ def test_ue_to_es_aclr_and_acs_partial_overlap(self): ) p_loss_1k = self.fspl.get_loss( - simulation_1k.ue.get_3d_distance_to(simulation_1k.system), + simulation_1k.ue.geom.get_3d_distance_to(simulation_1k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -649,7 +649,7 @@ def test_ue_to_es_aclr_and_acs_partial_overlap(self): simulation_1k.results.imt_system_path_loss ) p_loss_3k = self.fspl.get_loss( - simulation_3k.ue.get_3d_distance_to(simulation_3k.system), + simulation_3k.ue.geom.get_3d_distance_to(simulation_3k.system.geom), # TODO: maybe should change this? np.array([self.param.imt.frequency]) ) @@ -660,7 +660,7 @@ def test_ue_to_es_aclr_and_acs_partial_overlap(self): g1_co_1k = np.zeros((1, 2)) g1_adj_1k = np.zeros((1, 2)) - phis, thetas = simulation_1k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_1k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(2), phis, thetas): g1_co_1k[0][i] = simulation_1k.ue.antenna[i].calculate_gain( @@ -683,7 +683,7 @@ def test_ue_to_es_aclr_and_acs_partial_overlap(self): g1_co_3k = np.zeros((1, 6)) g1_adj_3k = np.zeros((1, 6)) - phis, thetas = simulation_3k.ue.get_pointing_vector_to(simulation_1k.system) + phis, thetas = simulation_3k.ue.geom.get_global_pointing_vector_to(simulation_1k.system.geom) for i, phi, theta in zip(range(6), phis, thetas): g1_co_3k[0][i] = simulation_3k.ue.antenna[i].calculate_gain( diff --git a/tests/parameters/parameters_for_testing.yaml b/tests/parameters/parameters_for_testing.yaml index 20297abad..86631172b 100644 --- a/tests/parameters/parameters_for_testing.yaml +++ b/tests/parameters/parameters_for_testing.yaml @@ -232,21 +232,30 @@ imt: min: 0.0 max: 66.1 service_grid: + grid_exclusion_zone: + type: CIRCLE # may be "CIRCLE" or "FROM_COUNTRIES" + circle: + center_lat: -14.123 + center_lon: -47.1 + radius_km: 123 + transform_grid_randomly: true # add per drop rand rotation + transl. to grid # by default this already gets shapefile from natural earth - country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp - # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` - country_names: - - Brazil - - Chile - - # This margin defines where the grid is constructed - # margin from inside of border [km] - # By default, this is made to be the same as beam_radius - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - grid_margin_from_border: 0.11 + grid_in_zone: + type: FROM_COUNTRIES # may be "CIRCLE" or "FROM_COUNTRIES" + from_countries: + country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp + # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` + country_names: + - Brazil + - Chile + # This margin defines where the grid is constructed + # margin from inside of border [km] + # By default, this is made to be the same as beam_radius + # if positive, makes border smaller by x km + # if negative, makes border bigger by x km + margin_from_border: 0.11 # This margin defines what satellites may serve the grid # it should normally be a smaller value than the grid from border # since a satellite not right above the grid may serve some point better @@ -255,6 +264,7 @@ imt: # if positive, makes border smaller by x km # if negative, makes border bigger by x km eligible_sats_margin_from_border: -2.1 + minimum_service_angle: 10.1 sat_is_active_if: # for a satellite to be active, it needs to respect ALL conditions conditions: @@ -304,6 +314,30 @@ imt: # Defines the antenna model to be used in compatibility studies between # IMT and other services in adjacent band # Possible values: SINGLE_ELEMENT, BEAMFORMING + sampling_from_spherical_grid: + num_bs: 324 + max_ue_distance: 132 + grid: + cell_radius: 111 + grid_in_zone: + type: CIRCLE # may be "CIRCLE" or "FROM_COUNTRIES" + circle: + center_lat: 1 + center_lon: 2 + radius_km: 3 + from_countries: + country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp + # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` + country_names: + - Brazil + - Chile + + # This margin defines where the grid is constructed + # margin from inside of border [km] + # By default, this is made to be the same as beam_radius + # if positive, makes border smaller by x km + # if negative, makes border bigger by x km + margin_from_border: 0.11 adjacent_antenna_model : BEAMFORMING # Base station parameters bs: @@ -326,6 +360,8 @@ imt: ohmic_loss : 3.1 # Base Station Antenna parameters: antenna: + pattern: "ARRAY" + # pattern: ITU-R-S.1528-Taylor array: ########################################################################### # If normalization of M2101 should be applied for BS @@ -408,7 +444,19 @@ imt: element_vert_spacing: 0.05 # Sub array eletrical downtilt [deg] eletrical_downtilt: 9.0 - + gain: 34.1 + itu_r_s_1528: + ### 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 + frequency: 2177.0 + bandwidth: 6.0 ########################################################################### # User Equipment parameters: ue: @@ -884,6 +932,13 @@ single_space_station: ########################################################################### # earth station lat long [deg] es_long_deg: 3.9 + # Where to point antenna if azimuth|elevation use "POINTING_AT_LAT_LONG_ALT" + # latitude [deg] + pointing_at_lat: 12. + # longitude [deg] + pointing_at_long: -1. + # altitude [m] + pointing_at_alt: 123. ########################################################################### # Azimuth angle [degrees] azimuth: @@ -944,6 +999,9 @@ single_space_station: # Antenna diameter [m] # if no diameter is passed, a diameter will be assumed according to document diameter: 2.12 + itu_r_s_1528: + major_minor_axis_ratio: 1.0 + far_out_side_lobe: -25 ########################################################################### # Selected channel model channel_model: "P619" @@ -967,7 +1025,7 @@ mss_d2d: # Number of sectors num_sectors: 19 beam_positioning: - # type may be one of + # type may be one of # "ANGLE_FROM_SUBSATELLITE", "ANGLE_AND_DISTANCE_FROM_SUBSATELLITE", # "SERVICE_GRID" # when "ANGLE_FROM_SUBSATELLITE", both phi and theta must be specified @@ -1009,20 +1067,30 @@ mss_d2d: min: 0.0 max: 66.1 service_grid: + grid_exclusion_zone: + type: CIRCLE # may be "CIRCLE" or "FROM_COUNTRIES" + circle: + center_lat: -14.123 + center_lon: 120 + radius_km: 321 + transform_grid_randomly: true # add per drop rand rotation + transl. to grid - # by default this already gets shapefile from natural earth - country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp - # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` - country_names: - - Brazil - - Chile - # This margin defines where the grid is constructed - # margin from inside of border [km] - # By default, this is made to be the same as beam_radius - # if positive, makes border smaller by x km - # if negative, makes border bigger by x km - grid_margin_from_border: 0.11 + grid_in_zone: + type: FROM_COUNTRIES # may be "CIRCLE" or "FROM_COUNTRIES" + from_countries: + country_shapes_filename: sharc/data/countries/ne_110m_admin_0_countries.shp + # By default, these are the same as taken from `sat_is_active_if.lat_long_inside_country` + country_names: + - Brazil + - Chile + + # This margin defines where the grid is constructed + # margin from inside of border [km] + # By default, this is made to be the same as beam_radius + # if positive, makes border smaller by x km + # if negative, makes border bigger by x km + margin_from_border: 0.11 # This margin defines what satellites may serve the grid # it should normally be a smaller value than the grid from border @@ -1032,6 +1100,7 @@ mss_d2d: # if positive, makes border smaller by x km # if negative, makes border bigger by x km eligible_sats_margin_from_border: -2.1 + minimum_service_angle: 12.1 sat_is_active_if: # for a satellite to be active, it needs to respect ALL conditions conditions: @@ -1053,19 +1122,21 @@ mss_d2d: # 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 + antenna: + pattern: ITU-R-S.1528-Taylor + gain: 34.1 + itu_r_s_1528: + ### 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 + frequency: 2177.0 + bandwidth: 6.0 # channel model, possible values are "FSPL" (free-space path loss), # "SatelliteSimple" (FSPL + 4 + clutter loss) # "P619" diff --git a/tests/parameters/test_parameters.py b/tests/parameters/test_parameters.py index 697693d3f..e5fd967d2 100644 --- a/tests/parameters/test_parameters.py +++ b/tests/parameters/test_parameters.py @@ -171,6 +171,17 @@ def test_parameters_imt(self): self.assertEqual( self.parameters.imt.topology.central_longitude, -12.134) + # Now check S.1528 antenna parameters when used in DC-MSS-IMT + self.parameters.imt.bs.antenna.pattern = "ITU-R-S.1528-Taylor" + self.parameters.imt.bs.antenna.validate("test_imt_parameters") + self.assertEqual(self.parameters.imt.bs.antenna.gain, 34.1) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.frequency, 2177.0) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.bandwidth, 6.0) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.slr, 20) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.n_side_lobes, 2) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.l_r, 1.6) + self.assertEqual(self.parameters.imt.bs.antenna.itu_r_s_1528.l_t, 1.6) + """Test ParametersSubarrayImt """ # testing default value not enabled @@ -280,21 +291,37 @@ def test_parameters_imt(self): self.assertEqual( self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.beam_radius, 19000) + + self.assertEqual( + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_exclusion_zone.type, + "CIRCLE") + self.assertEqual( + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_exclusion_zone.circle.center_lat, + -14.123) + self.assertEqual( + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_exclusion_zone.circle.center_lon, + -47.1) + self.assertEqual( + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_exclusion_zone.circle.radius_km, + 123) + self.assertEqual( self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.transform_grid_randomly, True) self.assertEqual( - self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_margin_from_border, + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_in_zone.from_countries.margin_from_border, 0.11) self.assertEqual( self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.eligible_sats_margin_from_border, -2.1) + self.assertEqual( + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.minimum_service_angle, 10.1) self.assertEqual(len( - self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.country_names), 2) + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_in_zone.from_countries.country_names), 2) self.assertEqual( - self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.country_names[0], + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_in_zone.from_countries.country_names[0], "Brazil") self.assertEqual( - self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.country_names[1], + self.parameters.imt.topology.mss_dc.beam_positioning.service_grid.grid_in_zone.from_countries.country_names[1], "Chile") self.assertEqual(len( @@ -366,6 +393,39 @@ def test_parameters_imt(self): self.assertEqual( getattr(orbit_params, k), expected_orbit_params[i][k]) + """ + Test parameters spherical topology + """ + sampling_from_spherical_grid = self.parameters.imt.topology.sampling_from_spherical_grid + self.assertEqual( + sampling_from_spherical_grid.num_bs, + 324, + ) + self.assertEqual( + sampling_from_spherical_grid.grid.cell_radius, + 111, + ) + self.assertEqual( + sampling_from_spherical_grid.grid.grid_in_zone.type, + "CIRCLE", + ) + self.assertEqual( + sampling_from_spherical_grid.grid.grid_in_zone.circle.center_lat, + 1, + ) + self.assertEqual( + sampling_from_spherical_grid.grid.grid_in_zone.circle.center_lon, + 2, + ) + self.assertEqual( + sampling_from_spherical_grid.grid.grid_in_zone.circle.radius_km, + 3, + ) + + self.assertEqual( + sampling_from_spherical_grid.max_ue_distance, + 132, + ) def test_imt_validation(self): """ @@ -609,21 +669,17 @@ def test_parametes_mss_d2d(self): self.assertEqual(self.parameters.mss_d2d.beam_radius, 19001) 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, + self.parameters.mss_d2d.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.parameters.mss_d2d.antenna.gain, 34.1) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.frequency, 2177.0) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.bandwidth, 6.0) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.slr, 20) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.n_side_lobes, 2) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.l_r, 1.6) + self.assertEqual(self.parameters.mss_d2d.antenna.itu_r_s_1528.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) @@ -633,21 +689,37 @@ def test_parametes_mss_d2d(self): self.assertEqual( self.parameters.mss_d2d.beam_positioning.service_grid.beam_radius, 19001) + + self.assertEqual( + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone.type, + "CIRCLE") + self.assertEqual( + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone.circle.center_lat, + -14.123) + self.assertEqual( + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone.circle.center_lon, + 120) + self.assertEqual( + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone.circle.radius_km, + 321) + self.assertEqual( self.parameters.mss_d2d.beam_positioning.service_grid.transform_grid_randomly, True) self.assertEqual( - self.parameters.mss_d2d.beam_positioning.service_grid.grid_margin_from_border, + self.parameters.mss_d2d.beam_positioning.service_grid.grid_in_zone.from_countries.margin_from_border, 0.11) self.assertEqual( self.parameters.mss_d2d.beam_positioning.service_grid.eligible_sats_margin_from_border, -2.1) self.assertEqual( - len(self.parameters.mss_d2d.beam_positioning.service_grid.country_names), 2) + self.parameters.mss_d2d.beam_positioning.service_grid.minimum_service_angle, 12.1) + self.assertEqual( + len(self.parameters.mss_d2d.beam_positioning.service_grid.grid_in_zone.from_countries.country_names), 2) self.assertEqual( - self.parameters.mss_d2d.beam_positioning.service_grid.country_names[0], + self.parameters.mss_d2d.beam_positioning.service_grid.grid_in_zone.from_countries.country_names[0], "Brazil") self.assertEqual( - self.parameters.mss_d2d.beam_positioning.service_grid.country_names[1], + self.parameters.mss_d2d.beam_positioning.service_grid.grid_in_zone.from_countries.country_names[1], "Chile") self.assertEqual( @@ -745,6 +817,15 @@ def test_parameters_single_space_station(self): self.assertEqual( self.parameters.single_space_station.geometry.es_long_deg, 3.9, ) + self.assertEqual( + self.parameters.single_space_station.geometry.pointing_at_alt, 123, + ) + self.assertEqual( + self.parameters.single_space_station.geometry.pointing_at_lat, 12, + ) + self.assertEqual( + self.parameters.single_space_station.geometry.pointing_at_long, -1, + ) self.assertEqual( self.parameters.single_space_station.geometry.azimuth.type, "FIXED", @@ -794,6 +875,20 @@ def test_parameters_single_space_station(self): self.parameters.single_space_station.antenna.gain, ) + self.assertEqual( + self.parameters.single_space_station.antenna.itu_r_s_1528.antenna_gain, + self.parameters.single_space_station.antenna.gain, + ) + self.assertEqual( + self.parameters.single_space_station.antenna.itu_r_s_1528.major_minor_axis_ratio, + 1.0 + ) + + self.assertEqual( + self.parameters.single_space_station.antenna.itu_r_s_1528.far_out_side_lobe, + -25 + ) + self.assertEqual( self.parameters.single_space_station.param_p619.earth_station_alt_m, self.parameters.single_space_station.geometry.es_altitude, @@ -852,6 +947,94 @@ def test_mss_d2d_loaded_geom(self): (CL_AREA + BR_AREA) / 1e6, delta=53e3) + def test_mss_d2d_loaded_exclusion_zone(self): + """Test loading and geometry checks for MSS D2D exclusion zone parameters.""" + exclusion_zone = self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone + exclusion_zone.type = "CIRCLE" + exclusion_zone.circle.center_lat = 0.0 + exclusion_zone.circle.center_lon = 0.0 + exclusion_zone.circle.radius_km = 1.0 + + # test exclusion zone circle area + exclusion_zone._calculate_polygon() + pol = exclusion_zone._polygon + + geod = Geod(a=EARTH_RADIUS_M, b=EARTH_RADIUS_M) + + # geod area should be similar to a circle area for small area + CIRCLE_AREA = np.pi * 1e6 + CIRCLE_PERIMETER = 2 * np.pi * 1e3 + area, perimeter = geod.geometry_area_perimeter(pol) + area = abs(area) + + self.assertAlmostEqual(perimeter, CIRCLE_PERIMETER, delta=3) + # 0.2 % error: + rel_delta = 0.2 / 100 + self.assertAlmostEqual(area, CIRCLE_AREA, delta=rel_delta * CIRCLE_AREA) + + def test_mss_d2d_loaded_service_grid(self): + """ + Testing if service grid is created according to exclusion zone specification + """ + # test service grid on 2 countries + beam_radius_m = 40e3 + seed = 2 + self.parameters.mss_d2d.beam_positioning.service_grid.beam_radius = beam_radius_m + self.parameters.mss_d2d.beam_positioning.service_grid.grid_in_zone.from_countries.margin_from_border = beam_radius_m / 1e3 + rng = np.random.RandomState(seed) + + self.parameters.mss_d2d.beam_positioning.service_grid.country_names = [ + "Paraguay", "Brazil"] + + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone.type = None + self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone._calculate_polygon() + self.parameters.mss_d2d.beam_positioning.service_grid.reset_grid( + "test", rng, True) + + """Test circle with same radius as margin""" + original_grid = self.parameters.mss_d2d.beam_positioning.service_grid.lon_lat_grid + + grid_exclusion_zone = self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone + grid_exclusion_zone.type = "CIRCLE" + # at frienship bridge, so should affect more than 1 grid + grid_exclusion_zone.circle.center_lat = -25.5094741 + grid_exclusion_zone.circle.center_lon = -54.6007197 + grid_exclusion_zone.circle.radius_km = beam_radius_m / 1e3 + + rng = np.random.RandomState(seed) + grid_exclusion_zone._calculate_polygon() + + self.parameters.mss_d2d.beam_positioning.service_grid.reset_grid( + "test", rng, True) + grid_w_exclusion = self.parameters.mss_d2d.beam_positioning.service_grid.lon_lat_grid + + self.assertEqual(original_grid.shape, grid_w_exclusion.shape) + + """Test circle with radius bigger than margin""" + original_grid = self.parameters.mss_d2d.beam_positioning.service_grid.lon_lat_grid + + grid_exclusion_zone = self.parameters.mss_d2d.beam_positioning.service_grid.grid_exclusion_zone + grid_exclusion_zone.type = "CIRCLE" + # at frienship bridge, so should affect more than 1 grid + grid_exclusion_zone.circle.center_lat = -25.5094741 + grid_exclusion_zone.circle.center_lon = -54.6007197 + grid_exclusion_zone.circle.radius_km = 4 * beam_radius_m / 1e3 + + rng = np.random.RandomState(seed) + grid_exclusion_zone._calculate_polygon() + + self.parameters.mss_d2d.beam_positioning.service_grid.reset_grid( + "test", rng, True) + grid_w_exclusion = self.parameters.mss_d2d.beam_positioning.service_grid.lon_lat_grid + + n_original = original_grid.shape[1] + n_after = grid_w_exclusion.shape[1] + + # aft >= orig - 12 + self.assertLessEqual(n_original - 12, n_after) + # aft < orig + self.assertLess(n_after, n_original) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_adjacent_channel.py b/tests/test_adjacent_channel.py index aca41822e..358cefa22 100644 --- a/tests/test_adjacent_channel.py +++ b/tests/test_adjacent_channel.py @@ -16,6 +16,7 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS @@ -175,8 +176,10 @@ def test_simulation_2bs_4ue_downlink(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -204,6 +207,9 @@ def test_simulation_2bs_4ue_downlink(self): random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -243,12 +249,16 @@ def test_simulation_2bs_4ue_downlink(self): 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]) + self.simulation.system.geom.set_global_coords( + np.array([0.01]), # avoids zero-division + np.array([0]), + np.array([self.param.fss_ss.altitude]), + ) - # test the method that calculates interference from IMT UE to FSS space + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) + # test the method that calculates interference from IMT BS to FSS space # station self.simulation.calculate_external_interference() @@ -313,8 +323,10 @@ def test_simulation_2bs_4ue_uplink(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -341,6 +353,9 @@ def test_simulation_2bs_4ue_uplink(self): random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -378,11 +393,15 @@ def test_simulation_2bs_4ue_uplink(self): 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]) + self.simulation.system.geom.set_global_coords( + np.array([0.01]), # avoids zero-division + np.array([0]), + np.array([self.param.fss_ss.altitude]), + ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) # test the method that calculates interference from IMT UE to FSS space # station self.simulation.calculate_external_interference() diff --git a/tests/test_antenna_array.py b/tests/test_antenna_array.py new file mode 100644 index 000000000..686e05575 --- /dev/null +++ b/tests/test_antenna_array.py @@ -0,0 +1,357 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Apr 15 15:36:22 2017 + +@author: Calil +""" + +import unittest +import numpy as np +import numpy.testing as npt + +from sharc.antenna.antenna_array import AntennaArray +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt + + +class AntennaArrayTest(unittest.TestCase): + """Unit tests for the AntennaArray class.""" + + def setUp(self): + """Set up test fixtures for AntennaArray tests.""" + # Array parameters + self.bs_param = ParametersAntennaImt() + self.ue_param = ParametersAntennaImt() + + # NOTE: not implemented: + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.normalization = False + self.bs_param.normalization_file = None + self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.ue_param.normalization = False + self.ue_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.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.bs_param.get_antenna_parameters() + self.antenna1 = AntennaArray(par) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaArray(par) + + def test_element_gain(self): + """Testing element gain calculations""" + + """Test M.2101 horizontal pattern calculation for various phi values.""" + # phi = 0 results in zero gain + phi = np.array([0., 120., 150.]) + theta = np.zeros_like(phi) + 90. + h_att = self.antenna1._element_gain(phi, theta) + npt.assert_equal( + h_att, + self.antenna1.par.element_max_g - np.array([0.0, 27.0, 30.0]) + ) + + """Test M.2101 vertical pattern calculation for various theta values.""" + theta = np.array([90, 180, 210]) + phi = np.zeros_like(theta) + v_att = self.antenna1._element_gain(phi, theta) + npt.assert_equal( + v_att, + self.antenna1.par.element_max_g - np.array([0.0, 27.0, 30.0]) + ) + + """Test element pattern calculation for various phi/theta values.""" + phi = np.array([0, 80, 150]) + theta = np.array([90, 150, 210]) + e_gain = self.antenna1._element_gain(phi, theta) + npt.assert_equal( + e_gain, + np.array([5.0, -19.0, -25.0]) + ) + + def test_weight_vector(self): + """Test calculation of the weight vector for beamforming.""" + # Error margin + eps = 1e-5 + + acc_phi_scan = [] + acc_theta_tilt = [] + acc_w_vec = [] + # Test 1 + phi_scan = 0 + theta_tilt = 0 + w_vec = self.antenna2._weight_vector( + phi_scan, theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi_scan.append(phi_scan) + acc_theta_tilt.append(theta_tilt) + acc_w_vec.append(w_vec) + expected_w_vec = np.array([[[0.5, 0.5], [0.5, 0.5]]]) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + # Test 2 + phi_scan = 90 + theta_tilt = 90 + w_vec = self.antenna2._weight_vector( + phi_scan, theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi_scan.append(phi_scan) + acc_theta_tilt.append(theta_tilt) + acc_w_vec.append(w_vec) + expected_w_vec = np.array([[[0.5, 0.5], [-0.5, -0.5]]]) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + # Test 3 + phi_scan = 45 + theta_tilt = 45 + w_vec = self.antenna2._weight_vector( + phi_scan, theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi_scan.append(phi_scan) + acc_theta_tilt.append(theta_tilt) + acc_w_vec.append(w_vec) + expected_w_vec = np.array([[ + [0.5 + 0.0j, 0.0 - 0.5j], + [-0.3028499 + 0.3978466j, 0.3978466 + 0.3028499j], + ]]) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + # Test 4 + phi_scan = 0 + theta_tilt = 90 + w_vec = self.antenna2._weight_vector( + phi_scan, theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi_scan.append(phi_scan) + acc_theta_tilt.append(theta_tilt) + acc_w_vec.append(w_vec) + expected_w_vec = np.array([[[0.5, 0.5], [-0.5, -0.5]]]) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + # Test 5 + phi_scan = 45 + theta_tilt = 30 + w_vec = self.antenna2._weight_vector( + phi_scan, theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi_scan.append(phi_scan) + acc_theta_tilt.append(theta_tilt) + acc_w_vec.append(w_vec) + expected_w_vec = np.array([[ + [0.5 + 0.0j, -0.172870 - 0.469169j], + [0.0 + 0.5j, 0.469165 - 0.172870j], + ]]) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + acc_phi_scan = np.array(acc_phi_scan) + acc_theta_tilt = np.array(acc_theta_tilt) + acc_w_vec = np.array(acc_w_vec) + w_vec = self.antenna2._weight_vector( + acc_phi_scan, acc_theta_tilt, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + expected_w_vec = np.squeeze(acc_w_vec, axis=1) + npt.assert_allclose( + w_vec, + expected_w_vec, rtol=eps, + ) + + def test_super_position_vector(self): + """Test calculation of the superposition vector.""" + # Error margin + eps = 1e-5 + + acc_phi = [] + acc_theta = [] + acc_v_vec = [] + + # Test 1 + phi = 0 + theta = 0 + v_vec = self.antenna2._super_position_vector( + phi, theta, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi.append(phi) + acc_theta.append(theta) + acc_v_vec.append(v_vec) + expected_v_vec = np.array([[[1.0, 1.0], [-1.0, -1.0]]]) + npt.assert_allclose( + v_vec, + expected_v_vec, rtol=eps, + ) + + # Test 2 + phi = 90 + theta = 90 + v_vec = self.antenna2._super_position_vector( + phi, theta, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi.append(phi) + acc_theta.append(theta) + acc_v_vec.append(v_vec) + expected_v_vec = np.array([[[1.0, -1.0], [1.0, -1.0]]]) + npt.assert_allclose( + v_vec, + expected_v_vec, rtol=eps, + ) + + # Test 3 + phi = 45 + theta = 45 + v_vec = self.antenna2._super_position_vector( + phi, theta, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi.append(phi) + acc_theta.append(theta) + acc_v_vec.append(v_vec) + expected_v_vec = np.array([[ + [1.0 + 0.0j, 0.0 + 1.0j], + [-0.6056998 + 0.7956932j, -0.7956932 - 0.6056998j], + ]]) + npt.assert_allclose( + v_vec, + expected_v_vec, rtol=eps, + ) + + # Test 4 + phi = 60 + theta = 90 + v_vec = self.antenna2._super_position_vector( + phi, theta, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + acc_phi.append(phi) + acc_theta.append(theta) + acc_v_vec.append(v_vec) + expected_v_vec = np.array([[ + [1.0 + 0.0j, -0.912724 + 0.408576j], + [1.0 + 0.0j, -0.912724 + 0.408576j], + ]]) + npt.assert_allclose( + v_vec, + expected_v_vec, rtol=eps, + ) + + acc_phi = np.array(acc_phi) + acc_theta = np.array(acc_theta) + expected_v_vec = np.squeeze(acc_v_vec, axis=1) + v_vec = self.antenna2._super_position_vector( + acc_phi, acc_theta, + self.antenna2.par.n_rows, self.antenna2.par.n_columns, + self.antenna2.par.element_vert_spacing, + self.antenna2.par.element_horiz_spacing, + ) + npt.assert_allclose( + v_vec, + expected_v_vec, rtol=eps, + ) + + def test_calculate_gain(self): + """Test calculation of antenna gain for given phi/theta vectors.""" + # Error margin and antenna + eps = 1e-4 + par = self.bs_param.get_antenna_parameters() + self.antenna1 = AntennaArray(par) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaArray(par) + + # 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) + + # Test 2 + phi = 0.0 + theta = 60.0 + phi_scan = 45 + theta_tilt = 180 + self.antenna2.add_beam(phi_scan, theta_tilt) + beams_l = np.zeros_like(phi, 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) + + # 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) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_antenna_multiple_transceiver.py b/tests/test_antenna_multiple_transceiver.py index 7a49cfa02..047b4c67b 100644 --- a/tests/test_antenna_multiple_transceiver.py +++ b/tests/test_antenna_multiple_transceiver.py @@ -15,10 +15,14 @@ class AntennaAntennaMultipleTransceiverTest(unittest.TestCase): def setUp(self): """Set up test fixtures for AntennaMultipleTransceiver tests.""" param = ParametersAntennaS1528() - param.antenna_gain = 30 - param.frequency = 2170.0 - param.bandwidth = 5.0 - param.antenna_3_dB_bw = 4.4127 + param.pattern = "ITU-R-S.1528-Taylor" + param.frequency = 43000.0 + param.bandwidth = 500.0 + param.antenna_gain = 46.6 + param.slr = 20.0 + param.n_side_lobes = 2 + param.l_r = 1.6 + param.l_t = 1.6 self.base_antenna = AntennaS1528Taylor(param) diff --git a/tests/test_antenna_s1528.py b/tests/test_antenna_s1528.py index 0d2aae6cf..b025506b5 100644 --- a/tests/test_antenna_s1528.py +++ b/tests/test_antenna_s1528.py @@ -23,6 +23,8 @@ def setUp(self): param.antenna_gain = 39 param.antenna_pattern = "ITU-R S.1528-0" param.antenna_3_dB_bw = 2 + param.major_minor_axis_ratio = 1 + param.far_out_side_lobe = 0 param.antenna_l_s = -20 self.antenna20 = AntennaS1528(param) diff --git a/tests/test_antenna_s672.py b/tests/test_antenna_s672.py index 74efba3a5..a17425fa6 100644 --- a/tests/test_antenna_s672.py +++ b/tests/test_antenna_s672.py @@ -8,7 +8,7 @@ import unittest from sharc.antenna.antenna_s672 import AntennaS672 -from sharc.parameters.parameters_fss_ss import ParametersFssSs +from sharc.parameters.antenna.parameters_antenna_s672 import ParametersAntennaS672 import numpy as np import numpy.testing as npt @@ -19,10 +19,10 @@ class AntennaS672Test(unittest.TestCase): def setUp(self): """Set up test fixtures for AntennaS672 tests.""" - param = ParametersFssSs() + param = ParametersAntennaS672() param.antenna_gain = 50 param.antenna_pattern = "ITU-R S.672-4" - param.antenna_3_dB = 2 + param.antenna_3_dB_bw = 2 param.antenna_l_s = -20 self.antenna20 = AntennaS672(param) diff --git a/tests/test_geometry_converter.py b/tests/test_coordinate_system.py similarity index 58% rename from tests/test_geometry_converter.py rename to tests/test_coordinate_system.py index 674d6e219..cc5081889 100644 --- a/tests/test_geometry_converter.py +++ b/tests/test_coordinate_system.py @@ -1,30 +1,30 @@ import unittest import numpy as np import numpy.testing as npt -from sharc.support.sharc_geom import GeometryConverter +from sharc.support.sharc_geom import CoordinateSystem from sharc.satellite.utils.sat_utils import ecef2lla, lla2ecef from sharc.station_manager import StationManager -class TestGeometryConverter(unittest.TestCase): - """Unit tests for the GeometryConverter class and related coordinate transformations.""" +class TestCoordinateSystem(unittest.TestCase): + """Unit tests for the CoordinateSystem class and related coordinate transformations.""" def setUp(self): - """Set up test fixtures for GeometryConverter tests.""" - self.conv0_0km = GeometryConverter() + """Set up test fixtures for CoordinateSystem tests.""" + self.conv0_0km = CoordinateSystem() self.conv0_0km.set_reference( 0, 0, 0 ) - self.conv0_52km = GeometryConverter() + self.conv0_52km = CoordinateSystem() self.conv0_52km.set_reference( 0, 0, 52e3 ) - self.conv1_0km = GeometryConverter() + self.conv1_0km = CoordinateSystem() self.conv1_0km.set_reference( -15, -47, 0 ) - self.conv1_10km = GeometryConverter() + self.conv1_10km = CoordinateSystem() self.conv1_10km.set_reference( -15, -47, 10e3 ) @@ -66,21 +66,20 @@ def test_set_reference(self): def test_reference_ecef(self): """Test ECEF to LLA conversion for reference points.""" - for conv in self.all_converters: - lat, lon, alt = ecef2lla(conv.ref_x, conv.ref_y, conv.ref_z) + for coord_sys in self.all_converters: + lat, lon, alt = ecef2lla(coord_sys.ref_x, coord_sys.ref_y, coord_sys.ref_z) # ecef2lla approximation requires "almost equal" directive - print("conv.ref_lat", conv.ref_lat) - self.assertAlmostEqual(lat[0], conv.ref_lat, places=8) - self.assertAlmostEqual(lon[0], conv.ref_long, places=8) - self.assertAlmostEqual(alt[0], conv.ref_alt, places=8) + self.assertAlmostEqual(lat[0], coord_sys.ref_lat, places=8) + self.assertAlmostEqual(lon[0], coord_sys.ref_long, places=8) + self.assertAlmostEqual(alt[0], coord_sys.ref_alt, places=8) def test_ecef_to_enu(self): """Test ECEF to ENU coordinate transformation.""" # for each converter defined at the setup - for conv in self.all_converters: + for coord_sys in self.all_converters: # check if reference point always goes to (0,0,0) - x, y, z = conv.convert_cartesian_to_transformed_cartesian( - conv.ref_x, conv.ref_y, conv.ref_z) + x, y, z = coord_sys.ecef2enu( + coord_sys.ref_x, coord_sys.ref_y, coord_sys.ref_z) self.assertEqual(x, 0) self.assertEqual(y, 0) self.assertEqual(z, 0) @@ -88,9 +87,9 @@ def test_ecef_to_enu(self): def test_lla_to_enu(self): """Test LLA to ENU coordinate transformation.""" # for each converter defined at the setup - for conv in self.all_converters: - x, y, z = conv.convert_lla_to_transformed_cartesian( - conv.ref_lat, conv.ref_long, conv.ref_alt) + for coord_sys in self.all_converters: + x, y, z = coord_sys.lla2enu( + coord_sys.ref_lat, coord_sys.ref_long, coord_sys.ref_alt) self.assertEqual(x, 0) self.assertEqual(y, 0) self.assertEqual(z, 0) @@ -98,17 +97,17 @@ def test_lla_to_enu(self): def test_enu_to_ecef(self): """Test ENU to ECEF coordinate transformation.""" # for each converter defined at the setup - for conv in self.all_converters: + for coord_sys in self.all_converters: # check if the reverse is true - x, y, z = conv.revert_transformed_cartesian_to_cartesian(0, 0, 0) - self.assertEqual(x, conv.ref_x) - self.assertEqual(y, conv.ref_y) - self.assertEqual(z, conv.ref_z) + x, y, z = coord_sys.enu2ecef(0, 0, 0) + self.assertEqual(x, coord_sys.ref_x) + self.assertEqual(y, coord_sys.ref_y) + self.assertEqual(z, coord_sys.ref_z) def test_station_converter(self): """Test station coordinate and orientation conversions between ECEF and ENU.""" # for each converter defined at the setup - for conv in self.all_converters: + for coord_sys in self.all_converters: rng = np.random.default_rng(0) n_samples = 100 stations = StationManager(n_samples) @@ -119,32 +118,43 @@ def test_station_converter(self): rng.uniform(0, 35e3, n_samples), ) # set first station to be the reference - xyz_bef[0][0] = conv.ref_x - xyz_bef[1][0] = conv.ref_y - xyz_bef[2][0] = conv.ref_z + xyz_bef[0][0] = coord_sys.ref_x + xyz_bef[1][0] = coord_sys.ref_y + xyz_bef[2][0] = coord_sys.ref_z # point them randomly azim_bef = rng.uniform(-180, 180, n_samples) elev_bef = rng.uniform(-90, 90, n_samples) - stations.x, stations.y, stations.z = xyz_bef - stations.azimuth = azim_bef - stations.elevation = elev_bef + stations.geom.set_global_coords( + xyz_bef[0], xyz_bef[1], xyz_bef[2], + azim_bef, elev_bef + ) # get relative distances and off axis while in ecef - dists_bef = stations.get_3d_distance_to(stations) - off_axis_bef = stations.get_off_axis_angle(stations) + dists_bef = stations.geom.get_3d_distance_to(stations.geom) + off_axis_bef = stations.geom.get_off_axis_angle(stations.geom) # convert stations to enu - conv.convert_station_3d_to_2d(stations) - + nx, ny, nz = coord_sys.ecef2enu( + stations.geom.x_global, + stations.geom.y_global, + stations.geom.z_global, + ) + nazim, nelev = coord_sys.angle_ecef2enu( + stations.geom.pointn_azim_global, + stations.geom.pointn_elev_global, + ) + stations.geom.set_global_coords( + nx, ny, nz, nazim, nelev + ) # check if reference origin - self.assertEqual(stations.x[0], 0) - self.assertEqual(stations.y[0], 0) - self.assertEqual(stations.z[0], 0) + self.assertEqual(stations.geom.x_global[0], 0) + self.assertEqual(stations.geom.y_global[0], 0) + self.assertEqual(stations.geom.z_global[0], 0) # get relative distances and off axis while in enu - dists_aft = stations.get_3d_distance_to(stations) - off_axis_aft = stations.get_off_axis_angle(stations) + dists_aft = stations.geom.get_3d_distance_to(stations.geom) + off_axis_aft = stations.geom.get_off_axis_angle(stations.geom) # all stations should maintain same relative distances and off axis # since their relative positioning and pointing should eq in ECEF @@ -167,39 +177,49 @@ def test_station_converter(self): # so we ignore the first station (used as reference) on these # checks npt.assert_equal( - np.abs(stations.x[1:] - xyz_bef[0][1:]) > 1e3, + np.abs(stations.geom.x_global[1:] - xyz_bef[0][1:]) > 1e3, True ) npt.assert_equal( - np.abs(stations.y[1:] - xyz_bef[1][1:]) > 1e3, + np.abs(stations.geom.y_global[1:] - xyz_bef[1][1:]) > 1e3, True ) npt.assert_equal( - np.abs(stations.z[1:] - xyz_bef[2][1:]) > 1e3, + np.abs(stations.geom.z_global[1:] - xyz_bef[2][1:]) > 1e3, True ) # and the elevation angle may not change much if pointing vector is to the east/west # since the pointing vector is aligned with the x axis, the rotation along it # won't change the value much npt.assert_equal( - np.abs(stations.azimuth - azim_bef) > 0.4, + np.abs(stations.geom.pointn_azim_global - azim_bef) > 0.4, True ) npt.assert_equal( - np.abs(stations.elevation - elev_bef) > 0.4, + np.abs(stations.geom.pointn_elev_global - elev_bef) > 0.4, True ) # return stations to starting case: - conv.revert_station_2d_to_3d(stations) - + nx, ny, nz = coord_sys.enu2ecef( + stations.geom.x_global, + stations.geom.y_global, + stations.geom.z_global, + ) + nazim, nelev = coord_sys.angle_enu2ecef( + stations.geom.pointn_azim_global, + stations.geom.pointn_elev_global, + ) + stations.geom.set_global_coords( + nx, ny, nz, nazim, nelev + ) # check if their position is the same as at the start # some precision error occurs, so "almost equal" is needed - npt.assert_almost_equal(stations.x, xyz_bef[0]) - npt.assert_almost_equal(stations.y, xyz_bef[1]) - npt.assert_almost_equal(stations.z, xyz_bef[2]) - npt.assert_almost_equal(stations.azimuth, azim_bef) - npt.assert_almost_equal(stations.elevation, elev_bef) + npt.assert_almost_equal(stations.geom.x_global, xyz_bef[0]) + npt.assert_almost_equal(stations.geom.y_global, xyz_bef[1]) + npt.assert_almost_equal(stations.geom.z_global, xyz_bef[2]) + npt.assert_almost_equal(stations.geom.pointn_azim_global, azim_bef) + npt.assert_almost_equal(stations.geom.pointn_elev_global, elev_bef) if __name__ == '__main__': diff --git a/tests/test_geometry.py b/tests/test_geometry.py new file mode 100644 index 000000000..9144eca5e --- /dev/null +++ b/tests/test_geometry.py @@ -0,0 +1,833 @@ +import unittest +import numpy as np +import numpy.testing as npt + +from sharc.support.geometry import ( + SimulatorGeometry, DWNReferenceFrame, ENUReferenceFrame, RigidTransform +) +from sharc.satellite.ngso.constants import EARTH_RADIUS_M +from copy import deepcopy +from scipy.spatial.transform import Rotation +from itertools import product + + +def random_rigid_transform(rng, N): + """ + Generate a random RigidTransform with batch size N. + Rotations are orthonormal matrices generated via QR. + """ + A = rng.normal(size=(N, 3, 3)) + Q, _ = np.linalg.qr(A) + + # ensure right-handed (det = +1) + det = np.linalg.det(Q) + Q[det < 0, :, 0] *= -1 + + t = rng.normal(size=(N, 3)) + + return RigidTransform(Rotation.from_matrix(Q), t) + + +def rot_identity(n=1): + """Returns identity rotation of batch size n.""" + return Rotation.from_rotvec(np.zeros((n, 3))) + + +def rot_z(angle_deg, n=1): + """Returns rotation about Z axis by angle_deg degrees, batch size n.""" + return Rotation.from_rotvec( + np.tile([0.0, 0.0, angle_deg], (n, 1)), + degrees=True + ) + + +def rot_x(angle_deg, n=1): + """Returns rotation about X axis by angle_deg degrees, batch size n.""" + return Rotation.from_rotvec( + np.tile([angle_deg, 0.0, 0.0], (n, 1)), + degrees=True + ) + + +def rot_y(angle_deg, n=1): + """Returns rotation about Y axis by angle_deg degrees, batch size n.""" + return Rotation.from_rotvec( + np.tile([0.0, angle_deg, 0.0], (n, 1)), + degrees=True + ) + + +class TestRigidTransform(unittest.TestCase): + """Unit tests for RigidTransform class.""" + + def setUp(self): + """Set up test fixtures for RigidTransform tests.""" + pass + + def test_init_and_broadcasting(self): + """Test RigidTransform initialization and broadcasting behavior.""" + for rot_shp, t_shp in product( + [(1, 3), (4, 3)], + [(1, 3), (4, 3)], + ): + Nrot = rot_shp[0] + Nt = t_shp[0] + N = max(Nrot, Nt) + + rot = rot_identity(rot_shp[0]) + t = np.zeros(t_shp) + + with self.assertRaises(ValueError): + RigidTransform(rot, np.zeros((3,))) + + if Nt > 1: + with self.assertRaises(ValueError): + RigidTransform(rot_identity(N + 1), t) + + if Nrot > 1: + with self.assertRaises(ValueError): + RigidTransform(rot, np.zeros((N + 1, 3))) + + # should not throw: + tr = RigidTransform(rot, t) + RigidTransform(rot, np.zeros((1, 3))) + RigidTransform(rot_identity(1), t) + + # test broadcasting contracts: + for fn in [tr.apply_points, tr.apply_vectors]: + if N > 1: + with self.assertRaises(AssertionError): + fn(np.zeros((N + 1, 3))) + + for in_shp in [(1, 3), (N, 3)]: + res = fn(np.zeros(in_shp)) + npt.assert_equal( + res.shape, (N, 3) + ) + + for fn, in_shp in product( + [tr.apply_points_permutation, tr.apply_vectors_permutation], + [(1, 3), (N, 3), (N + 1, 3)] + ): + Nin = in_shp[0] + res = fn(np.zeros(in_shp)) + npt.assert_equal( + res.shape, (N, Nin, 3) + ) + + def test_simple_transformations(self): + """Test RigidTransform simple transformations and their combinations.""" + eps = 1e-4 + + ux = np.array([1., 0., 0.]) + uy = np.array([0., 1., 0.]) + uz = np.array([0., 0., 1.]) + u = np.array([ux, uy, uz, ux + uy + uz]) + + ####################################################################### + # Identity transform + id_tr = RigidTransform(rot_identity(4), np.zeros((1, 3))) + out = id_tr.apply_points(u) + npt.assert_equal(out, u) + + out = id_tr.apply_vectors(u) + npt.assert_equal(out, u) + + out = id_tr.apply_points_permutation(u) + npt.assert_equal(out, np.stack((u, u, u, u))) + + out = id_tr.apply_vectors_permutation(u) + npt.assert_equal(out, np.stack((u, u, u, u))) + + ####################################################################### + # Translation only transform + t1_tr = RigidTransform(rot_identity(4), np.ones((1, 3))) + out = t1_tr.apply_points(u) + npt.assert_equal(out, u + 1.) + out = t1_tr.inv().apply_points(out) + npt.assert_equal(out, u) + + out = t1_tr.apply_vectors(u) + npt.assert_equal(out, u) + + out = t1_tr.apply_points_permutation(u) + npt.assert_equal(out, np.stack((u, u, u, u)) + 1.) + out = t1_tr.inv().apply_points(out[0]) + npt.assert_equal(out, u) + + out = t1_tr.apply_vectors_permutation(u) + npt.assert_equal(out, np.stack((u, u, u, u))) + + ####################################################################### + # Rotation Z only transform + rot_z90_tr = RigidTransform( + rot_z(-90.), np.zeros((1, 3)) + ) + out_rot_z90 = rot_z90_tr.apply_points(u) + npt.assert_allclose( + out_rot_z90, + np.array([ + -uy, ux, uz, ux - uy + uz + ]), + atol=eps + ) + out = rot_z90_tr.inv().apply_points(out_rot_z90) + npt.assert_allclose( + out, + u, + atol=eps + ) + + ####################################################################### + # Rotation X only transform + rot_x90_tr = RigidTransform( + rot_x(-90., 4), np.zeros((1, 3)) + ) + + out_rot_z90_x90 = rot_x90_tr.apply_points(out_rot_z90) + expected_out_rot_z90_x90 = np.array([ + uz, ux, uy, ux + uy + uz + ]) + npt.assert_allclose( + out_rot_z90_x90, + expected_out_rot_z90_x90, + atol=eps + ) + out = rot_x90_tr.inv().apply_points(expected_out_rot_z90_x90) + npt.assert_allclose( + out, + out_rot_z90, + atol=eps + ) + + ####################################################################### + # Rotation Z and then Rotation X transform + npt.assert_allclose( + rot_z90_tr.and_then(rot_x90_tr).apply_points(u), + expected_out_rot_z90_x90, + atol=eps, + ) + npt.assert_allclose( + (rot_z90_tr.and_then(rot_x90_tr).inv() + .apply_points(expected_out_rot_z90_x90)), + u, + atol=eps, + ) + npt.assert_allclose( + (rot_x90_tr.inv().and_then(rot_z90_tr.inv()) + .apply_points(expected_out_rot_z90_x90)), + u, + atol=eps, + ) + + ####################################################################### + # Rotation Z and then Rotation X and then Translation transform + npt.assert_allclose( + rot_z90_tr.and_then(rot_x90_tr).and_then(t1_tr).apply_points(u), + expected_out_rot_z90_x90 + 1., + atol=eps, + ) + npt.assert_allclose( + (rot_z90_tr.and_then(rot_x90_tr).and_then(t1_tr).inv() + .apply_points(expected_out_rot_z90_x90 + 1.)), + u, + atol=eps, + ) + npt.assert_allclose( + (t1_tr.inv().and_then(rot_x90_tr.inv().and_then(rot_z90_tr.inv())) + .apply_points(expected_out_rot_z90_x90 + 1.)), + u, + atol=eps, + ) + # considering that VECTOR calculation should NOT translate + npt.assert_allclose( + rot_z90_tr.and_then(rot_x90_tr).and_then(t1_tr).apply_vectors(u), + expected_out_rot_z90_x90, + atol=eps, + ) + npt.assert_allclose( + (rot_z90_tr.and_then(rot_x90_tr).and_then(t1_tr).inv() + .apply_vectors(expected_out_rot_z90_x90)), + u, + atol=eps, + ) + npt.assert_allclose( + (t1_tr.inv().and_then(rot_x90_tr.inv().and_then(rot_z90_tr.inv())) + .apply_vectors(expected_out_rot_z90_x90)), + u, + atol=eps, + ) + + def test_permutation_points_equivalence(self): + """Test equivalence of permutation and non-permutation point applications.""" + rng = np.random.default_rng(0) + + for n in range(1, 10): + tr = random_rigid_transform(rng, n) + + x = rng.normal(size=(n, 3)) + + y = tr.apply_points(x) + y_permutation = tr.apply_points_permutation(x) + + # diagonal of permutation must equal non-permutation + npt.assert_allclose( + y, + y_permutation.diagonal().T, + atol=1e-12, + ) + + def test_permutation_vectors_equivalence(self): + """Test equivalence of permutation and non-permutation vector applications.""" + rng = np.random.default_rng(0) + + for n in range(1, 10): + tr = random_rigid_transform(rng, n) + + x = rng.normal(size=(n, 3)) + + y = tr.apply_vectors(x) + y_permutation = tr.apply_vectors_permutation(x) + + # diagonal of permutation must equal non-permutation + npt.assert_allclose( + y, + y_permutation.diagonal().T, + atol=1e-12, + ) + + def test_take_commutes_with_apply_points(self): + """Test that 'take' method commutes with apply_points.""" + rng = np.random.default_rng(0) + + for n in range(1, 10): + tr = random_rigid_transform(rng, n) + x = rng.normal(size=(n, 3)) + + y_full = tr.apply_points(x) + + for i in range(n): + y_take = tr.take(i).apply_points(x) + npt.assert_allclose( + y_take[i], + y_full[i], + atol=1e-12, + ) + + def test_take_matches_permutation_points(self): + """Test that 'take' method matches permutation point applications.""" + rng = np.random.default_rng(1) + + for n in [2, 5]: + tr = random_rigid_transform(rng, n) + x = rng.normal(size=(n, 3)) + + Y = tr.apply_points_permutation(x) + + for i in range(n): + tr_i = tr.take(i) + yi = tr_i.apply_points(x) + + npt.assert_allclose( + yi, + Y[i], + rtol=1e-12, + atol=1e-12, + ) + + def test_take_matches_permutation_vectors(self): + """Test that 'take' method matches permutation vector applications.""" + rng = np.random.default_rng(1) + + for n in [2, 5]: + tr = random_rigid_transform(rng, n) + x = rng.normal(size=(n, 3)) + + Y = tr.apply_vectors_permutation(x) + + for i in range(n): + tr_i = tr.take(i) + yi = tr_i.apply_vectors(x) + + npt.assert_allclose( + yi, + Y[i], + rtol=1e-12, + atol=1e-12, + ) + + +class TestDWNReferenceFrame(unittest.TestCase): + """Unit tests for DWNReferenceFrame class.""" + + def setUp(self): + """Set up test fixtures for DWNReferenceFrame tests.""" + self.lat = np.array([0.0]) + self.lon = np.array([0.0]) + self.alt = np.array([0.0]) + + self.enu = ENUReferenceFrame( + lat=self.lat, lon=self.lon, alt=self.alt + ) + self.dwn = DWNReferenceFrame( + lat=self.lat, lon=self.lon, alt=self.alt + ) + + def test_enu_to_dwn_basis(self): + """Test transformation of basis vectors from ENU to DWN.""" + # ENU basis vectors + e = np.array([1.0, 0.0, 0.0]) # East + n = np.array([0.0, 1.0, 0.0]) # North + u = np.array([0.0, 0.0, 1.0]) # Up + + # Transform ENU -> ECEF -> DWN + e_dwn = self.dwn.from_ecef.apply_vectors( + self.enu.to_ecef.apply_vectors(e) + )[0] + n_dwn = self.dwn.from_ecef.apply_vectors( + self.enu.to_ecef.apply_vectors(n) + )[0] + u_dwn = self.dwn.from_ecef.apply_vectors( + self.enu.to_ecef.apply_vectors(u) + )[0] + + npt.assert_allclose(e_dwn, np.array([0.0, -1.0, 0.0]), atol=1e-4) + npt.assert_allclose(n_dwn, np.array([0.0, 0.0, 1.0]), atol=1e-4) + npt.assert_allclose(u_dwn, np.array([-1.0, 0.0, 0.0]), atol=1e-4) + npt.assert_allclose( + np.linalg.norm(e_dwn), + np.linalg.norm(e), + ) + npt.assert_allclose( + np.linalg.norm(n_dwn), + np.linalg.norm(n), + ) + npt.assert_allclose( + np.linalg.norm(u_dwn), + np.linalg.norm(u), + ) + + +class TestGeometry(unittest.TestCase): + """Unit tests for the CoordinateSystem class and related coordinate transformations.""" + + def _test_expected_geom( + self, + geom, + expect_local, + expect_global, + *, + only_global_azim=None, + only_local_azim=None + ): + npt.assert_allclose( + np.stack((geom.x_local, geom.y_local, geom.z_local)), + np.stack((expect_local["x"], expect_local["y"], expect_local["z"])), + atol=0.001 + ) + npt.assert_allclose(geom.pointn_elev_local, expect_local["elev"], atol=0.001) + if only_local_azim is None: + npt.assert_allclose(geom.pointn_azim_local, expect_local["azim"], atol=0.001) + else: + self.assertEqual(expect_local["azim"].shape, geom.pointn_azim_local.shape) + npt.assert_allclose( + geom.pointn_azim_local[only_local_azim], + expect_local["azim"][only_local_azim], + atol=0.001 + ) + + npt.assert_allclose( + np.stack((geom.x_global, geom.y_global, geom.z_global)), + np.stack((expect_global["x"], expect_global["y"], expect_global["z"])), + atol=0.001 + ) + npt.assert_allclose(geom.pointn_elev_global, expect_global["elev"], atol=0.001) + if only_global_azim is None: + npt.assert_allclose( + geom.pointn_azim_global, + expect_global["azim"], + atol=0.001 + ) + else: + self.assertEqual(expect_local["azim"].shape, geom.pointn_azim_local.shape) + npt.assert_allclose( + geom.pointn_azim_global[only_global_azim], + expect_global["azim"][only_global_azim], + atol=0.001 + ) + + def setUp(self): + """Set up test fixtures for CoordinateSystem tests.""" + pass + + def test_set_coords_when_local_eq_global(self): + """Test setting coordinates when local should eq global + """ + ref_lla = (10, -5, 1200) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + no_local = SimulatorGeometry(3, False, ref_frame) + local_eq_global = SimulatorGeometry(3, True, ref_frame) + local_eq_global.set_local_reference_frame( + ENUReferenceFrame( + lat=np.repeat([ref_lla[0]], 3), + lon=np.repeat([ref_lla[1]], 3), + alt=np.repeat([ref_lla[2]], 3), + ) + ) + + """Test setting global coordinates + """ + expect = { + "x": np.array([10., 11., 21.]), "y": np.array([15., 19., 211]), + "z": np.array([20., 30., 50.]), + "azim": np.array([90., 70., 170.]), "elev": np.array([12., 15., 19.]), + } + no_local.set_global_coords(**expect) + local_eq_global.set_global_coords(**expect) + + """Verify that when local reference is not set or is set to global ref, + local and global are set to the expected, equal, value + """ + self._test_expected_geom(no_local, expect, expect) + self._test_expected_geom(local_eq_global, expect, expect) + + """Test setting local coordinates + """ + expect = { + "x": np.array([1235., 1241., 12341.]), "y": np.array([12413., 89012., 767.]), + "z": np.array([91238., 481., 123980.]), + "azim": np.array([-10., -12., -98.]), "elev": np.array([-70., -1., 0.]), + } + no_local.set_local_coords(**expect) + local_eq_global.set_local_coords(**expect) + + """Verify that when local reference is not set or is the same as global + local and global are set to the expected, equal, value + """ + self._test_expected_geom(no_local, expect, expect) + self._test_expected_geom(local_eq_global, expect, expect) + + def test_setting_different_alts(self): + """Test setting coordinates when local should eq global + """ + ref_lla = (-45.0, 90, 30.) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + + alt_vals = np.array([500., 800., 1200., 1800.]) + + def init_diff_alt(): + geom = SimulatorGeometry(4, True, ref_frame) + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=np.repeat([ref_lla[0]], 4), + lon=np.repeat([ref_lla[1]], 4), + alt=alt_vals, + ) + ) + return geom + diff_alt = init_diff_alt() + + """Test setting global coordinates + """ + expect_local = { + "x": np.array([10., 11., 12., 985.]), "y": np.array([15., 31., 41341., 10.]), + "z": np.array([-5., 100., 1e4, -1e3]), + "azim": np.array([90., 10., -179., 180.]), "elev": np.array([89., -89., -1., 12.]), + } + expect_global = deepcopy(expect_local) + alt_vals_diff = ref_lla[2] - alt_vals + expect_global["z"] -= alt_vals_diff + + diff_alt.set_global_coords(**expect_global) + + """Verify that when local reference is not set, local and global + are set to the expected, equal, value + """ + self._test_expected_geom(diff_alt, expect_local, expect_global) + + """Test setting local coordinates + """ + diff_alt = init_diff_alt() + diff_alt.set_local_coords(**expect_local) + + """Verify that when local reference is set to be the same as global, + local and global are set to the expected, value + """ + self._test_expected_geom(diff_alt, expect_local, expect_global) + + def test_setting_different_llas(self): + """Test setting coordinates when local should eq global + """ + ref_lla = (0.0, 0.0, 0.0) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + + llas = np.array([ + [-90., 0., 0.], + [90., 0., 0.], + [0., 90., 0.], + [0., -90., 0.], + ]).T + + def init_diff_llas(): + geom = SimulatorGeometry(4, True, ref_frame) + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=llas[0], + lon=llas[1], + alt=llas[2], + ) + ) + return geom + + """Test setting global coordinates + """ + expect_local = { + "x": np.zeros(4), "y": np.zeros(4), + "z": np.zeros(4), + # point to north + "azim": np.zeros(4) + 90., "elev": np.zeros(4) - 10, + } + expect_global = { + "x": np.array([0., 0., 1., -1.]) * EARTH_RADIUS_M, + "y": np.array([-1., 1., 0., 0.]) * EARTH_RADIUS_M, + "z": np.zeros(4) - EARTH_RADIUS_M, + "azim": np.array([90., -90., 100., 80.]), + "elev": np.array([80., -80., 0., 0.]), + } + + diff_llas = init_diff_llas() + diff_llas.set_local_coords(**expect_local) + + self._test_expected_geom( + diff_llas, expect_local, expect_global, + # only_global_azim=np.where(abs(expect_global["elev"]) != 90.), + # only_local_azim=np.where(abs(expect_local["elev"]) != 90.), + ) + + diff_llas = init_diff_llas() + diff_llas.set_global_coords(**expect_global) + + self._test_expected_geom( + diff_llas, expect_local, expect_global, + # only_global_azim=np.where(abs(expect_global["elev"]) != 90.), + # only_local_azim=np.where(abs(expect_local["elev"]) != 90.), + ) + + def test_get_local_distance_to_diff_ref(self): + """Tests getting local distance from a station to another when + they have different local references + """ + ref_lla = (90., 0., 0.) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + local_llas = np.array([ + [0., 0., 0.], + [0., 1., 0.], + [0., -1., 0.], + [1., 0., 0.], + [-1., 0., 0.], + ]).T + + def init_geom(): + geom = SimulatorGeometry(5, True, ref_frame) + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=local_llas[0], + lon=local_llas[1], + alt=local_llas[2], + ) + ) + geom.set_local_coords( + np.repeat(0., 5), + np.repeat(0., 5), + np.repeat(0., 5), + np.repeat(0., 5), + np.repeat(0., 5), + ) + return geom + + geom = init_geom() + + dists2d = geom.get_local_distance_to(geom) + + self.assertEqual(dists2d.shape, (5, 5)) + + # distance of any coord to itself + npt.assert_allclose(np.diagonal(dists2d), 0., atol=1e-8) + + # assuming 1deg difference should be ~ 111km at lat,lon = (0, 0) + npt.assert_allclose( + dists2d[0], np.array([0., 111e3, 111e3, 111e3, 111e3]), + atol=1e-8, # tolerance for == 0. + rtol=0.3 / 100, # tolerance for all others + ) + npt.assert_allclose( + dists2d[1], np.array([111e3, 0., 2 * 111e3, np.sqrt(2) * 111e3, np.sqrt(2) * 111e3]), + atol=1e-8, # tolerance for == 0. + rtol=0.3 / 100, # tolerance for all others + ) + npt.assert_allclose( + dists2d[2], np.array([111e3, 2 * 111e3, 0., np.sqrt(2) * 111e3, np.sqrt(2) * 111e3]), + atol=1e-8, # tolerance for == 0. + rtol=0.3 / 100, # tolerance for all others + ) + npt.assert_allclose( + dists2d[3], np.array([111e3, np.sqrt(2) * 111e3, np.sqrt(2) * 111e3, 0., 2 * 111e3]), + atol=1e-8, # tolerance for == 0. + rtol=0.3 / 100, # tolerance for all others + ) + npt.assert_allclose( + dists2d[4], np.array([111e3, np.sqrt(2) * 111e3, np.sqrt(2) * 111e3, 2 * 111e3, 0.]), + atol=1e-8, # tolerance for == 0. + rtol=0.3 / 100, # tolerance for all others + ) + + def test_get_local_distance_to_same_ref(self): + """Tests getting local distance from a station to another when + they have same local references + """ + ref_lla = (90., 0., 0.) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + local_llas = np.array([ + [0., 0., 0.], + [0., 0., 0.], + [0., -1., 0.], + [0., -1., 0.], + ]).T + + def init_geom(): + geom = SimulatorGeometry(4, True, ref_frame) + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=local_llas[0], + lon=local_llas[1], + alt=local_llas[2], + ) + ) + geom.set_local_coords( + np.tile([0., 20.], 2), + np.tile([0., 20.], 2), + np.tile([0., 10.], 2), + np.repeat(0., 4), + np.repeat(0., 4), + ) + return geom + + geom = init_geom() + + dists2d, z_dist = geom.get_local_distance_to(geom, return_z_dist=True) + + self.assertEqual(dists2d.shape, (4, 4)) + self.assertEqual(z_dist.shape, (4, 4)) + + # distance of any coord to itself + npt.assert_allclose(np.diagonal(dists2d), 0., atol=1e-8) + npt.assert_allclose(np.diagonal(z_dist), 0., atol=1e-8) + + # and that square diagonal is sqrt(2) * side_len + npt.assert_allclose( + dists2d[0, :2], np.array([0., 20 * np.sqrt(2)]), + atol=1e-8, + ) + npt.assert_allclose( + z_dist[0, :2], np.array([0., 10]), + atol=1e-8, + ) + + npt.assert_allclose( + dists2d[1, :2], np.array([20 * np.sqrt(2), 0.]), + atol=1e-8, + ) + npt.assert_allclose( + z_dist[1, :2], np.array([-10, 0.]), + atol=1e-8, + ) + + npt.assert_allclose( + dists2d[2, 2:], np.array([0., 20 * np.sqrt(2)]), + atol=1e-8, + ) + npt.assert_allclose( + z_dist[2, 2:], np.array([0, 10.]), + atol=1e-8, + ) + + npt.assert_allclose( + dists2d[3, 2:], np.array([20 * np.sqrt(2), 0.]), + atol=1e-8, + ) + npt.assert_allclose( + z_dist[3, 2:], np.array([-10., 0.]), + atol=1e-8, + ) + + # assuming 1deg difference should be ~ 111km at lat,lon = (0, 0) + npt.assert_allclose( + dists2d[:2, 2:], 111e3, + rtol=0.4 / 100, # tolerance for all others + ) + npt.assert_allclose( + dists2d[2:, :2], 111e3, + rtol=0.4 / 100, # tolerance for all others + ) + + def test_get_local_elevation(self): + """Tests getting local elevation from a station to another. + """ + ref_lla = (90., 0., 0.) + ref_frame = ENUReferenceFrame( + lat=ref_lla[0], lon=ref_lla[1], alt=ref_lla[2], + ) + local_llas = np.array([ + [0., 0., 0.], + [0., 0., 0.], + [0., -1., 0.], + [0., -1., 0.], + ]).T + + def init_geom(): + geom = SimulatorGeometry(4, True, ref_frame) + geom.set_local_reference_frame( + ENUReferenceFrame( + lat=local_llas[0], + lon=local_llas[1], + alt=local_llas[2], + ) + ) + geom.set_local_coords( + np.tile([0., 20.], 2), + np.tile([0., 0.], 2), + np.tile([0., 20.], 2), + np.repeat(0., 4), + np.repeat(0., 4), + ) + return geom + + geom = init_geom() + + elev = geom.get_local_elevation(geom) + + self.assertEqual(elev.shape, (4, 4)) + + npt.assert_allclose(elev[0, 1], 45.) + npt.assert_allclose(elev[1, 0], -45.) + + npt.assert_allclose(elev[2, 3], 45.) + npt.assert_allclose(elev[3, 2], -45.) + + # obviously below horizon for each other + npt.assert_array_less(elev[:2, 2:], 0.) + npt.assert_array_less(elev[2:, :2], 0.) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_propagation_abg.py b/tests/test_propagation_abg.py index 44849d956..82b648534 100644 --- a/tests/test_propagation_abg.py +++ b/tests/test_propagation_abg.py @@ -16,41 +16,6 @@ class PropagationABGTest(unittest.TestCase): """Unit tests for the PropagationABG class and its loss calculations.""" - - def setUp(self): - """Set up test fixtures for PropagationABG tests.""" - 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): - """Test the get_loss method for various distance and frequency inputs.""" - 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, - ) - def setUp(self): """Set up test fixtures for PropagationABG tests.""" self.abg = PropagationABG( @@ -75,11 +40,11 @@ def test_loss(self): loss, atol=1e-2, ) - d = np.array([500, 3000])[:, np.newaxis] - f = np.array([27000, 40000])[:, np.newaxis] + d = np.array([500, 3000]) + f = np.array([27000, 40000]) indoor = np.zeros(d.shape[0], dtype=bool) shadowing = False - loss = np.array([143.886, 174.269])[:, np.newaxis] + loss = np.array([143.886, 174.269]) npt.assert_allclose( self.abg.get_loss(d, f, indoor, shadowing), loss, atol=1e-2, diff --git a/tests/test_propagation_clutter.py b/tests/test_propagation_clutter.py index 8ddf7cf71..5aec00f87 100644 --- a/tests/test_propagation_clutter.py +++ b/tests/test_propagation_clutter.py @@ -25,7 +25,7 @@ def test_spatial_clutter_loss(self): frequency=frequency, elevation=elevation, loc_percentage=loc_percentage, - station_type=StationType.FSS_SS, + clutter_scenario="spatial", earth_station_height=earth_station_height, mean_clutter_height=mean_clutter_height, below_rooftop=below_rooftop, @@ -48,7 +48,7 @@ def test_terrestrial_clutter_loss(self): frequency=frequency, distance=distance, loc_percentage=loc_percentage, - station_type=StationType.IMT_BS, + clutter_scenario="terrestrial", clutter_type=clutter_type ) @@ -65,7 +65,7 @@ def test_random_loc_percentage(self): frequency=frequency, distance=distance, loc_percentage="RANDOM", - station_type=StationType.IMT_UE, + clutter_scenario="terrestrial", clutter_type=clutter_type ) diff --git a/tests/test_propagation_hdfss_roof_top.py b/tests/test_propagation_hdfss_roof_top.py index a2167c80d..e6185e7b7 100644 --- a/tests/test_propagation_hdfss_roof_top.py +++ b/tests/test_propagation_hdfss_roof_top.py @@ -95,7 +95,7 @@ def setUp(self): def test_get_loss(self): """Test the get_loss method for various roof top scenarios.""" # Not on same building - d = np.array([[10.0, 20.0, 30.0, 60.0, 90.0, 300.0, 1000.0]]) + d = np.array([10.0, 20.0, 30.0, 60.0, 90.0, 300.0, 1000.0]) f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) @@ -107,25 +107,25 @@ def test_get_loss(self): 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]), + es_x=np.zeros((7,)), + es_y=np.zeros((7,)), + es_z=np.zeros((7,)), ) loss = loss[0] expected_loss = np.array( - [[84.48, 90.50, 94.02, 100.72, 104.75, 139.33, 162.28]], + [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]]) + d = np.array([10.0, 20.0, 30.0]) 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]) + es_x = np.repeat([0.0], 3) + es_y = np.repeat([0.0], 3) + es_z = np.repeat([10.0], 3) 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]) @@ -144,14 +144,14 @@ def test_get_loss(self): ) loss = loss[0] - expected_loss = np.array([[150 + 84.48, 100 + 90.50, 50 + 94.02]]) + expected_loss = np.array([150 + 84.48, 100 + 90.50, 50 + 94.02]) npt.assert_allclose(loss, expected_loss, atol=1e-1) def test_get_build_loss(self): """Test get_building_loss for various station types and probabilities.""" # Initialize variables - ele = np.array([[0.0, 45.0, 90.0]]) + ele = np.array([0.0, 45.0, 90.0]) f = 40000 * np.ones_like(ele) sta_type = StationType.IMT_BS @@ -165,7 +165,7 @@ def test_get_build_loss(self): self.assertEqual(build_loss, expected_build_loss) # Test 2: fixed probability - expected_build_loss = np.array([[24.4, 33.9, 43.4]]) + expected_build_loss = np.array([24.4, 33.9, 43.4]) build_loss = self.propagation_fixed_prob.get_building_loss( sta_type, f, @@ -174,7 +174,7 @@ def test_get_build_loss(self): 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]]) + expected_build_loss = np.array([21.7, 32.9, 15.9]) build_loss = self.propagation_random_prob.get_building_loss( sta_type, f, @@ -184,7 +184,7 @@ def test_get_build_loss(self): # Test 4: UE station sta_type = StationType.IMT_UE - expected_build_loss = np.array([[21.7, 32.9, 15.9]]) + expected_build_loss = np.array([21.7, 32.9, 15.9]) build_loss = self.propagation_fixed_value.get_building_loss( sta_type, f, @@ -197,7 +197,7 @@ def test_get_build_loss(self): ele, ) npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) - expected_build_loss = np.array([[10.1, 36.8, 52.6]]) + expected_build_loss = np.array([10.1, 36.8, 52.6]) build_loss = self.propagation_random_prob.get_building_loss( sta_type, f, @@ -208,9 +208,9 @@ def test_get_build_loss(self): def test_same_building(self): """Test is_same_building method for correct building identification and loss.""" # Test is_same_building() - es_x = np.array([0.0]) - es_y = np.array([0.0]) - es_z = np.array([19.0]) + es_x = np.repeat([0.0], 5) + es_y = np.repeat([0.0], 5) + es_z = np.repeat([19.0], 5) 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) @@ -226,7 +226,6 @@ def test_same_building(self): # Test loss d = np.sqrt(np.power(imt_x, 2) + np.power(imt_y, 2)) - d = np.array([list(d)]) f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) @@ -243,7 +242,7 @@ def test_same_building(self): es_z=es_z, ) loss = loss[0] - expected_loss = np.array([[4067.5, 94.0, 103.6, 103.1, 4086.5]]) + expected_loss = np.array([4067.5, 94.0, 103.6, 103.1, 4086.5]) npt.assert_allclose(loss, expected_loss, atol=1e-1) diff --git a/tests/test_propagation_path.py b/tests/test_propagation_path.py new file mode 100644 index 000000000..eb3e42b55 --- /dev/null +++ b/tests/test_propagation_path.py @@ -0,0 +1,408 @@ +import unittest +import numpy as np +import numpy.testing as npt + +from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation_path import PropagationPath +from sharc.propagation.propagation_free_space import PropagationFreeSpace +from sharc.propagation.propagation_factory import PropagationFactory +from sharc.station_manager import StationManager +from sharc.support.enumerations import StationType + + +def assert_array_not_equal(x, y): + """Asserts that two arrays are not equal.""" + npt.assert_raises(AssertionError, npt.assert_array_equal, x, y) + + +def create_mock_function(ret): + """Creates function that returns the argument + """ + def mock_function(*args, **kwargs): + return ret + return mock_function + + +class PropagationPathTest(unittest.TestCase): + """This is more of an integration test since it depends on the parts + it joins. + """ + def setUp(self): + """setUp that runs before each test""" + pass + + def test_undeduped_masking_operations(self): + """Testing masking operations when not deduplicating + """ + bs = StationManager(4) + bs.geom.set_global_coords( + np.array([5., 15., 25., 35.]), + np.array([0., 0., 0., 0.]), + np.repeat(10., 4), + ) + ue = StationManager(3) + ue.geom.set_global_coords( + np.array([-5., -5., -25.]), + np.array([0., 0., 0.]), + np.repeat(10., 3), + ) + + path = PropagationPath.create_default(ue, bs) + path.calc_mask(deduplicate=False) + + mtx = np.array([ + [1., 2., 3., 4.], + [5., 6., 7., 8.], + [9., 10., 11., 12.], + ]) + mskd_mtx = path.mtx_to_masked(mtx) + + npt.assert_equal(np.ravel(mtx), mskd_mtx) + + unmskd_mtx = path.from_masked_mtx(mskd_mtx) + npt.assert_equal(mtx, unmskd_mtx) + + vec = np.array([10., 20., 30.]) + vec_cast = path.sta_a_to_masked(vec) + # since paths are ordered by sta_a, + # they are iterated for (0, ...), (1, ...) + # as such it repeats the i-th indice n sta_b times + expected = np.array([ + 10., 10., 10., 10., + 20., 20., 20., 20., + 30., 30., 30., 30., + ]) + npt.assert_array_equal(expected, vec_cast) + + # since paths are ordered by sta_a, + # they are iterated for (0, ...j), (1, ...) + # as such it goes through entire j sequence + # sta_a times + vec = np.array([10., 20., 30., 40.]) + vec_cast = path.sta_b_to_masked(vec) + expected = np.array([ + 10., 20., 30., 40., + 10., 20., 30., 40., + 10., 20., 30., 40., + ]) + npt.assert_array_equal(expected, vec_cast) + + ue.active[0] = False + path.calc_mask(deduplicate=False) + + mskd_mtx = path.mtx_to_masked(mtx) + + npt.assert_equal(np.ravel(mtx[1:]), mskd_mtx) + + unmskd_mtx = path.from_masked_mtx(mskd_mtx) + # first row of mtx should be np.nan + expected = np.copy(mtx) + expected[0, :] = np.nan + + npt.assert_equal(expected, unmskd_mtx) + + vec = np.array([10., 20., 30.]) + vec_cast = path.sta_a_to_masked(vec) + expected = np.array([ + 20., 20., 20., 20., + 30., 30., 30., 30., + ]) + npt.assert_array_equal(expected, vec_cast) + + vec = np.array([10., 20., 30., 40.]) + vec_cast = path.sta_b_to_masked(vec) + expected = np.array([ + 10., 20., 30., 40., + 10., 20., 30., 40., + ]) + npt.assert_array_equal(expected, vec_cast) + + def test_deduped_mask_operations(self): + """Testing masking operations when deduplicating + """ + bs = StationManager(4) + bs.geom.set_global_coords( + np.array([5., 15., 25., 35.]), + np.array([0., 0., 0., 0.]), + np.repeat(10., 4), + ) + ue = StationManager(3) + ue.geom.set_global_coords( + np.array([-5., -5., -25.]), + np.array([0., 0., 0.]), + np.repeat(10., 3), + ) + + """Since zeroth row it represents 1, it should be used for both 0 and 1""" + path = PropagationPath.create_default(ue, bs) + path.calc_mask(deduplicate=True) + + mtx = np.array([ + [1., 2., 3., 4.], + [5., 6., 7., 8.], + [9., 10., 11., 12.], + ]) + mskd_mtx = path.mtx_to_masked(mtx) + expected = np.concatenate((mtx[0], mtx[2])) + # expect reduction in values for path loss calc + npt.assert_equal(expected, mskd_mtx) + + unmskd_mtx = path.from_masked_mtx(mskd_mtx) + # and reverse mapping for deduped values (0) and the rows that need + # those values (0 and 1) + expected = np.concatenate(([mtx[0]], [mtx[0]], [mtx[2]])) + npt.assert_equal(expected, unmskd_mtx) + + vec = np.array([10., 20., 30.]) + vec_cast = path.sta_a_to_masked(vec) + # since paths are ordered by sta_a, + # they are iterated for (0, ...), (1, ...) + # as such it repeats the i-th indice n sta_b times + expected = np.array([ + 10., 10., 10., 10., + 30., 30., 30., 30., + ]) + npt.assert_array_equal(expected, vec_cast) + + # since paths are ordered by sta_a, + # they are iterated for (0, ...j), (1, ...) + # as such it goes through entire j sequence + # sta_a times + vec = np.array([10., 20., 30., 40.]) + vec_cast = path.sta_b_to_masked(vec) + expected = np.array([ + 10., 20., 30., 40., + 10., 20., 30., 40., + ]) + npt.assert_array_equal(expected, vec_cast) + + """Even if 0 is not active, since it represents 1, it should be calc'd""" + ue.active[0] = False + path.calc_mask(deduplicate=True) + + mskd_mtx = path.mtx_to_masked(mtx) + # so its value should be masked + expected = np.concatenate((mtx[0], mtx[2])) + npt.assert_equal(expected, mskd_mtx) + + unmskd_mtx = path.from_masked_mtx(mskd_mtx) + # zeroth row of mtx should be np.nan + expected = np.copy(mtx) + expected[0, :] = np.nan + # and second should come from the masked zeroth + expected[1, :] = mtx[0, :] + + npt.assert_equal(expected, unmskd_mtx) + + def test_get_path_loss_fspl(self): + """Test get_path_loss with different shapes on fspl + """ + bs = StationManager(3) + bs.geom.set_global_coords( + np.array([5., 15., 25.]), + np.array([0., 0., 0.]), + np.repeat(10., 3), + ) + ue = StationManager(3) + ue.geom.set_global_coords( + np.array([-5., -15., -25.]), + np.array([0., 0., 0.]), + np.repeat(10., 3), + ) + + path = PropagationPath.create_default(ue, bs) + + fspl = path.get_path_loss( + PropagationFreeSpace(None), + None, + 1e3, # [MHz] + ) + + expected_fspl = PropagationFreeSpace(None).get_free_space_loss( + 1e3, + ue.geom.get_3d_distance_to(bs.geom), + ) + + self.assertEqual(fspl.shape, expected_fspl.shape) + npt.assert_array_equal(fspl, expected_fspl) + + def test_get_path_loss_in_propagations(self): + """Test get_path_loss method with all propagations + """ + bs = StationManager(12) + bs.geom.set_global_coords( + np.arange(0., 12., 1.0) * 10, + np.repeat(0., 12), + np.repeat(10., 12), + ) + ue = StationManager(36) + bs.station_type = StationType.IMT_UE + ue.geom.set_global_coords( + np.repeat(np.arange(0., 18., 1.0) * 10, 2), + np.repeat(5., 36), + np.repeat(1.5, 36), + ) + bs.is_space_station = False + for i in range(5, 11): + ue.active[i] = False + + for i in range(20, 23): + ue.active[i] = False + + for i in range(0, 3): + bs.active[i] = False + + path = PropagationPath.create_default(ue, bs) + parameters = Parameters() + parameters.imt.topology.type = "MSS_DC" + parameters.imt.interfered_with = True + gains0 = np.zeros(path._orig_shape) + + bs_w_beams_gains = np.zeros([*path._orig_shape, 3]) + ue.is_space_station = True + for ch_model in [ + "FSPL", + "ABG", + "UMa", + "UMi", + # "SatelliteSimple", + "TerrestrialSimple", + "P619", + "P452", + "TVRO-URBAN", + "TVRO-SUBURBAN", + "HDFSS", + "INDOOR", + ]: + rng = np.random.RandomState(1) + propagation = PropagationFactory.create_propagation( + ch_model, parameters, + parameters.single_earth_station, + rng, + ) + if hasattr(propagation, "_get_atmospheric_gasses_loss"): + # since P619 takes too long calculating this + propagation._get_atmospheric_gasses_loss = create_mock_function(0.0) + + ploss = path.get_path_loss( + propagation, + parameters, + 1e3, # [MHz] + # sta_a_gains=gains0, + sta_a_gains=bs_w_beams_gains, + sta_b_gains=gains0.T, + ) + + if ch_model == "HDFSS": + ploss = ploss[0] + + expected_shape = (ue.num_stations, bs.num_stations) + expected_filled = np.stack(np.where(path.mask), axis=0) + expected_nans = np.stack(np.where(~path.mask), axis=0) + + if ch_model == "INDOOR": + # NOTE: current Indoor channel model implementaiton + # depends on the matrix structure so it does not consider + # the only active paths for path loss calculation + # TODO: update indoor implementation to let that happen + expected_filled = np.stack(np.where(np.ones(expected_shape)), axis=0) + expected_nans = np.stack(np.where(np.zeros(expected_shape)), axis=0) + else: + # checking if the _paths_from_to is representative + npt.assert_array_equal(expected_filled, path._paths_from_to.T) + + self.assertEqual(ploss.shape, expected_shape) + npt.assert_array_equal(ploss[tuple(expected_nans)], np.nan) + assert_array_not_equal(ploss[tuple(expected_filled)], np.nan) + + def test_get_path_loss_single_ss_vs_bs_in_propagations(self): + """Test for the case when a single other station is vs base station + important for code coverage in P.619 (single entry interference) + """ + single_sta = StationManager(1) + single_sta.geom.set_global_coords( + np.array([0.]), + np.array([5.]), + np.array([30.]), + ) + single_sta.is_space_station = True + single_sta.active[0] = True + + bs = StationManager(12) + bs.geom.set_global_coords( + np.arange(0., 12., 1.0) * 10, + np.repeat(0., 12), + np.repeat(10., 12), + ) + bs.station_type = StationType.IMT_UE + bs.is_space_station = False + + for i in range(0, 3): + bs.active[i] = False + + path = PropagationPath.create_default(single_sta, bs) + parameters = Parameters() + parameters.imt.topology.type = "MSS_DC" + parameters.imt.interfered_with = True + gains0 = np.zeros(path._orig_shape) + + bs_w_beams_gains = np.zeros([path._orig_shape[1], path._orig_shape[0], 3]) + for ch_model in [ + "FSPL", + "ABG", + "UMa", + "UMi", + # "SatelliteSimple", + "TerrestrialSimple", + "P619", + "P452", + "TVRO-URBAN", + "TVRO-SUBURBAN", + "HDFSS", + "INDOOR", + ]: + rng = np.random.RandomState(1) + propagation = PropagationFactory.create_propagation( + ch_model, parameters, + parameters.single_earth_station, + rng, + ) + if hasattr(propagation, "_get_atmospheric_gasses_loss"): + # since P619 takes too long calculating this + propagation._get_atmospheric_gasses_loss = create_mock_function(0.0) + + ploss = path.get_path_loss( + propagation, + parameters, + 1e3, # [MHz] + sta_a_gains=gains0, + # sta_b_gains=gains0.T, + sta_b_gains=bs_w_beams_gains, + ) + + if ch_model == "HDFSS": + ploss = ploss[0] + + expected_shape = (single_sta.num_stations, bs.num_stations) + expected_filled = np.stack(np.where(path.mask), axis=0) + expected_nans = np.stack(np.where(~path.mask), axis=0) + + if ch_model == "INDOOR": + # NOTE: current Indoor channel model implementaiton + # depends on the matrix structure so it does not consider + # the only active paths for path loss calculation + # TODO: update indoor implementation to let that happen + expected_filled = np.stack(np.where(np.ones(expected_shape)), axis=0) + expected_nans = np.stack(np.where(np.zeros(expected_shape)), axis=0) + else: + # checking if the _paths_from_to is representative + npt.assert_array_equal(expected_filled, path._paths_from_to.T) + + self.assertEqual(ploss.shape, expected_shape) + npt.assert_array_equal(ploss[tuple(expected_nans)], np.nan) + assert_array_not_equal(ploss[tuple(expected_filled)], np.nan) + + +if __name__ == '__main__': + # unittest.main(module="tests.test_propagation_path") + unittest.main() diff --git a/tests/test_propagation_uma.py b/tests/test_propagation_uma.py index efedf64b7..f88f9662a 100644 --- a/tests/test_propagation_uma.py +++ b/tests/test_propagation_uma.py @@ -24,12 +24,12 @@ def test_los_probability(self): distance_2D = np.array([ [10, 15, 40], [17, 60, 80], - ]) - h_ue = np.array([1.5, 8, 15]) + ]).flatten() + h_ue = np.repeat([1.5, 8, 15], 2) los_probability = np.array([ [1, 1, 0.74], [1, 0.57, 0.45], - ]) + ]).flatten() npt.assert_allclose( self.uma.get_los_probability(distance_2D, h_ue), los_probability, @@ -38,14 +38,14 @@ def test_los_probability(self): def test_breakpoint_distance(self): """Test the calculation of breakpoint distance for UMa scenario.""" - h_bs = np.array([15, 20, 25, 30]) - h_ue = np.array([3, 4]) - h_e = np.ones((h_ue.size, h_bs.size)) + h_bs = np.tile([15, 20, 25, 30], 2) + h_ue = np.repeat([3, 4], 4) + h_e = np.ones((h_ue.size,)) frequency = 30000 * np.ones(h_e.shape) breakpoint_distance = np.array([ [11200, 15200, 19200, 23200], [16800, 22800, 28800, 34800], - ]) + ]).flatten() npt.assert_array_equal( self.uma.get_breakpoint_distance(frequency, h_bs, h_ue, h_e), breakpoint_distance, @@ -58,11 +58,11 @@ def test_loss_los(self): [200, 600], [300, 700], [400, 800], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -70,7 +70,7 @@ def test_loss_los(self): [108.09, 117.56], [111.56, 118.90], [114.05, 120.06], - ]) + ]).flatten() npt.assert_allclose( self.uma.get_loss_los( distance_2D, distance_3D, frequency, @@ -85,11 +85,11 @@ def test_loss_los(self): [200, 600], [300, 700], [400, 800], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -97,7 +97,7 @@ def test_loss_los(self): [68.09, 84.39], [71.56, 83.57], [74.05, 83.40], - ]) + ]).flatten() npt.assert_allclose( self.uma.get_loss_los( distance_2D, distance_3D, frequency, @@ -114,11 +114,11 @@ def test_loss_nlos(self): [200, 600], [300, 700], [400, 800], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -126,7 +126,7 @@ def test_loss_nlos(self): [132.25, 150.77], [138.45, 152.78], [142.70, 154.44], - ]) + ]).flatten() npt.assert_allclose( self.uma.get_loss_nlos( distance_2D, distance_3D, frequency, @@ -141,11 +141,11 @@ def test_loss_nlos(self): [2000, 6000], [5000, 7000], [4000, 8000], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -153,7 +153,7 @@ def test_loss_nlos(self): [131.18, 149.83], [146.13, 151.84], [141.75, 153.51], - ]) + ]).flatten() npt.assert_allclose( self.uma.get_loss_nlos( distance_2D, distance_3D, frequency, diff --git a/tests/test_propagation_umi.py b/tests/test_propagation_umi.py index af598453f..ee04a5546 100644 --- a/tests/test_propagation_umi.py +++ b/tests/test_propagation_umi.py @@ -29,11 +29,11 @@ def test_los_probability(self): distance_2D = np.array([ [10, 15, 40], [17, 60, 80], - ]) + ]).flatten() los_probability = np.array([ [1, 1, 0.631], [1, 0.432, 0.308], - ]) + ]).flatten() npt.assert_allclose( self.umi.get_los_probability( distance_2D, @@ -45,14 +45,14 @@ def test_los_probability(self): def test_breakpoint_distance(self): """Test the calculation of breakpoint distance for UMi scenario.""" - h_bs = np.array([15, 20, 25, 30]) - h_ue = np.array([3, 4]) - h_e = np.ones((h_ue.size, h_bs.size)) + h_bs = np.tile([15, 20, 25, 30], 2) + h_ue = np.repeat([3, 4], 4) + h_e = np.ones((h_ue.size)) frequency = 30000 * np.ones(h_e.shape) breakpoint_distance = np.array([ [11200, 15200, 19200, 23200], [16800, 22800, 28800, 34800], - ]) + ]).flatten() npt.assert_array_equal( self.umi.get_breakpoint_distance(frequency, h_bs, h_ue, h_e), breakpoint_distance, @@ -65,11 +65,11 @@ def test_loss_los(self): [200, 600], [300, 700], [400, 800], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -77,7 +77,7 @@ def test_loss_los(self): [110.396, 120.346], [114.046, 121.748], [116.653, 122.963], - ]) + ]).flatten() npt.assert_allclose( self.umi.get_loss_los( distance_2D, distance_3D, frequency, @@ -92,11 +92,11 @@ def test_loss_los(self): [200, 600], [300, 700], [400, 800], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -104,7 +104,7 @@ def test_loss_los(self): [70.396, 86.829], [74.046, 86.187], [76.653, 86.139], - ]) + ]).flatten() npt.assert_allclose( self.umi.get_loss_los( distance_2D, distance_3D, frequency, @@ -121,19 +121,21 @@ def test_loss_nlos(self): [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 - h_ue[:, np.newaxis])**2) - frequency = 30000 * np.ones(distance_2D.shape) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) + h_e = np.ones((distance_2D.size,)) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) + frequency = 30000 * np.ones(distance_3D.shape) shadowing_std = 0 loss = np.array([ [128.84, 152.96], [138.72, 155.45], [144.56, 157.50], [148.64, 159.25], - ]) + ]).flatten() + distance_2D = distance_2D.flatten() + npt.assert_allclose( self.umi.get_loss_nlos( distance_2D, distance_3D, frequency, @@ -148,11 +150,11 @@ def test_loss_nlos(self): [2000, 6000], [5000, 7000], [4000, 8000], - ]) - h_bs = np.array([30, 35]) - h_ue = np.array([2, 3, 4, 5]) + ]).flatten() + h_bs = np.tile([30, 35], 4) + h_ue = np.repeat([2, 3, 4, 5], 2) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue)**2) frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 loss = np.array([ @@ -160,7 +162,7 @@ def test_loss_nlos(self): [131.29, 148.13], [145.03, 150.19], [141.31, 151.94], - ]) + ]).flatten() npt.assert_allclose( self.umi.get_loss_nlos( distance_2D, distance_3D, frequency, diff --git a/tests/test_sat_utils.py b/tests/test_sat_utils.py index b1bff794a..c15be42c3 100644 --- a/tests/test_sat_utils.py +++ b/tests/test_sat_utils.py @@ -136,6 +136,50 @@ def test_calculate_elev_angle(self): ) npt.assert_almost_equal(e, expected_elevations[i], 1) + def test_sat_elevation_offaxis_conversion(self): + """Test conversion between satellite elevation angle and off-axis angle.""" + sat_altitude_km = 520.0 + + test_elevation_angles = np.array([0.0, 10.0, 30.0, 45.0, 60.0, 80.0, 90.0]) + for elev_angle in test_elevation_angles: + offaxis_angle = sat_utils.sat_elevation_to_offaxis(elev_angle, sat_altitude_km) + elev_angle_converted = sat_utils.offaxis_to_sat_elevation(offaxis_angle, sat_altitude_km) + npt.assert_almost_equal(elev_angle, elev_angle_converted, 5) + + def test_sat_elevation_offaxis_conversion_raises_value_error(self): + """Test that ValueError is raised for invalid elevation angles.""" + sat_altitude_km = 520.0 + + invalid_elevation_angles = [-10.0, 100.0, 150.0] + for elev_angle in invalid_elevation_angles: + with self.assertRaises(ValueError): + sat_utils.sat_elevation_to_offaxis(elev_angle, sat_altitude_km) + + with self.assertRaises(ValueError): + sat_utils.sat_elevation_to_offaxis(np.array([10.0, -5.0, 30.0]), sat_altitude_km) + + def test_offaxis_sat_elevation_conversion_raises_value_error(self): + """Test that ValueError is raised for invalid elevation angles in offaxis_to_sat_elevation.""" + sat_altitude_km = 520.0 + + invalid_offaxis_angles = [-10.0, 100.0, 150.0] + for elev_angle in invalid_offaxis_angles: + with self.assertRaises(ValueError): + sat_utils.offaxis_to_sat_elevation(elev_angle, sat_altitude_km) + + with self.assertRaises(ValueError): + sat_utils.offaxis_to_sat_elevation(np.array([10.0, -5.0, 30.0]), sat_altitude_km) + + def test_earth_arc_length_from_nadir(self): + """Test calculation of Earth's arc length from nadir based on off-axis angle.""" + sat_altitude_m = 520.0 * 1e3 + + test_offaxis_angles = np.array([0.0, 10.0, 30.0, 45.0, 60.0]) + expected_arc_lengths_m = np.array([0, 91.80971067, 304.53451317, 543.82932797, 1056.78781731]) * 1e3 + + arc_length = sat_utils.earth_arc_length_from_nadir(test_offaxis_angles, sat_altitude_m) + npt.assert_almost_equal(arc_length, expected_arc_lengths_m, decimal=3) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_simulation_downlink.py b/tests/test_simulation_downlink.py index 16768c0ab..4a8c93f73 100644 --- a/tests/test_simulation_downlink.py +++ b/tests/test_simulation_downlink.py @@ -15,6 +15,7 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS @@ -119,28 +120,29 @@ def setUp(self): 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 - self.param.fss_ss.lat_deg = 0 - self.param.fss_ss.azimuth = 0 - self.param.fss_ss.elevation = 270 - self.param.fss_ss.tx_power_density = -30 - self.param.fss_ss.noise_temperature = 950 - self.param.fss_ss.antenna_gain = 51 - 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.channel_model = "FSPL" - self.param.fss_ss.line_of_sight_prob = 0.01 - self.param.fss_ss.surf_water_vapour_density = 7.5 - self.param.fss_ss.specific_gaseous_att = 0.1 - 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.polarization_loss = 3.0 + self.param.single_space_station.frequency = 10000.0 + self.param.single_space_station.bandwidth = 100 + self.param.single_space_station.tx_power_density = -30 + self.param.single_space_station.noise_temperature = 950 + self.param.single_space_station.antenna.gain = 51 + self.param.single_space_station.antenna.pattern = "OMNI" + self.param.single_space_station.acs = 0 + self.param.single_space_station.geometry.azimuth.type = "FIXED" + self.param.single_space_station.geometry.azimuth.fixed = 0.0 + self.param.single_space_station.geometry.elevation.type = "FIXED" + self.param.single_space_station.geometry.elevation.fixed = 270 + self.param.single_space_station.geometry.es_altitude = 1000 + self.param.single_space_station.geometry.es_lat_deg = -23.5629739 + self.param.single_space_station.geometry.es_long_deg = -46.6555132 + self.param.single_space_station.geometry.location.type = "FIXED" + self.param.single_space_station.geometry.altitude = 35786000 + self.param.single_space_station.geometry.location.fixed.long_deg = 75 + self.param.single_space_station.geometry.location.fixed.lat_deg = 0 + # self.param.single_space_station.imt_long_diff_deg = (-46.6555132 - 75) + self.param.single_space_station.channel_model = "FSPL" + + self.param.single_space_station.acs = 0 + self.param.single_space_station.polarization_loss = 3.0 self.param.fss_es.x = -5000 self.param.fss_es.y = 0 @@ -180,9 +182,9 @@ def setUp(self): self.param.ras.tx_power_density = -500 self.param.ras.polarization_loss = 0.0 - def test_simulation_2bs_4ue_fss_ss(self): - """Test simulation with 2 base stations and 4 UEs for FSS-SS scenario.""" - self.param.general.system = "FSS_SS" + def test_simulation_2bs_4ue_single_space_station(self): + """Test simulation with 2 base stations and 4 UEs for single space station scenario.""" + self.param.general.system = "SINGLE_SPACE_STATION" self.simulation = SimulationDownlink(self.param, "") self.simulation.initialize() @@ -209,8 +211,10 @@ def test_simulation_2bs_4ue_fss_ss(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -232,12 +236,16 @@ def test_simulation_2bs_4ue_fss_ss(self): random_number_gen, ) self.simulation.propagation_system = PropagationFactory.create_propagation( - self.param.fss_ss.channel_model, + self.param.single_space_station.channel_model, self.param, self.simulation.param_system, random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) + # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -334,14 +342,18 @@ def test_simulation_2bs_4ue_fss_ss(self): rx_power - total_interference, atol=1e-2, ) - self.simulation.system = StationFactory.generate_fss_space_station( - self.param.fss_ss, + self.simulation.system = StationFactory.generate_single_space_station( + self.param.single_space_station, + ) + self.simulation.system.geom.set_global_coords( + np.array([0.01]), + np.array([0]), + np.array([self.param.single_space_station.geometry.altitude]), ) - 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]) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) # test the method that calculates interference from IMT UE to FSS space # station self.simulation.calculate_external_interference() @@ -420,8 +432,10 @@ def test_simulation_2bs_4ue_fss_es(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -433,6 +447,9 @@ def test_simulation_2bs_4ue_fss_es(self): self.simulation.param_system, random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) @@ -519,9 +536,11 @@ def test_simulation_2bs_4ue_fss_es(self): 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.system.geom.set_global_coords( + np.array([-2000]), + np.array([0]), + np.array([self.param.fss_es.height]), + ) self.simulation.propagation_imt = PropagationFactory.create_propagation( self.param.imt.channel_model, @@ -536,6 +555,9 @@ def test_simulation_2bs_4ue_fss_es(self): self.simulation.param_system, random_number_gen, ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) # what if FSS ES is the interferer? self.simulation.calculate_sinr_ext() @@ -577,6 +599,9 @@ def test_simulation_2bs_4ue_fss_es(self): atol=1e-2, ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) # what if IMT is interferer? self.simulation.calculate_external_interference() @@ -641,8 +666,10 @@ def test_simulation_2bs_4ue_ras(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -660,12 +687,13 @@ def test_simulation_2bs_4ue_ras(self): self.simulation.param_system, random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) 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_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) self.simulation.scheduler() @@ -691,13 +719,14 @@ def test_simulation_2bs_4ue_ras(self): atol=1e-2, ) - self.simulation.system = StationFactory.generate_ras_station( + self.simulation.system = StationFactory.generate_single_earth_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.geometry.height]) + self.simulation.system.geom.set_global_coords( + np.array([-2000]), + np.array([0]), + np.array([self.param.ras.geometry.height]), + ) self.simulation.system.antenna[0].effective_area = 54.9779 # Test gain calculation @@ -706,6 +735,9 @@ def test_simulation_2bs_4ue_ras(self): ) npt.assert_equal(gains, np.array([[50, 50]])) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) self.simulation.calculate_external_interference() polarization_loss = 3 diff --git a/tests/test_simulation_downlink_haps.py b/tests/test_simulation_downlink_haps.py index 6b85631b0..11891abf3 100644 --- a/tests/test_simulation_downlink_haps.py +++ b/tests/test_simulation_downlink_haps.py @@ -15,6 +15,7 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS @@ -169,8 +170,10 @@ def test_simulation_2bs_4ue_1haps(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -191,6 +194,9 @@ def test_simulation_2bs_4ue_1haps(self): self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) self.simulation.coupling_loss_imt = \ self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, @@ -264,6 +270,9 @@ def test_simulation_2bs_4ue_1haps(self): self.param.haps, 0, random_number_gen, ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) # now we evaluate interference from HAPS to IMT UE self.simulation.calculate_sinr_ext() diff --git a/tests/test_simulation_downlink_tvro.py b/tests/test_simulation_downlink_tvro.py index 216181a94..50dd64614 100644 --- a/tests/test_simulation_downlink_tvro.py +++ b/tests/test_simulation_downlink_tvro.py @@ -14,6 +14,7 @@ from sharc.parameters.parameters import Parameters from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath class SimulationDownlinkTvroTest(unittest.TestCase): @@ -153,10 +154,12 @@ def test_simulation_1bs_1ue_tvro(self): 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.bs.geom.set_global_coords( + np.array([0.0, -200.0]), + np.array([0.0, 0.0]), + azim=np.array([0.0, 180.0]), + elev=np.array([-10.0, -10.0]), + ) self.simulation.ue = StationFactory.generate_imt_ue( self.param.imt, @@ -164,8 +167,10 @@ def test_simulation_1bs_1ue_tvro(self): 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]) + self.simulation.ue.geom.set_global_coords( + np.array([30.0, 60.0, -220.0, -300.0]), + np.array([0.0, 0.0, 0.0, 0.0]), + ) # test connection method self.simulation.connect_ue_to_bs() @@ -185,6 +190,9 @@ def test_simulation_1bs_1ue_tvro(self): self.param, random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -285,9 +293,14 @@ def test_simulation_1bs_1ue_tvro(self): 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]) + self.simulation.system.geom.set_global_coords( + np.array([600.0]), + np.array([0.0]), + ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) # test the method that calculates interference from IMT UE to FSS space # station self.simulation.calculate_external_interference() diff --git a/tests/test_simulation_indoor.py b/tests/test_simulation_indoor.py index 1a7ce702a..a3b0b4f6d 100644 --- a/tests/test_simulation_indoor.py +++ b/tests/test_simulation_indoor.py @@ -167,10 +167,11 @@ def test_simulation_fss_es(self): # 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.z = np.ones_like( - self.simulation.ue.x) * self.param.imt.ue.height + self.simulation.ue.geom.set_global_coords( + np.array([0.0, 45.0, 75.0, 120.0]), + np.array([0.0, 50.0, 0.0, 50.0]), + np.ones_like(self.simulation.ue.geom.x_global) * self.param.imt.ue.height, + ) # print("Forced position:") # self.simulation.plot_scenario() @@ -239,8 +240,8 @@ def test_simulation_fss_es(self): ) # Test angle to ES in the IMT coord system - phi_es, theta_es = self.simulation.bs.get_pointing_vector_to( - self.simulation.system, + phi_es, theta_es = self.simulation.bs.geom.get_global_pointing_vector_to( + self.simulation.system.geom, ) 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) diff --git a/tests/test_simulation_uplink.py b/tests/test_simulation_uplink.py index def02bb60..b2f06626e 100644 --- a/tests/test_simulation_uplink.py +++ b/tests/test_simulation_uplink.py @@ -15,6 +15,7 @@ from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.propagation.propagation_path import PropagationPath class SimulationUplinkTest(unittest.TestCase): @@ -201,8 +202,10 @@ def test_simulation_2bs_4ue_ss(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -230,6 +233,9 @@ def test_simulation_2bs_4ue_ss(self): random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -363,10 +369,14 @@ def test_simulation_2bs_4ue_ss(self): 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]) + self.simulation.system.geom.set_global_coords( + np.array([0]), + np.array([0]), + np.array([self.param.fss_ss.altitude]), + ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) # test the method that calculates interference from IMT UE to FSS space # station @@ -433,8 +443,10 @@ def test_simulation_2bs_4ue_es(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -458,6 +470,9 @@ def test_simulation_2bs_4ue_es(self): random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( self.simulation.ue, self.simulation.bs, ) @@ -587,6 +602,10 @@ def test_simulation_2bs_4ue_es(self): self.param.fss_es, random_number_gen, ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.bs + ) + # what if FSS ES is interferer??? self.simulation.calculate_sinr_ext() @@ -655,6 +674,9 @@ def test_simulation_2bs_4ue_es(self): atol=1e-2, ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) # what if IMT is interferer? self.simulation.calculate_external_interference() @@ -720,8 +742,10 @@ def test_simulation_2bs_4ue_ras(self): 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.geom.set_global_coords( + np.array([20, 70, 110, 170]), + np.array([0, 0, 0, 0]), + ) self.simulation.ue.antenna = np.array( [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)], ) @@ -744,6 +768,9 @@ def test_simulation_2bs_4ue_ras(self): self.simulation.param_system, random_number_gen, ) + self.simulation.intra_imt_paths = PropagationPath.create_default( + self.simulation.ue, self.simulation.bs + ) # test coupling loss method self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss( @@ -779,10 +806,14 @@ def test_simulation_2bs_4ue_ras(self): 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.geometry.height]) + self.simulation.system.geom.set_global_coords( + np.array([-2000]), + np.array([0]), + np.array([self.param.ras.geometry.height]), + ) + self.simulation.paths_between_imt_and_sys = PropagationPath.create_default( + self.simulation.system, self.simulation.ue + ) self.simulation.system.antenna[0].effective_area = 54.9779 # Test gain calculation @@ -862,8 +893,10 @@ def test_beamforming_gains(self): 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.geom.set_global_coords( + np.array([50.000, 43.301, 150.000, 175.000]), + np.array([0.000, 25.000, 0.000, 43.301]), + ) # Physical pointing angles self.assertEqual(self.simulation.bs.antenna[0].azimuth, 0) @@ -872,13 +905,16 @@ def test_beamforming_gains(self): 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]) + self.simulation.ue.geom.set_global_coords( + azim=np.array([180, -90, 90, -90]), + elev=np.array([-30, -15, 15, 30]), + ) + 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], + par, self.simulation.ue.geom.pointn_azim_global[i], + self.simulation.ue.geom.pointn_elev_global[i], ) self.assertEqual(self.simulation.ue.antenna[0].azimuth, 180) self.assertEqual(self.simulation.ue.antenna[0].elevation, -30) @@ -895,8 +931,8 @@ def test_beamforming_gains(self): # Test BS gains # Test pointing vector - phi, theta = self.simulation.bs.get_pointing_vector_to( - self.simulation.ue, + phi, theta = self.simulation.bs.geom.get_global_pointing_vector_to( + self.simulation.ue.geom, ) npt.assert_allclose( phi, np.array([ diff --git a/tests/test_station.py b/tests/test_station.py index 8b60bced7..087e6ec4e 100644 --- a/tests/test_station.py +++ b/tests/test_station.py @@ -60,7 +60,7 @@ def setUp(self): self.station.station_type = StationType.IMT_BS self.station.x = 10 self.station.y = 15 - self.station.height = 6 + self.station.z = 6 self.station.tx_power = 20 self.station.rx_power = -3 par = self.bs_param.get_antenna_parameters() @@ -71,7 +71,7 @@ def setUp(self): self.station2.station_type = StationType.IMT_UE self.station2.x = 10 self.station2.y = 15 - self.station2.height = 6 + self.station2.z = 6 self.station2.tx_power = 17 self.station2.rx_power = 9 par = self.ue_param.get_antenna_parameters() @@ -82,7 +82,7 @@ def setUp(self): self.station3.station_type = StationType.FSS_SS self.station3.x = 10 self.station3.y = 15 - self.station3.height = 6 + self.station3.z = 6 self.station3.tx_power = 20 self.station3.rx_power = -3 self.station3.antenna = AntennaOmni(50) @@ -105,7 +105,7 @@ def test_y(self): def test_height(self): """Test that the station height is set correctly.""" - self.assertEqual(self.station.height, 6) + self.assertEqual(self.station.z, 6) def test_tx_power(self): """Test that the station transmit power is set correctly.""" @@ -143,7 +143,7 @@ def test_ne(self): """Test the inequality operator for stations.""" self.assertFalse(self.station != self.station2) # changing id, x, y, or height should change the result - self.station.height = 9 + self.station.z = 9 self.assertTrue(self.station != self.station2) # self.assertTrue(self.station != self.station3) diff --git a/tests/test_station_factory.py b/tests/test_station_factory.py index 0453ef9b3..b114c2335 100644 --- a/tests/test_station_factory.py +++ b/tests/test_station_factory.py @@ -13,6 +13,8 @@ from sharc.station_factory import StationFactory from sharc.topology.topology_ntn import TopologyNTN from sharc.parameters.parameters_single_space_station import ParametersSingleSpaceStation +from sharc.station_manager import StationManager +from sharc.satellite.ngso.constants import EARTH_RADIUS_M class StationFactoryTest(unittest.TestCase): @@ -52,16 +54,16 @@ def test_generate_imt_base_stations_ntn(self): 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) + npt.assert_equal(ntn_bs.geom.z_global, param_imt.topology.ntn.bs_height) # the azimuth seen from BS antenna npt.assert_almost_equal( - ntn_bs.azimuth[0], + ntn_bs.geom.pointn_azim_global[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.geom.pointn_elev_global[0], -45.0, 1e-2) npt.assert_almost_equal( - ntn_bs.x, param_imt.topology.ntn.bs_height * + ntn_bs.geom.x_global, 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, ) @@ -99,7 +101,7 @@ def test_generate_imt_ue_outdoor_ntn(self): 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) + dist = np.sqrt(ntn_ue.geom.x_global**2 + ntn_ue.geom.y_global**2) # test if the maximum distance is close to the cell radius within a # 100km range npt.assert_almost_equal( @@ -131,21 +133,21 @@ def test_generate_single_space_station(self): param.validate() # experimental from simulator - max_gso_fov = 81.30784 + max_gso_fov = 81.299501 def get_ground_elevation(ss): return np.rad2deg( np.arctan2( - ss.height, + ss.geom.z_global, np.sqrt( - ss.x**2 + - ss.y**2))) + ss.geom.x_global**2 + + ss.geom.y_global**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(space_station.geom.z_global, param.geometry.altitude) npt.assert_almost_equal(get_ground_elevation(space_station), 90) param.geometry.es_lat_deg = max_gso_fov @@ -153,7 +155,7 @@ def get_ground_elevation(ss): 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) + npt.assert_almost_equal(space_station.geom.z_global, 0, 0) param.geometry.es_lat_deg = 0 param.geometry.es_long_deg = max_gso_fov @@ -161,7 +163,7 @@ def get_ground_elevation(ss): 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) + npt.assert_almost_equal(space_station.geom.z_global, 0, 0) param.geometry.es_long_deg = 0 param.geometry.location.fixed.lat_deg = max_gso_fov @@ -169,14 +171,88 @@ def get_ground_elevation(ss): 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) + npt.assert_almost_equal(space_station.geom.z_global, 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) + npt.assert_almost_equal(space_station.geom.z_global, 0, 0) + + def test_single_space_station_pointing(self): + """Basic test for space station generation.""" + + 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.es_altitude = 1200 + param.geometry.location.fixed.lat_deg = -5 + param.geometry.location.fixed.long_deg = 5 + + param.propagate_parameters() + # This should not error on this test: + param.validate() + + imt_center = StationManager(1) + imt_center.geom.set_global_coords( + np.array([0.]), + np.array([0.]), + np.array([0.]), + ) + + # Test point it toward IMT center (0, 0, 0) + param.geometry.azimuth.type = "POINTING_AT_IMT" + param.geometry.elevation.type = "POINTING_AT_IMT" + + space_station = StationFactory.generate_single_space_station(param) + + npt.assert_almost_equal(space_station.geom.get_off_axis_angle(imt_center.geom), 0, 5) + + # Test pointing it toward IMT center (0, 0, 0) + # but in another way + param.geometry.azimuth.type = "POINTING_AT_LAT_LONG_ALT" + param.geometry.elevation.type = "POINTING_AT_LAT_LONG_ALT" + param.geometry.pointing_at_lat = 0 + param.geometry.pointing_at_long = 0 + param.geometry.pointing_at_alt = 1200 + + space_station = StationFactory.generate_single_space_station(param) + + npt.assert_almost_equal(space_station.geom.get_off_axis_angle(imt_center.geom), 0, 5) + + # Test pointing it toward subsatellite. + # In spherical earth model, + # same as pointing toward center of earth + center_of_earth = StationManager(1) + + center_of_earth.geom.set_global_coords( + np.array([0.]), + np.array([0.]), + -np.array([EARTH_RADIUS_M + 1200]), + ) + + param.geometry.azimuth.type = "POINTING_AT_LAT_LONG_ALT" + param.geometry.elevation.type = "POINTING_AT_LAT_LONG_ALT" + param.geometry.pointing_at_lat = -5 + param.geometry.pointing_at_long = 5 + param.geometry.pointing_at_alt = 1200 + + space_station = StationFactory.generate_single_space_station(param) + + npt.assert_almost_equal(space_station.geom.get_off_axis_angle(center_of_earth.geom), 0, 5) if __name__ == '__main__': diff --git a/tests/test_station_factory_ngso.py b/tests/test_station_factory_ngso.py index 191f0257d..9f2359d5c 100644 --- a/tests/test_station_factory_ngso.py +++ b/tests/test_station_factory_ngso.py @@ -3,7 +3,7 @@ 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 +from sharc.support.sharc_geom import CoordinateSystem, lla2ecef import numpy as np import numpy.testing as npt @@ -42,8 +42,8 @@ def setUp(self): self.long = -47.9292 self.alt = 1200 - self.geoconvert = GeometryConverter() - self.geoconvert.set_reference( + self.coord_sys = CoordinateSystem() + self.coord_sys.set_reference( -15.7801, -47.9292, 1200, @@ -55,9 +55,14 @@ def setUp(self): orbits=[orbit_1, orbit_2], 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 + self.param.antenna.pattern = "ITU-R-S.1528-Taylor" + self.param.antenna.itu_r_s_1528.frequency = 43000.0 + self.param.antenna.itu_r_s_1528.bandwidth = 500.0 + self.param.antenna.itu_r_s_1528.antenna_gain = 46.6 + self.param.antenna.itu_r_s_1528.slr = 20.0 + self.param.antenna.itu_r_s_1528.n_side_lobes = 2 + self.param.antenna.itu_r_s_1528.l_r = 1.6 + self.param.antenna.itu_r_s_1528.l_t = 1.6 # Creating an IMT topology # imt_topology = TopologySingleBaseStation( @@ -70,15 +75,15 @@ def setUp(self): rng = np.random.RandomState(seed=self.seed) self.ngso_manager = StationFactory.generate_mss_d2d( - self.param, rng, self.geoconvert) + self.param, rng, self.coord_sys) def test_ngso_manager(self): """Test that the NGSO manager creates the correct number and type of stations.""" 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,)) + self.assertEqual(self.ngso_manager.geom.x_global.shape, (20 * 32 + 12 * 20,)) + self.assertEqual(self.ngso_manager.geom.y_global.shape, (20 * 32 + 12 * 20,)) + self.assertEqual(self.ngso_manager.geom.z_global.shape, (20 * 32 + 12 * 20,)) def test_satellite_antenna_pointing(self): """Test that satellite antennas point to nadir and off-axis angles are correct.""" @@ -88,27 +93,29 @@ def test_satellite_antenna_pointing(self): # y > 0 <=> azimuth < 0 # y < 0 <=> azimuth > 0 npt.assert_array_equal( - np.sign(self.ngso_manager.azimuth), -np.sign(self.ngso_manager.y)) + np.sign(self.ngso_manager.geom.pointn_azim_global), -np.sign(self.ngso_manager.geom.y_global)) # 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.sqrt( - x * x + y * y + z * z, + earth_center.geom.set_global_coords( + np.array([0.]), + np.array([0.]), + -np.sqrt( + x * x + y * y + z * z, + ) ) - self.assertNotAlmostEqual(earth_center.z[0], 0.) + self.assertNotAlmostEqual(earth_center.geom.z_global[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) + off_axis_angle = self.ngso_manager.geom.get_off_axis_angle(earth_center.geom) + distance_to_center_of_earth = self.ngso_manager.geom.get_3d_distance_to( + earth_center.geom) 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, + self.ngso_manager.geom.x_global ** 2 + + self.ngso_manager.geom.y_global ** 2 + + (np.sqrt(x * x + y * y + z * z) + self.ngso_manager.geom.z_global) ** 2, ) npt.assert_allclose(off_axis_angle, 0.0, atol=1e-05) @@ -125,49 +132,70 @@ def test_satellite_coordinate_reversing(self): 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) + self.param, rng, self.coord_sys) + nx, ny, nz = self.coord_sys.enu2ecef( + ngso_original_coord.geom.x_global, + ngso_original_coord.geom.y_global, + ngso_original_coord.geom.z_global, + ) + nazim, nelev = self.coord_sys.angle_enu2ecef( + ngso_original_coord.geom.pointn_azim_global, + ngso_original_coord.geom.pointn_elev_global, + ) + ngso_original_coord.geom.set_global_coords( + nx, ny, nz, nazim, nelev + ) # 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)) + np.sign(ngso_original_coord.geom.pointn_azim_global), -np.sign(ngso_original_coord.geom.y_global)) # 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.]) + earth_center.geom.set_global_coords( + np.array([0.]), + np.array([0.]), + np.array([0.]), + ) - off_axis_angle = ngso_original_coord.get_off_axis_angle(earth_center) + off_axis_angle = ngso_original_coord.geom.get_off_axis_angle(earth_center.geom) npt.assert_allclose(off_axis_angle, 0.0, atol=1e-05) - self.geoconvert.convert_station_3d_to_2d(ngso_original_coord) + # convert stations to enu + nx, ny, nz = self.coord_sys.ecef2enu( + ngso_original_coord.geom.x_global, + ngso_original_coord.geom.y_global, + ngso_original_coord.geom.z_global, + ) + nazim, nelev = self.coord_sys.angle_ecef2enu( + ngso_original_coord.geom.pointn_azim_global, + ngso_original_coord.geom.pointn_elev_global, + ) + ngso_original_coord.geom.set_global_coords( + nx, ny, nz, nazim, nelev + ) 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, + self.ngso_manager.geom.x_global, + ngso_original_coord.geom.x_global, atol=1e-500) npt.assert_allclose( - self.ngso_manager.z, - ngso_original_coord.z, + self.ngso_manager.geom.y_global, + ngso_original_coord.geom.y_global, atol=1e-500) npt.assert_allclose( - self.ngso_manager.height, - ngso_original_coord.height, + self.ngso_manager.geom.z_global, + ngso_original_coord.geom.z_global, atol=1e-500) npt.assert_allclose( - self.ngso_manager.azimuth, - ngso_original_coord.azimuth, + self.ngso_manager.geom.pointn_azim_global, + ngso_original_coord.geom.pointn_azim_global, atol=1e-500) npt.assert_allclose( - self.ngso_manager.elevation, - ngso_original_coord.elevation, + self.ngso_manager.geom.pointn_elev_global, + ngso_original_coord.geom.pointn_elev_global, atol=1e-500) diff --git a/tests/test_station_manager.py b/tests/test_station_manager.py index db86d1e61..8259b5eb2 100644 --- a/tests/test_station_manager.py +++ b/tests/test_station_manager.py @@ -61,11 +61,12 @@ def setUp(self): 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 + self.station_manager.geom.set_global_coords( + np.array([10, 20, 30]), + np.array([15, 25, 35]), + np.array([1, 2, 3]), + ) + self.station_manager.geom.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]) @@ -78,11 +79,12 @@ def setUp(self): 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 + self.station_manager2.geom.set_global_coords( + np.array([100, 200]), + np.array([105, 250]), + np.array([4, 5]), + ) + self.station_manager2.geom.intersite_dist = 100.0 # this is for downlink self.station_manager2.tx_power = dict({0: [25], 1: [28, 35]}) self.station_manager2.rx_power = np.array([-50, -35]) @@ -93,11 +95,12 @@ def setUp(self): 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 + self.station_manager3.geom.set_global_coords( + np.array([300]), + np.array([400]), + np.array([2]), + ) + self.station_manager3.geom.intersite_dist = 100.0 # this is for uplink self.station_manager3.tx_power = 22 self.station_manager3.rx_power = np.array([-50, -35]) @@ -111,7 +114,7 @@ def setUp(self): self.station.id = 0 self.station.x = 10 self.station.y = 15 - self.station.height = 1 + self.station.z = 1 self.station.tx_power = 30 self.station.rx_power = -50 par = self.ue_param.get_antenna_parameters() @@ -122,7 +125,7 @@ def setUp(self): self.station2.id = 1 self.station2.x = 20 self.station2.y = 25 - self.station2.height = 2 + self.station2.z = 2 self.station2.tx_power = 35 self.station2.rx_power = -35 par = self.bs_param.get_antenna_parameters() @@ -150,55 +153,55 @@ def test_station_type(self): def test_x(self): """Test getting and setting x-coordinates for stations.""" # get a single value from the original array - self.assertEqual(self.station_manager.x[0], 10) + self.assertEqual(self.station_manager.geom.x_global[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.geom.x_global[[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.geom.x_global[[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.geom.x_global, [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]) + self.station_manager.geom._x_global[0] = 8 + npt.assert_array_equal(self.station_manager.geom.x_global[[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.geom._x_global[[1, 2]] = [16, 32] + npt.assert_array_equal(self.station_manager.geom.x_global, [8, 16, 32]) def test_y(self): """Test getting and setting y-coordinates for stations.""" # get a single value from the original array - self.assertEqual(self.station_manager.y[0], 15) + self.assertEqual(self.station_manager.geom.y_global[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.geom.y_global[[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.geom.y_global[[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.geom.y_global, [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]) + self.station_manager.geom._y_global[1] = 9 + npt.assert_array_equal(self.station_manager.geom.y_global[[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.geom._y_global[[0, 2]] = [7, 21] + npt.assert_array_equal(self.station_manager.geom.y_global, [7, 9, 21]) def test_height(self): """Test getting and setting station heights.""" # get a single value from the original array - self.assertEqual(self.station_manager.height[0], 1) + self.assertEqual(self.station_manager.geom.z_global[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.geom.z_global[[0, 2]], [1, 3]) # get values in reverse order npt.assert_array_equal( - self.station_manager.height[[2, 1, 0]], [3, 2, 1], + self.station_manager.geom.z_global[[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.geom.z_global, [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]) + self.station_manager.geom._z_global[1] = 7 + npt.assert_array_equal(self.station_manager.geom.z_global[[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.geom._z_global[[0, 2]] = [5, 4] + npt.assert_array_equal(self.station_manager.geom.z_global, [5, 7, 4]) def test_tx_power(self): """Test getting and setting transmit power for stations.""" @@ -315,7 +318,7 @@ def test_station_list(self): def test_distance_to(self): """Test 2D distance calculation between station managers.""" ref_distance = np.array([[356.405, 180.277]]) - distance = self.station_manager3.get_distance_to(self.station_manager2) + distance = self.station_manager3.geom.get_local_distance_to(self.station_manager2.geom) npt.assert_allclose(distance, ref_distance, atol=1e-2) ref_distance = np.asarray([ @@ -323,14 +326,14 @@ def test_distance_to(self): [113.137, 288.140], [98.994, 274.089], ]) - distance = self.station_manager.get_distance_to(self.station_manager2) + distance = self.station_manager.geom.get_local_distance_to(self.station_manager2.geom) npt.assert_allclose(distance, ref_distance, atol=1e-2) def test_3d_distance_to(self): """Test 3D distance calculation between station managers.""" ref_distance = np.asarray([[356.411, 180.302]]) - distance = self.station_manager3.get_3d_distance_to( - self.station_manager2, + distance = self.station_manager3.geom.get_3d_distance_to( + self.station_manager2.geom, ) npt.assert_allclose(distance, ref_distance, atol=1e-2) @@ -339,29 +342,31 @@ def test_3d_distance_to(self): [113.154, 288.156], [99, 274.096], ]) - distance = self.station_manager.get_3d_distance_to( - self.station_manager2, + distance = self.station_manager.geom.get_3d_distance_to( + self.station_manager2.geom, ) npt.assert_allclose(distance, ref_distance, atol=1e-2) def test_wrap_around(self): """Test wrap-around distance and angle calculations between managers.""" 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_manager.geom.set_global_coords( + np.array([0, 150]), + np.array([0, -32]), + np.array([4, 5]), + ) + self.station_manager.geom.intersite_dist = 100.0 self.station_manager2 = StationManager(3) - 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]) + self.station_manager2.geom.set_global_coords( + np.array([10, 200, 30]), + np.array([15, 250, -350]), + np.array([1, 2, 3]), + ) # 2D Distance - d_2D, d_3D, phi, theta = self.station_manager.get_dist_angles_wrap_around( - self.station_manager2, ) + d_2D, d_3D, phi, theta = self.station_manager.geom.get_global_dist_angles_wrap_around( + self.station_manager2.geom, ) ref_d_2D = np.asarray([ [18.03, 150.32, 85.39], [147.68, 181.12, 205.25], @@ -392,8 +397,8 @@ def test_pointing_vector_to(self): """Test calculation of pointing vectors between station managers.""" eps = 1e-1 # Test 1 - phi, theta = self.station_manager.get_pointing_vector_to( - self.station_manager2, + phi, theta = self.station_manager.geom.get_global_pointing_vector_to( + self.station_manager2.geom, ) npt.assert_allclose( phi, np.array([ @@ -411,8 +416,8 @@ def test_pointing_vector_to(self): ) # Test 2 - phi, theta = self.station_manager2.get_pointing_vector_to( - self.station_manager, + phi, theta = self.station_manager2.geom.get_global_pointing_vector_to( + self.station_manager.geom, ) npt.assert_allclose( phi, np.array([ @@ -428,15 +433,15 @@ def test_pointing_vector_to(self): ) # Test 3 - phi, theta = self.station_manager3.get_pointing_vector_to( - self.station_manager2, + phi, theta = self.station_manager3.geom.get_global_pointing_vector_to( + self.station_manager2.geom, ) 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, + phi, theta = self.station_manager2.geom.get_global_pointing_vector_to( + self.station_manager3.geom, ) npt.assert_allclose(phi, np.array([[55.86], [56.31]]), atol=eps) npt.assert_allclose(theta, np.array([[90.32], [90.95]]), atol=eps) @@ -444,116 +449,128 @@ def test_pointing_vector_to(self): def test_off_axis_angle(self): """Test calculation of off-axis angles between station managers.""" 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]) + sm1.geom.set_global_coords( + np.array([0.]), + np.array([0.]), + np.array([0.]), + np.array([0.]), + np.array([0.]), + ) 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]) + sm2.geom.set_global_coords( + np.array([100, 100, 0, 100, 100, 100]), + np.array([0, 0, 100, 100, 100, 100]), + np.array([0, 100, 0, 0, 100, 100]), + np.array([180, 180, 180, 180, 180, 225]), + np.array([0, 0, 0, 0, 0, 0]), + ) phi_ref = np.array([[0, 45, 90, 45, 54.73, 54.73]]) - npt.assert_allclose(phi_ref, sm1.get_off_axis_angle(sm2), atol=1e-2) + npt.assert_allclose(phi_ref, sm1.geom.get_off_axis_angle(sm2.geom), atol=1e-2) ####################################################################### 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]) + sm3.geom.set_global_coords( + np.array([0]), + np.array([0]), + np.array([0]), + np.array([45]), + np.array([0]), + ) 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]) + sm4.geom.set_global_coords( + np.array([100, 60]), + np.array([100, 80]), + np.array([100, 100]), + np.array([180, 180]), + np.array([0, 0]), + ) phi_ref = np.array([[35.26, 45.57]]) - npt.assert_allclose(phi_ref, sm3.get_off_axis_angle(sm4), atol=1e-2) + npt.assert_allclose(phi_ref, sm3.geom.get_off_axis_angle(sm4.geom), atol=1e-2) ####################################################################### 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]) + sm5.geom.set_global_coords( + np.array([0]), + np.array([0]), + np.array([0]), + np.array([0]), + np.array([45]), + ) 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]) + sm6.geom.set_global_coords( + np.array([100, 100]), + np.array([0, 0]), + np.array([100, 100]), + np.array([180, 180]), + np.array([0, 0]), + ) phi_ref = np.array([[0, 0]]) - npt.assert_allclose(phi_ref, sm5.get_off_axis_angle(sm6), atol=1e-2) + npt.assert_allclose(phi_ref, sm5.geom.get_off_axis_angle(sm6.geom), atol=1e-2) ####################################################################### 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]) + sm6.geom.set_global_coords( + np.array([0]), + np.array([0]), + np.array([100]), + np.array([0]), + np.array([270]), + ) 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]) + sm7.geom.set_global_coords( + np.array([0, 100]), + np.array([0, 0]), + np.array([0, 0]), + np.array([180, 180]), + 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.geom.get_off_axis_angle(sm7.geom), atol=1e-2) def test_elevation(self): """Test calculation of elevation angles between station managers.""" sm1 = StationManager(1) - sm1.x = np.array([0]) - sm1.y = np.array([0]) - sm1.z = np.array([10]) - sm1.height = np.array([10]) + sm1.geom.set_global_coords( + np.array([0]), + np.array([0]), + 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.z = np.array([10, 20, 5, 0, 20, 20]) - sm2.height = np.array([10, 20, 5, 0, 20, 20]) + sm2.geom.set_global_coords( + np.array([10, 10, 0, 0, 30, 20]), + np.array([0, 0, 5, 10, 30, 20]), + 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.geom.get_local_elevation(sm2.geom), 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]) + sm3.geom.set_global_coords( + np.array([0, 30]), + np.array([0, 0]), + np.array([10, 10]), + ) sm4 = StationManager(2) - sm4.x = np.array([10, 10]) - sm4.y = np.array([0, 0]) - sm4.z = np.array([10, 20]) - sm4.height = np.array([10, 20]) + sm4.geom.set_global_coords( + np.array([10, 10]), + np.array([0, 0]), + 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.geom.get_local_elevation(sm4.geom), atol=1e-2) if __name__ == '__main__': diff --git a/tests/test_topology_imt_mss_dc.py b/tests/test_topology_imt_mss_dc.py index 142af61ac..69a715ca5 100644 --- a/tests/test_topology_imt_mss_dc.py +++ b/tests/test_topology_imt_mss_dc.py @@ -5,7 +5,8 @@ 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 +from sharc.support.sharc_geom import CoordinateSystem, lla2ecef +from sharc.satellite.utils.sat_utils import calc_elevation class TestTopologyImtMssDc(unittest.TestCase): @@ -45,22 +46,22 @@ def setUp(self): 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) + self.coordinate_system = CoordinateSystem() + self.coordinate_system.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.coordinate_system.ref_lat, + self.coordinate_system.ref_long, + self.coordinate_system.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) + self.params, self.coordinate_system) def test_initialization(self): """Test initialization of the IMT MSS-DC topology.""" @@ -107,18 +108,22 @@ def test_calculate_coordinates(self): # by default, satellites should always point to nadir (earth center) ref_earth_center = StationManager(1) - ref_earth_center.x = self.earth_center_x.flatten() - ref_earth_center.y = self.earth_center_y.flatten() - ref_earth_center.z = self.earth_center_z.flatten() + ref_earth_center.geom.set_global_coords( + self.earth_center_x.flatten(), + self.earth_center_y.flatten(), + self.earth_center_z.flatten(), + ) 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 + ref_space_stations.geom.set_global_coords( + self.imt_mss_dc_topology.space_station_x, + self.imt_mss_dc_topology.space_station_y, + self.imt_mss_dc_topology.space_station_z, + ) - phi, theta = ref_space_stations.get_pointing_vector_to( - ref_earth_center) + phi, theta = ref_space_stations.geom.get_global_pointing_vector_to( + ref_earth_center.geom) npt.assert_array_almost_equal( np.squeeze( phi[center_beam_idxs]), @@ -152,6 +157,51 @@ def test_visible_satellites(self): # oblateness npt.assert_array_less(min_elevation_angle, xy_plane_elevations) + def test_minimum_service_angle(self): + """Test minimum visibility angle for service grid service.""" + orbit = ParametersOrbit( + n_planes=1, + sats_per_plane=1, + phasing_deg=3.9, + long_asc_deg=18.0, + inclination_deg=54.5, + perigee_alt_km=525, + apogee_alt_km=525, + ) + self.coordinate_system.set_reference(0.0, 0.0, 0) + + self.params.orbits = [orbit] + self.params.beam_positioning.type = "SERVICE_GRID" + self.params.beam_positioning.service_grid.grid_in_zone.type = "CIRCLE" + self.params.beam_positioning.service_grid.grid_in_zone.circle.center_lat = 0.0 + self.params.beam_positioning.service_grid.grid_in_zone.circle.center_lon = 0.0 + self.params.beam_positioning.service_grid.grid_in_zone.circle.radius_km = 10 * 111.0 + self.params.validate("") + + self.imt_mss_dc_topology = TopologyImtMssDc( + self.params, self.coordinate_system) + + n_previous_selected = np.inf + for a in [5, 50, 80]: + self.imt_mss_dc_topology.orbit_params.beam_positioning.service_grid.minimum_service_angle = a + self.imt_mss_dc_topology.calculate_coordinates( + random_number_gen=np.random.RandomState(8)) + + lon_lat_grid = self.params.beam_positioning.service_grid.lon_lat_grid + elev_from_bs = calc_elevation( + lon_lat_grid[1], + self.imt_mss_dc_topology.lat[0], + lon_lat_grid[0], + self.imt_mss_dc_topology.lon[0], + sat_height=self.imt_mss_dc_topology.height[0], + es_height=0.0, + ) + n_selected = np.sum(elev_from_bs >= a) + self.assertLess(n_selected, n_previous_selected) + self.assertLess(n_selected, len(elev_from_bs)) + self.assertEqual(n_selected, self.imt_mss_dc_topology.num_base_stations) + n_previous_selected = n_selected + if __name__ == '__main__': unittest.main()