diff --git a/.github/workflows/unittest-lint.yml b/.github/workflows/unittest-lint.yml new file mode 100644 index 000000000..8216d5634 --- /dev/null +++ b/.github/workflows/unittest-lint.yml @@ -0,0 +1,45 @@ +name: Python application + +on: + push: + branches: + - master + - development + - 'v1.*' + pull_request: + branches: + - master + - development + - 'v1.*' + types: + - opened + - reopened + - synchronize + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v3 + with: + python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82,W191,W291,W292,W293,W391,E131,E2,E3,E266 --ignore=E266 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with unittest + run: | + python -m unittest discover -s tests \ No newline at end of file diff --git a/.gitignore b/.gitignore index cd5e29c00..28544e500 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -/sharc/logfile.log /sharc/output/*.txt /sharc/output/*.png /sharc/output_*/* +/sharc/campaigns/*/output/ + __pycache__ .spyproject @@ -17,6 +18,7 @@ __pycache__/ # Distribution / packaging .Python +venv/ env/ build/ develop-eggs/ @@ -70,3 +72,30 @@ target/ # pyenv python configuration file .python-version /sharc/antenna/figs +/sharc/.venv/ +.DS_Store +.venv/ +venv/ +myenv/pyvenv.cfg +myenv/Scripts/activate +myenv/Scripts/activate.bat +myenv/Scripts/Activate.ps1 +myenv/Scripts/deactivate.bat +myenv/Scripts/pip.exe +myenv/Scripts/pip3.12.exe +myenv/Scripts/pip3.exe +myenv/Scripts/python.exe +myenv/Scripts/pythonw.exe + +# Ignore output files +[IMT]*.txt +[SYS]*.txt +IMT_*.txt +SYS_*.txt + + +# Ignores campaigns output + +/sharc/campaigns/imt_*/output/output_imt*/*.csv +/sharc/campaigns/imt_*/output/output_imt*/*.ini +/sharc/campaigns/imt_*/output/Figs/*.png diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..e5cb3b23f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,16 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.python", + "ms-python.debugpy", + "ms-python.flake8", + "ms-python.autopep8" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..61dfa9517 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: SHARC main_cli.py", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/sharc/main_cli.py", + "console": "integratedTerminal", + "args": [ + "-p", + "${workspaceFolder}/sharc/input/parameters.yaml" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..3dfbb64b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.autopep8", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.rulers": [ + 120 + ] + }, + "flake8.args": [ + "--count", + "--max-line-length=127", + "--statistics", + "--ignore=W605,W504,E266,F811,E131,C0209,C0103,C0115,C0115,C0116,C0201" + ], + "autopep8.args": [ + "--max-line-length 120", + "--agressive" + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..bda117610 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,202 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +## Types of Contributions + +### Report Bugs + +Report bugs at https://github.com/Radio-Spectrum/SHARC/issues + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +### Fix Bugs +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +### Implement Features +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +### Write Documentation +SHARC could always use more documentation, whether as part of the +official SHARC docs, in docstrings, or even on the web in blog posts, +articles, and such. + +When implementing a new feature such as a new propagation model or a new antenna model, +add the documentation to the Wiki. + +### Submit Feedback +The best way to send feedback is to file an issue at https://github.com/Radio-Spectrum/SHARC/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +## Branching model + +The branching model is based on this excelent post written by Vincent Driessen. + +A Successful Git Branching Model - https://nvie.com/posts/a-successful-git-branching-model/ + +### The main branches +The central repo holds two main branches with an infinite lifetime: + +* `master` - main branch where the source code of HEAD always reflects a production-ready state. +* `development` - the main branch where the source code of HEAD always reflects a state with the latest +delivered development changes for the next release + +When the source code in the `development` branch reaches a stable point and is ready to be released, +all of the changes should be merged back into `master` and then tagged with a release number. + +### supporting branches +The supporting porting branches to aid parallel development between team members, ease tracking of features, +prepare for production releases and to assist in quickly fixing live production problems. +Unlike the main branches, these branches always have a limited life time, since they will be removed eventually. + +* `feature` branches +* `release` branches +* `hotfix` branches + +### feature branches +Feature branches are used to develop new features. For example, an addidion of a new antenna model or +a new propagation model. + +May branch off from: + `develop` +Must merge back into: + `develop` +Branch naming convention: + anything except `master`, `development`, `release-*`, or `hotfix-*`. For example, `feature-propagation-619`. + +### release branches +Release branches support preparation of a new production release. They allow for minor bug fixes and +preparing meta-data for a release (version number, build dates, etc.) + +May branch off from: + `develop` +Must merge back into: + `develop` and `master`` +Branch naming convention: + `release-*` + +### hotfix branches +Used when a critical bug in a production version must be resolved immediately. + +May branch off from: + `master` +Must merge back into: + `develop` and `master` +Branch naming convention: + `hotfix-*` + +## Languages + +### Python + +The [PEP8 style guide](https://www.python.org/dev/peps/pep-0008/) is authoritative. + +#### Type annotations +All new code should be fully type-annotated. +For reference, please look at this [type hints cheat sheet for Python 3](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html). + +#### Documentation +* Document all public functions and keep those docs up to date when you make changes +* We use [Google style docstrings](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) in our codebase (if you find a docstring missing or in the wrong format you're welcome to fix it.) + * For VSCode users, [Python Docstring Generator](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) plugin is recommended +Example: + +``` +def foo(arg1: str) -> int: + """Returns the length of arg1. + + Args: + arg1 (str): string to calculate the length of + + Returns: the length of the provided parameter + """ + return len(arg1) +``` + + +## Get Started! +Ready to contribute? Here's how to set up `sharc` for local development. + +1. Fork the `sharc` repo on GitHub. +2. Clone your fork locally: + + `$ git clone git@github.com:your_name_here/sharc.git` + + `$ cd SHARC/` + +3. Install any version of python 3 and the virutalenv module in your system (SHARC has been tested from versions 3.8 and above). +4. Install your local copy into a virtualenv. + + `$ cd sharc` + + `$ python3 -m venv .venv` + + `$ source .venv/bin/activate` + + You shall see (.venv) in the begining of your command prompt indicating that the virutalenv has been activated. + Now, instalal the dependencies for development. + `$ pip install -r requirements.txt` + + Install sharc on your local enviroment. + Run from the source code directory root: + `$ pip install -e .` + +4. Create a branch for local development:: + + `$ git checkout -b name-of-your-bugfix-or-feature` + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox: + + `$ flake8 sharc tests` + + `$ python setup.py test or py.test` + + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub:: + + `$ git add .` + + `$ git commit -m "Your detailed description of your changes."` + + `$ git push origin name-of-your-bugfix-or-feature` + +7. Submit a pull request through the GitHub website. + +# Pull Request Guidelines + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check + https://travis-ci.org/edgar-souza/sharc/pull_requests + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + +$ py.test tests.test_sharc + diff --git a/README.md b/README.md index 6c9f47a0a..bc431f2b7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # SHARC -Simulator for use in sharing and compatibility studies of radio communication systems +Welcome to SHARC, a simulator for use in SHARing and Compatibility studies of radiocommunication systems. The development of this software is being lead by the Telecommunications Regulatory Authority (TRA) of Brazil, ANATEL, and it implements the framework proposed by Recommendation ITU-R M.2101 for "modelling and simulation of IMT networks and systems for use in sharing and compatibility studies". + +Please refer to `CONTRIBUTING.md` for contribution guidelines and how to setup your environment. \ No newline at end of file diff --git a/profile/P619/detailed_profile_p619.py b/profile/P619/detailed_profile_p619.py new file mode 100644 index 000000000..4a0e8a73e --- /dev/null +++ b/profile/P619/detailed_profile_p619.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jun 19 10:00:00 2024 + +Profile script for PropagationP619 +""" + +import os +import csv +import numpy as np +import matplotlib +from sharc.propagation.propagation_p619 import PropagationP619 +import cProfile +import pstats +import io +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt # noqa: E402 + +# Constants +frequency_MHz = 2680.0 +space_station_alt_m = 35786.0 * 1000 # Geostationary orbit altitude in meters +earth_station_alt_m = 1000.0 +earth_station_lat_deg = -15.7801 +earth_station_long_diff_deg = 0.0 # Not used in the loss calculation +season = "SUMMER" +apparent_elevation = np.arange(0, 90) # Elevation angles from 0 to 90 degrees +surf_water_vapour_density = 2.5 + +# Initialize the propagation model +random_number_gen = np.random.RandomState(101) +propagation = PropagationP619(random_number_gen=random_number_gen, + space_station_alt_m=space_station_alt_m, + earth_station_alt_m=earth_station_alt_m, + earth_station_lat_deg=earth_station_lat_deg, + earth_station_long_diff_deg=earth_station_long_diff_deg, + season=season) + + +def profile_get_atmospheric_gasses_loss(): + losses = [] + for elevation in apparent_elevation: + loss = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz, + apparent_elevation=elevation, + surf_water_vapour_density=surf_water_vapour_density, + lookupTable=False) + losses.append(loss) + return losses + + +# Profile the function +pr = cProfile.Profile() +pr.enable() +profile_get_atmospheric_gasses_loss() +pr.disable() + +# Create profile results directory +output_dir = os.path.join(os.path.dirname(__file__), 'profile_results') +os.makedirs(output_dir, exist_ok=True) + +# Save profiling results to CSV +profile_data = io.StringIO() +ps = pstats.Stats(pr, stream=profile_data).sort_stats(pstats.SortKey.CUMULATIVE) +ps.print_stats() + +profile_data.seek(0) +lines = profile_data.readlines() + +output_file = os.path.join(output_dir, 'profiling_results.csv') +with open(output_file, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(['Function', 'Calls', 'Total Time', 'Per Call', 'Cumulative Time', 'Per Call (Cum)']) + for line in lines[1:]: + if line.strip() == "" or "percall" in line: + continue + parts = line.split() + if len(parts) < 6: + continue + func_name = " ".join(parts[5:]) + writer.writerow([func_name, parts[0], parts[2], parts[3], parts[4], parts[1]]) + +print(f"Profiling results saved to {output_file}") + +# Load profiling results from CSV +functions = [] +cumulative_times = [] + +with open(output_file, mode='r') as file: + reader = csv.reader(file) + next(reader) # Skip header + for row in reader: + try: + cumulative_times.append(float(row[4])) + functions.append(row[0]) + except ValueError: + continue + +# Sort the data +sorted_indices = sorted(range(len(cumulative_times)), key=lambda k: cumulative_times[k], reverse=True) +sorted_functions = [functions[i] for i in sorted_indices][:5] +sorted_cumulative_times = [cumulative_times[i] for i in sorted_indices][:5] + +# Plot profiling results +plt.figure(figsize=(14, 8)) +plt.barh(sorted_functions, sorted_cumulative_times, color='blue') +plt.xlabel("Cumulative Time (s)") +plt.ylabel("Function") +plt.title("Top 5 Functions by Cumulative Time") +plt.grid(True) +plt.tight_layout() # Adjust layout to fit labels +plot_file = os.path.join(output_dir, 'profiling_results_bar.png') +plt.savefig(plot_file) +print(f"Profiling results bar plot saved to {plot_file}") diff --git a/profile/P619/profile_with_and_without_lookuptable_p619.py b/profile/P619/profile_with_and_without_lookuptable_p619.py new file mode 100644 index 000000000..294ecac3c --- /dev/null +++ b/profile/P619/profile_with_and_without_lookuptable_p619.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jun 19 10:00:00 2024 + +Profile script for PropagationP619 +""" + +import os +import time +import csv +import numpy as np +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt # noqa: E402 +from sharc.propagation.propagation_p619 import PropagationP619 # noqa: E402 + +# Constants +frequency_MHz = 2680.0 +space_station_alt_m = 35786.0 * 1000 # Geostationary orbit altitude in meters +earth_station_alt_m = 1000.0 +earth_station_lat_deg = -15.7801 +earth_station_long_diff_deg = 0.0 # Not used in the loss calculation +season = "SUMMER" +apparent_elevation = np.arange(0, 90) # Elevation angles from 0 to 90 degrees +surf_water_vapour_density = 2.5 + +# Initialize the propagation model +random_number_gen = np.random.RandomState(101) +propagation = PropagationP619(random_number_gen=random_number_gen, + space_station_alt_m=space_station_alt_m, + earth_station_alt_m=earth_station_alt_m, + earth_station_lat_deg=earth_station_lat_deg, + earth_station_long_diff_deg=earth_station_long_diff_deg, + season=season) + +# Profile with lookup table +start_time = time.time() +losses_with_table = [] +for elevation in apparent_elevation: + loss = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz, + apparent_elevation=elevation, + surf_water_vapour_density=surf_water_vapour_density) + losses_with_table.append(loss) +time_with_table = time.time() - start_time + +# Profile without lookup table +start_time = time.time() +losses_without_table = [] +for elevation in apparent_elevation: + loss = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz, + apparent_elevation=elevation, + surf_water_vapour_density=surf_water_vapour_density, + lookupTable=False) + losses_without_table.append(loss) +time_without_table = time.time() - start_time + +# Save profiling results to CSV +output_dir = os.path.join(os.path.dirname(__file__), 'profile_results') +os.makedirs(output_dir, exist_ok=True) +output_file = os.path.join(output_dir, 'profiling_results_table.csv') + +with open(output_file, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(['Method', 'Time (s)']) + writer.writerow(['With Lookup Table', time_with_table]) + writer.writerow(['Without Lookup Table', time_without_table]) + +print(f"Profiling results saved to {output_file}") + +# Save atmospheric loss plots +plt.figure() +plt.plot(apparent_elevation, losses_with_table, label='With Lookup Table') +plt.plot(apparent_elevation, losses_without_table, label='Without Lookup Table') +plt.xlabel("Apparent Elevation (deg)") +plt.ylabel("Loss (dB)") +plt.title("Atmospheric Gasses Attenuation") +plt.legend() +plt.grid(True) +plot_file = os.path.join(output_dir, 'atmospheric_loss_comparison.png') +plt.savefig(plot_file) +print(f"Atmospheric loss plot saved to {plot_file}") + + +import matplotlib # noqa: E402 +matplotlib.use('Agg') +import matplotlib.pyplot as plt # noqa: E402 + +# Load profiling results from CSV +input_file = os.path.join(os.path.dirname(__file__), 'profile_results', 'profiling_results_table.csv') +methods = [] +times = [] + +with open(input_file, mode='r') as file: + reader = csv.reader(file) + next(reader) # Skip header + for row in reader: + methods.append(row[0]) + times.append(float(row[1])) + +# Plot profiling results +plt.figure() +plt.bar(methods, times, color=['blue', 'orange']) +plt.xlabel("Method") +plt.ylabel("Time (s)") +plt.title("Profiling Results for PropagationP619") +plt.grid(True) +plot_file = os.path.join(os.path.dirname(__file__), 'profile_results', 'profiling_results_bar_table.png') +plt.savefig(plot_file) +print(f"Profiling results bar plot saved to {plot_file}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..4ea10e8b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,119 @@ +asttokens==2.4.1 +attrs==23.2.0 +certifi==2024.7.4 +click==8.1.7 +colorama==0.4.6 +comm==0.2.2 +contourpy==1.2.0 +cycler==0.12.1 +debugpy==1.8.2 +decorator==5.1.1 +executing==2.0.1 +fastjsonschema==2.20.0 +fonttools==4.49.0 +geopandas==1.0.1 +ipykernel==6.29.5 +ipython==8.26.0 +jedi==0.19.1 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +kaleido==0.2.1 +kiwisolver==1.4.5 +matplotlib==3.8.3 +matplotlib-inline==0.1.7 +nbformat==5.10.4 +nest-asyncio==1.6.0 +numpy==1.26.4 +packaging==23.2 +pandas==2.2.2 +parso==0.8.4 +pillow==10.2.0 +platformdirs==4.2.2 +plotly==5.23.0 +prompt_toolkit==3.0.47 +psutil==6.0.0 +pure_eval==0.2.3 +pyaml==23.12.0 +Pygments==2.18.0 +pyogrio==0.9.0 +pyparsing==3.1.1 +pyproj==3.6.1 +python-dateutil==2.9.0.post0 +pytz==2024.1 +PyYAML==6.0.1 +pyzmq==26.0.3 +referencing==0.35.1 +rpds-py==0.19.1 +scipy==1.12.0 +shapely==2.0.3 +six==1.16.0 +stack-data==0.6.3 +tenacity==9.0.0 +tornado==6.4.1 +traitlets==5.14.3 +tzdata==2024.1 +wcwidth==0.2.13 +geopandas==1.0.1 +multipledispatch==1.0.0 +asttokens==2.4.1 +attrs==23.2.0 +certifi==2024.7.4 +click==8.1.7 +colorama==0.4.6 +comm==0.2.2 +contourpy==1.2.0 +cycler==0.12.1 +debugpy==1.8.2 +decorator==5.1.1 +executing==2.0.1 +fastjsonschema==2.20.0 +fonttools==4.49.0 +geopandas==1.0.1 +ipykernel==6.29.5 +ipython==8.26.0 +jedi==0.19.1 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +kaleido==0.2.1 +kiwisolver==1.4.5 +matplotlib==3.8.3 +matplotlib-inline==0.1.7 +nbformat==5.10.4 +nest-asyncio==1.6.0 +numpy==1.26.4 +packaging==23.2 +pandas==2.2.2 +parso==0.8.4 +pillow==10.2.0 +platformdirs==4.2.2 +plotly==5.23.0 +prompt_toolkit==3.0.47 +psutil==6.0.0 +pure_eval==0.2.3 +pyaml==23.12.0 +Pygments==2.18.0 +pyogrio==0.9.0 +pyparsing==3.1.1 +pyproj==3.6.1 +python-dateutil==2.9.0.post0 +pytz==2024.1 +PyYAML==6.0.1 +pyzmq==26.0.3 +referencing==0.35.1 +rpds-py==0.19.1 +scipy==1.12.0 +shapely==2.0.3 +six==1.16.0 +stack-data==0.6.3 +tenacity==9.0.0 +tornado==6.4.1 +traitlets==5.14.3 +tzdata==2024.1 +wcwidth==0.2.13 +geopandas==1.0.1 +multipledispatch==1.0.0 +area==1.1.1 diff --git a/setup.py b/setup.py index 4fd37be92..0b177c54b 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from setuptools import setup +from setuptools import setup, find_packages with open('README.rst') as readme_file: readme = readme_file.read() @@ -26,9 +26,7 @@ author="Edgar Souza", author_email='edgar@anatel.gov.br', url='https://github.com/edgar-souza/sharc', - packages=[ - 'sharc', - ], + packages=find_packages(include=['sharc', 'sharc.*']), package_dir={'sharc': 'sharc'}, entry_points={ diff --git a/sharc/__init__.py b/sharc/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/__init__.py +++ b/sharc/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/antenna/__init__.py b/sharc/antenna/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/antenna/__init__.py +++ b/sharc/antenna/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/antenna/antenna.py b/sharc/antenna/antenna.py index 3f06d3188..f6d4283e6 100644 --- a/sharc/antenna/antenna.py +++ b/sharc/antenna/antenna.py @@ -8,37 +8,34 @@ from abc import ABC, abstractmethod import numpy as np + class Antenna(ABC): """ Abstract antenna class. All antenna classes must inherit from it. - + Methods ------- calculate_gain: calculates the antenna gain in the given directions """ - + def __init__(self): self.beams_list = [] self.w_vec_list = [] - - + @abstractmethod def calculate_gain(self, *args, **kwargs) -> np.array: """ Calculates the antenan gain. - """ - pass - - + """ + 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] """ - pass \ No newline at end of file diff --git a/sharc/antenna/antenna_beamforming_imt.py b/sharc/antenna/antenna_beamforming_imt.py index 2aa1354d3..8a9b21582 100644 --- a/sharc/antenna/antenna_beamforming_imt.py +++ b/sharc/antenna/antenna_beamforming_imt.py @@ -5,10 +5,9 @@ @author: Calil """ +import sys import numpy as np import matplotlib.pyplot as plt -import sys -import os from sharc.antenna.antenna_element_imt_m2101 import AntennaElementImtM2101 from sharc.antenna.antenna_element_imt_f1336 import AntennaElementImtF1336 @@ -16,7 +15,7 @@ from sharc.antenna.antenna import Antenna from sharc.support.enumerations import StationType from sharc.support.named_tuples import AntennaPar -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt class AntennaBeamformingImt(Antenna): @@ -67,7 +66,9 @@ def __init__(self, par: AntennaPar, azimuth: float, elevation: float): elif (par.element_pattern).upper() == "FIXED": self.element = AntennaElementImtConst(par) else: - sys.stderr.write("ERROR\nantenna element type {} not supported".format(par.element_pattern)) + sys.stderr.write( + f"ERROR\nantenna element type {par.element_pattern} not supported", + ) sys.exit(1) self.azimuth = azimuth @@ -79,9 +80,9 @@ def __init__(self, par: AntennaPar, azimuth: float, elevation: float): self.n_cols = par.n_columns self.dh = par.element_horiz_spacing self.dv = par.element_vert_spacing - + self.adjacent_antenna_model = par.adjacent_antenna_model - + # Beamforming normalization self.normalize = par.normalization self.co_correction_factor_list = [] @@ -105,13 +106,17 @@ def add_beam(self, phi_etilt: float, theta_etilt: float): theta_etilt (float): elevation electrical tilt angle [degrees] """ phi, theta = self.to_local_coord(phi_etilt, theta_etilt) - self.beams_list.append((np.asscalar(phi), np.asscalar(theta-90))) - self.w_vec_list.append(self._weight_vector(phi, theta-90)) - + self.beams_list.append( + (np.ndarray.item(phi), np.ndarray.item(theta - 90)), + ) + self.w_vec_list.append(self._weight_vector(phi, theta - 90)) + if self.normalize: - lin = int(phi/self.resolution) - col = int(theta/self.resolution) - self.co_correction_factor_list.append(self.co_correction_factor[lin,col]) + lin = int(phi / self.resolution) + col = int(theta / self.resolution) + self.co_correction_factor_list.append( + self.co_correction_factor[lin, col], + ) else: self.co_correction_factor_list.append(0.0) @@ -138,14 +143,14 @@ def calculate_gain(self, *args, **kwargs) -> np.array: """ phi_vec = np.asarray(kwargs["phi_vec"]) theta_vec = np.asarray(kwargs["theta_vec"]) - + # Check if antenna gain has to be calculated on the co-channel or # on the adjacent channel - if("co_channel" in kwargs.keys()): + if "co_channel" in kwargs.keys(): co_channel = kwargs["co_channel"] - else: + else: co_channel = True - + # If gain has to be calculated on the adjacent channel, then check whether # to use beamforming or single element pattern. # Both options are explicitly written in order to improve readability @@ -155,53 +160,65 @@ def calculate_gain(self, *args, **kwargs) -> np.array: elif self.adjacent_antenna_model == "BEAMFORMING": co_channel = True else: - sys.stderr.write("ERROR\nInvalid antenna pattern for adjacent channel calculations: " + self.adjacent_antenna_model) - sys.exit(1) - - if("beams_l" in kwargs.keys()): - beams_l = np.asarray(kwargs["beams_l"],dtype=int) + sys.stderr.write( + "ERROR\nInvalid antenna pattern for adjacent channel calculations: " + + self.adjacent_antenna_model, + ) + sys.exit(1) + + correction_factor_idx = None + if "beams_l" in kwargs.keys(): + beams_l = np.asarray(kwargs["beams_l"], dtype=int) correction_factor = self.co_correction_factor_list correction_factor_idx = beams_l - else: - beams_l = -1*np.ones_like(phi_vec) + else: + beams_l = -1 * np.ones_like(phi_vec) if co_channel: if self.normalize: - lin_f = phi_vec/self.resolution - col_f = theta_vec/self.resolution + lin_f = phi_vec / self.resolution + col_f = theta_vec / self.resolution lin = lin_f.astype(int) col = col_f.astype(int) - correction_factor = self.co_correction_factor[lin,col] + correction_factor = self.co_correction_factor[lin, col] else: correction_factor = np.zeros_like(phi_vec) - correction_factor_idx = [i for i in range(len(correction_factor))] + correction_factor_idx = [ + i for i in range(len(correction_factor)) + ] lo_phi_vec, lo_theta_vec = self.to_local_coord(phi_vec, theta_vec) n_direct = len(lo_theta_vec) gains = np.zeros(n_direct) - - if(co_channel): + + if co_channel: for g in range(n_direct): - gains[g] = self._beam_gain(lo_phi_vec[g], lo_theta_vec[g], - beams_l[g])\ - + correction_factor[correction_factor_idx[g]] + gains[g] = self._beam_gain( + lo_phi_vec[g], lo_theta_vec[g], + beams_l[g], + )\ + + correction_factor[correction_factor_idx[g]] else: for g in range(n_direct): - gains[g] = self.element.element_pattern(lo_phi_vec[g], - lo_theta_vec[g])\ - + self.adj_correction_factor + gains[g] = self.element.element_pattern( + lo_phi_vec[g], + lo_theta_vec[g], + )\ + + self.adj_correction_factor gains = np.maximum(gains, self.minimum_array_gain) - + return gains def reset_beams(self): + """Reset beams lists + """ self.beams_list = [] self.w_vec_list = [] self.co_correction_factor_list = [] - def _super_position_vector(self,phi: float, theta: float) -> np.array: + def _super_position_vector(self, phi: float, theta: float) -> np.array: """ Calculates super position vector. Angles are in the local coordinate system. @@ -221,10 +238,10 @@ def _super_position_vector(self,phi: float, theta: float) -> np.array: n = np.arange(self.n_rows) + 1 m = np.arange(self.n_cols) + 1 - exp_arg = (n[:,np.newaxis] - 1)*self.dv*np.cos(r_theta) + \ - (m - 1)*self.dh*np.sin(r_theta)*np.sin(r_phi) + exp_arg = (n[:, np.newaxis] - 1) * self.dv * np.cos(r_theta) + \ + (m - 1) * self.dh * np.sin(r_theta) * np.sin(r_phi) - v_vec = np.exp(2*np.pi*1.0j*exp_arg) + v_vec = np.exp(2 * np.pi * 1.0j * exp_arg) return v_vec @@ -248,15 +265,15 @@ def _weight_vector(self, phi_tilt: float, theta_tilt: float) -> np.array: n = np.arange(self.n_rows) + 1 m = np.arange(self.n_cols) + 1 - exp_arg = (n[:,np.newaxis] - 1)*self.dv*np.sin(r_theta) - \ - (m - 1)*self.dh*np.cos(r_theta)*np.sin(r_phi) + exp_arg = (n[:, np.newaxis] - 1) * self.dv * np.sin(r_theta) - \ + (m - 1) * self.dh * np.cos(r_theta) * np.sin(r_phi) - w_vec = (1/np.sqrt(self.n_rows*self.n_cols))*\ - np.exp(2*np.pi*1.0j*exp_arg) + w_vec = (1 / np.sqrt(self.n_rows * self.n_cols)) *\ + np.exp(2 * np.pi * 1.0j * exp_arg) return w_vec - def _beam_gain(self,phi: float, theta: float, beam = -1) -> float: + def _beam_gain(self, phi: float, theta: float, beam=-1) -> float: """ Calculates gain for a single beam in a given direction. Angles are in the local coordinate system. @@ -273,125 +290,180 @@ def _beam_gain(self,phi: float, theta: float, beam = -1) -> float: gain (float): beam gain [dBi] """ - element_g = self.element.element_pattern(phi,theta) + element_g = self.element.element_pattern(phi, theta) - v_vec = self._super_position_vector(phi,theta) + v_vec = self._super_position_vector(phi, theta) - if(beam == -1): - w_vec = self._weight_vector(phi,theta-90) - array_g = 10*np.log10(abs(np.sum(np.multiply(v_vec,w_vec)))**2) + if beam == -1: + w_vec = self._weight_vector(phi, theta - 90) + array_g = 10 * np.log10(abs(np.sum(np.multiply(v_vec, w_vec)))**2) else: - array_g = 10*np.log10(abs(np.sum(np.multiply(v_vec,\ - self.w_vec_list[beam])))**2) + array_g = 10 * np.log10( + abs( + np.sum( + np.multiply( + v_vec, + self.w_vec_list[beam], + ), + ), + )**2, + ) gain = element_g + array_g return gain - def to_local_coord(self,phi: float, theta: float) -> tuple: - + def to_local_coord(self, phi: float, theta: float) -> tuple: + """Returns phi and theta to antennas local coordintate system + + Parameters + ---------- + phi : float + phi in the simulator's coordinate system + theta : float + theta in the simulator's coordinate system + + Returns + ------- + tuple + phi, theta in the antenna's coordinate system + """ + phi_rad = np.ravel(np.array([np.deg2rad(phi)])) theta_rad = np.ravel(np.array([np.deg2rad(theta)])) - - points = np.matrix([np.sin(theta_rad)*np.cos(phi_rad), - np.sin(theta_rad)*np.sin(phi_rad), - np.cos(theta_rad)]) - - rotated_points = self.rotation_mtx*points - - lo_phi = np.ravel(np.asarray(np.rad2deg(np.arctan2(rotated_points[1],rotated_points[0])))) - lo_theta = np.ravel(np.asarray(np.rad2deg(np.arccos(rotated_points[2])))) + + points = np.matrix([ + np.sin(theta_rad) * np.cos(phi_rad), + np.sin(theta_rad) * np.sin(phi_rad), + np.cos(theta_rad), + ]) + + rotated_points = self.rotation_mtx * points + + lo_phi = np.ravel( + np.asarray( + np.rad2deg( + np.arctan2(rotated_points[1], rotated_points[0]), + ), + ), + ) + lo_theta = np.ravel( + np.asarray( + np.rad2deg(np.arccos(rotated_points[2])), + ), + ) return lo_phi, lo_theta - + def _calculate_rotation_matrix(self): - + alpha = np.deg2rad(self.azimuth) beta = np.deg2rad(self.elevation) - Ry = np.matrix([[ np.cos(beta), 0.0, np.sin(beta)], - [ 0.0, 1.0, 0.0], - [-np.sin(beta), 0.0, np.cos(beta)]]) - Rz = np.matrix([[np.cos(alpha),-np.sin(alpha), 0.0], - [np.sin(alpha), np.cos(alpha), 0.0], - [ 0.0, 0.0, 1.0]]) - self.rotation_mtx = Ry*np.transpose(Rz) + ry = np.matrix([ + [np.cos(beta), 0.0, np.sin(beta)], + [0.0, 1.0, 0.0], + [-np.sin(beta), 0.0, np.cos(beta)], + ]) + rz = np.matrix([ + [np.cos(alpha), -np.sin(alpha), 0.0], + [np.sin(alpha), np.cos(alpha), 0.0], + [0.0, 0.0, 1.0], + ]) + self.rotation_mtx = ry * np.transpose(rz) ############################################################################### + + class PlotAntennaPattern(object): """ Plots imt antenna pattern. """ + def __init__(self, figs_dir): self.figs_dir = figs_dir - def plot_element_pattern(self, - antenna: AntennaBeamformingImt, - sta_type: str, - plot_type: str): + def plot_element_pattern( + self, + antenna: AntennaBeamformingImt, + sta_type: str, + plot_type: str, + ): phi_escan = 0 theta_tilt = 90 # Plot horizontal pattern - phi = np.linspace(-180, 180, num = 360) - theta = theta_tilt*np.ones(np.size(phi)) + phi = np.linspace(-180, 180, num=360) + theta = theta_tilt * np.ones(np.size(phi)) + gain = np.ones(phi.shape, dtype=float) * -500 if plot_type == "ELEMENT": gain = antenna.element.element_pattern(phi, theta) elif plot_type == "ARRAY": antenna.add_beam(phi_escan, theta_tilt) - gain = antenna.calculate_gain(phi_vec = phi, - theta_vec = theta, - beams_l = np.zeros_like(phi, dtype=int)) + gain = antenna.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=np.zeros_like(phi, dtype=int), + ) - top_y_lim = np.ceil(np.max(gain)/10)*10 + top_y_lim = np.ceil(np.max(gain) / 10) * 10 - fig = plt.figure(figsize=(15,5), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(15, 5), facecolor='w', edgecolor='k') ax1 = fig.add_subplot(121) - ax1.plot(phi,gain) + ax1.plot(phi, gain) ax1.grid(True) ax1.set_xlabel(r"$\varphi$ [deg]") ax1.set_ylabel("Gain [dBi]") if plot_type == "ELEMENT": - ax1.set_title("IMT " + sta_type + " element horizontal antenna pattern") + ax1.set_title( + "IMT " + sta_type + + " element horizontal antenna pattern", + ) elif plot_type == "ARRAY": ax1.set_title("IMT " + sta_type + " horizontal antenna pattern") ax1.set_xlim(-180, 180) # Plot vertical pattern - theta = np.linspace(0, 180, num = 360) - phi = phi_escan*np.ones(np.size(theta)) + theta = np.linspace(0, 180, num=360) + phi = phi_escan * np.ones(np.size(theta)) if plot_type == "ELEMENT": gain = antenna.element.element_pattern(phi, theta) elif plot_type == "ARRAY": - gain = antenna.calculate_gain(phi_vec = phi, - theta_vec = theta, - beams_l = np.zeros_like(phi, dtype=int)) + gain = antenna.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=np.zeros_like(phi, dtype=int), + ) - ax2 = fig.add_subplot(122, sharey = ax1) + ax2 = fig.add_subplot(122, sharey=ax1) - ax2.plot(theta,gain) + ax2.plot(theta, gain) ax2.grid(True) ax2.set_xlabel(r"$\theta$ [deg]") ax2.set_ylabel("Gain [dBi]") if plot_type == "ELEMENT": - ax2.set_title("IMT " + sta_type + " element vertical antenna pattern") + ax2.set_title( + "IMT " + sta_type + + " element vertical antenna pattern", + ) elif plot_type == "ARRAY": ax2.set_title("IMT " + sta_type + " vertical antenna pattern") ax2.set_xlim(0, 180) - if(np.max(gain) > top_y_lim): top_y_lim = np.ceil(np.max(gain)/10)*10 - ax2.set_ylim(top_y_lim - 60,top_y_lim) + if np.max(gain) > top_y_lim: + top_y_lim = np.ceil(np.max(gain) / 10) * 10 + ax2.set_ylim(top_y_lim - 60, top_y_lim) if sta_type == "BS": file_name = self.figs_dir + "bs_" - else: # sta_type == "UE": + else: # sta_type == "UE": file_name = self.figs_dir + "ue_" if plot_type == "ELEMENT": @@ -399,62 +471,65 @@ def plot_element_pattern(self, elif plot_type == "ARRAY": file_name = file_name + "array_pattern.png" - #plt.savefig(file_name) + # plt.savefig(file_name) plt.show() return fig + if __name__ == '__main__': figs_dir = "figs/" - param = ParametersAntennaImt() - param.adjacent_antenna_model = "SINGLE_ELEMENT" - param.bs_normalization = False - param.ue_normalization = False - param.bs_normalization_file = 'beamforming_normalization\\bs_indoor_norm.npz' - param.ue_normalization_file = 'beamforming_normalization\\ue_norm.npz' - param.bs_minimum_array_gain = -200 - param.ue_minimum_array_gain = -200 - - param.bs_element_pattern = "M2101" - param.bs_element_max_g = 5 - param.bs_element_phi_3db = 65 - param.bs_element_theta_3db = 65 - param.bs_element_am = 30 - param.bs_element_sla_v = 30 - param.bs_n_rows = 8 - param.bs_n_columns = 8 - param.bs_element_horiz_spacing = 0.5 - param.bs_element_vert_spacing = 0.5 - param.bs_multiplication_factor = 12 - param.bs_downtilt = 0 - - param.ue_element_pattern = "M2101" - param.ue_element_max_g = 5 - param.ue_element_phi_3db = 90 - param.ue_element_theta_3db = 90 - param.ue_element_am = 25 - param.ue_element_sla_v = 25 - param.ue_n_rows = 4 - param.ue_n_columns = 4 - param.ue_element_horiz_spacing = 0.5 - param.ue_element_vert_spacing = 0.5 - param.ue_multiplication_factor = 12 + bs_param = ParametersAntennaImt() + ue_param = ParametersAntennaImt() + bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + ue_param.adjacent_antenna_model = "SINGLE_ELEMENT" + bs_param.normalization = False + ue_param.normalization = False + bs_param.normalization_file = 'beamforming_normalization\\bs_indoor_norm.npz' + ue_param.normalization_file = 'beamforming_normalization\\ue_norm.npz' + bs_param.minimum_array_gain = -200 + ue_param.minimum_array_gain = -200 + + bs_param.element_pattern = "M2101" + bs_param.element_max_g = 5 + bs_param.element_phi_3db = 65 + bs_param.element_theta_3db = 65 + bs_param.element_am = 30 + bs_param.element_sla_v = 30 + bs_param.n_rows = 8 + bs_param.n_columns = 8 + bs_param.element_horiz_spacing = 0.5 + bs_param.element_vert_spacing = 0.5 + bs_param.multiplication_factor = 12 + bs_param.downtilt = 0 + + ue_param.element_pattern = "M2101" + ue_param.element_max_g = 5 + ue_param.element_phi_3db = 90 + ue_param.element_theta_3db = 90 + ue_param.element_am = 25 + ue_param.element_sla_v = 25 + ue_param.n_rows = 4 + ue_param.n_columns = 4 + ue_param.element_horiz_spacing = 0.5 + ue_param.element_vert_spacing = 0.5 + ue_param.multiplication_factor = 12 plot = PlotAntennaPattern(figs_dir) # Plot BS TX radiation patterns - par = param.get_antenna_parameters(StationType.IMT_BS) - bs_array = AntennaBeamformingImt(par,0,0) - f = plot.plot_element_pattern(bs_array, "BS","ELEMENT") - #f.savefig(figs_dir + "BS_element.pdf", bbox_inches='tight') + par = bs_param.get_antenna_parameters() + bs_array = AntennaBeamformingImt(par, 0, 0) + f = plot.plot_element_pattern(bs_array, "BS", "ELEMENT") + # f.savefig(figs_dir + "BS_element.pdf", bbox_inches='tight') f = plot.plot_element_pattern(bs_array, "TX", "ARRAY") - #f.savefig(figs_dir + "BS_array.pdf", bbox_inches='tight') + # f.savefig(figs_dir + "BS_array.pdf", bbox_inches='tight') # Plot UE TX radiation patterns - par = param.get_antenna_parameters(StationType.IMT_UE) - ue_array = AntennaBeamformingImt(par,0,0) - plot.plot_element_pattern(ue_array,"UE", "ELEMENT") - plot.plot_element_pattern(ue_array,"UE", "ARRAY") + par = ue_param.get_antenna_parameters() + ue_array = AntennaBeamformingImt(par, 0, 0) + plot.plot_element_pattern(ue_array, "UE", "ELEMENT") + plot.plot_element_pattern(ue_array, "UE", "ARRAY") print('END') diff --git a/sharc/antenna/antenna_element_imt_const.py b/sharc/antenna/antenna_element_imt_const.py index e8d6dd3d4..31a88430c 100644 --- a/sharc/antenna/antenna_element_imt_const.py +++ b/sharc/antenna/antenna_element_imt_const.py @@ -9,6 +9,7 @@ from sharc.support.named_tuples import AntennaPar import numpy as np + class AntennaElementImtConst(object): """ Implements a single element of an IMT antenna array with constant gain @@ -19,7 +20,7 @@ class AntennaElementImtConst(object): """ - def __init__(self,par: AntennaPar): + def __init__(self, par: AntennaPar): """ Constructs an AntennaElementImt object. diff --git a/sharc/antenna/antenna_element_imt_f1336.py b/sharc/antenna/antenna_element_imt_f1336.py index 1be81bb84..ec38ade1b 100644 --- a/sharc/antenna/antenna_element_imt_f1336.py +++ b/sharc/antenna/antenna_element_imt_f1336.py @@ -10,6 +10,7 @@ from sharc.support.named_tuples import AntennaPar + class AntennaElementImtF1336(object): """ Implements a single element of an IMT antenna array following ITU-R F.1336-4, item 3.1.1 @@ -22,7 +23,7 @@ class AntennaElementImtF1336(object): phi_3db (float): horizontal 3dB beamwidth of single element [degrees] """ - def __init__(self,par: AntennaPar): + def __init__(self, par: AntennaPar): """ Constructs an AntennaElementImt object. @@ -38,23 +39,28 @@ def __init__(self,par: AntennaPar): self.theta_3db = par.element_theta_3db else: if self.phi_3db > 120.: - sys.stderr.write("ERROR\nvertical beamwidth must be givem if horizontal beamwidth > 120 degrees") + sys.stderr.write( + "ERROR\nvertical beamwidth must be givem if horizontal beamwidth > 120 degrees", + ) sys.exit(1) # calculate based on F1336 - self.theta_3db = (31000 * 10**(-.1 * self.g_max))/self.phi_3db + self.theta_3db = (31000 * 10**(-.1 * self.g_max)) / self.phi_3db # antenna paremeters, according to ITU-R M2292 self.k_a = .7 self.k_p = .7 self.k_h = .7 - self.lambda_k_h = 3 * (1-.5**(-self.k_h)) + self.lambda_k_h = 3 * (1 - .5**(-self.k_h)) self.k_v = .3 - self.incline_factor = 10*np.log10(((180/self.theta_3db)**1.5 * (4**-1.5+self.k_v))/ - (1 + 8 * self.k_p)) / np.log10(22.5 / self.theta_3db) + self.incline_factor = \ + 10 * np.log10(((180 / self.theta_3db)**1.5 * (4**-1.5 + self.k_v)) / (1 + 8 * self.k_p)) / \ + np.log10(22.5 / self.theta_3db) self.x_k = np.sqrt(1 - .36 * self.k_v) - self.lambda_k_v = 12 - self.incline_factor * np.log10(4) - 10 * np.log10(4**-1.5 + self.k_v) + self.lambda_k_v = 12 - self.incline_factor * \ + np.log10(4) - 10 * np.log10(4**-1.5 + self.k_v) - self.g_hr_180 = -12. + 10 * np.log10(1 + 8 * self.k_a) - 15 * np.log10(180/self.theta_3db) + self.g_hr_180 = -12. + 10 * \ + np.log10(1 + 8 * self.k_a) - 15 * np.log10(180 / self.theta_3db) self.g_hr_0 = 0 def horizontal_pattern(self, phi: np.array) -> {np.array, float}: @@ -73,8 +79,8 @@ def horizontal_pattern(self, phi: np.array) -> {np.array, float}: phi_a = np.array([phi]) else: phi_a = phi - - x_h = np.abs(phi_a)/self.phi_3db + + x_h = np.abs(phi_a) / self.phi_3db gain = np.zeros(np.size(phi_a)) i0 = np.where(x_h < 0.5)[0] @@ -82,12 +88,12 @@ def horizontal_pattern(self, phi: np.array) -> {np.array, float}: i1 = np.where(x_h >= 0.5)[0] gain[i1] = -12 * np.power(x_h[i1], 2 - self.k_h) - self.lambda_k_h - + gain = np.maximum(gain, self.g_hr_180) - + if type(phi) is not np.ndarray: gain = gain[0] - + return gain def vertical_pattern(self, theta: np.array) -> np.array: @@ -102,26 +108,26 @@ def vertical_pattern(self, theta: np.array) -> np.array: ------- a_v (np.array): vertical radiation pattern gain value """ - # This correction is needed because the simulator calculates theta + # This correction is needed because the simulator calculates theta # with respect to z-axis and equations of F.1336 assume that theta is # calculated with respect to the direction of maximum gain if type(theta) is np.ndarray: theta_a = theta - 90 else: theta_a = np.array([theta]) - 90 - - x_v = np.abs(theta_a)/self.theta_3db + + x_v = np.abs(theta_a) / self.theta_3db gain = np.zeros(np.size(theta_a)) i0 = np.where(x_v < self.x_k)[0] gain[i0] = -12 * np.power(x_v[i0], 2) - + i1 = np.where((x_v >= self.x_k) & (x_v < 4))[0] - gain[i1] = -12 + 10*np.log10(np.power(x_v[i1], -1.5) + self.k_v) - + gain[i1] = -12 + 10 * np.log10(np.power(x_v[i1], -1.5) + self.k_v) + i2 = np.where((x_v >= 4) & (x_v < 90 / self.theta_3db))[0] gain[i2] = - self.lambda_k_v - self.incline_factor * np.log10(x_v[i2]) - + i3 = np.where(x_v >= (90 / self.theta_3db))[0] gain[i3] = self.g_hr_180 @@ -145,14 +151,17 @@ def element_pattern(self, phi: np.array, theta: np.array) -> np.array: """ gain_hor = self.horizontal_pattern(phi) - compression_ratio = (gain_hor - self.g_hr_180)/(self.g_hr_0 - self.g_hr_180) - gain = self.g_max + gain_hor + compression_ratio * self.vertical_pattern(theta) + compression_ratio = (gain_hor - self.g_hr_180) / \ + (self.g_hr_0 - self.g_hr_180) + gain = self.g_max + gain_hor + \ + compression_ratio * self.vertical_pattern(theta) return gain + if __name__ == '__main__': - from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt + from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from matplotlib import pyplot as plt param = ParametersAntennaImt() @@ -161,17 +170,17 @@ def element_pattern(self, phi: np.array, theta: np.array) -> np.array: param.element_phi_3db = 65 param.element_theta_3db = 0 - antenna = AntennaElementImtF1336( param ) + antenna = AntennaElementImtF1336(param) - phi_vec = np.arange(-180, 180, step = 1) - theta_vec = np.arange(0, 180, step = 1) + phi_vec = np.arange(-180, 180, step=1) + theta_vec = np.arange(0, 180, step=1) pattern_hor_0deg = antenna.element_pattern(phi_vec, 0) pattern_hor_10deg = antenna.element_pattern(phi_vec, 10) pattern_hor_30deg = antenna.element_pattern(phi_vec, 30) pattern_hor_60deg = antenna.element_pattern(phi_vec, 60) pattern_hor_90deg = antenna.element_pattern(phi_vec, 90) - + pattern_ver_0deg = antenna.element_pattern(0, theta_vec) pattern_ver_30deg = antenna.element_pattern(30, theta_vec) pattern_ver_60deg = antenna.element_pattern(60, theta_vec) @@ -179,11 +188,11 @@ def element_pattern(self, phi: np.array, theta: np.array) -> np.array: pattern_ver_120deg = antenna.element_pattern(120, theta_vec) plt.figure(1) - plt.plot(phi_vec, pattern_hor_0deg, label = 'elevation = 0 degrees') - plt.plot(phi_vec, pattern_hor_10deg, label = 'elevation = 10 degrees') - plt.plot(phi_vec, pattern_hor_30deg, label = 'elevation = 30 degrees') - plt.plot(phi_vec, pattern_hor_60deg, label = 'elevation = 60 degrees') - plt.plot(phi_vec, pattern_hor_90deg, label = 'elevation = 90 degrees') + plt.plot(phi_vec, pattern_hor_0deg, label='elevation = 0 degrees') + plt.plot(phi_vec, pattern_hor_10deg, label='elevation = 10 degrees') + plt.plot(phi_vec, pattern_hor_30deg, label='elevation = 30 degrees') + plt.plot(phi_vec, pattern_hor_60deg, label='elevation = 60 degrees') + plt.plot(phi_vec, pattern_hor_90deg, label='elevation = 90 degrees') plt.title('horizontal pattern') plt.xlabel('azimuth (degrees)') diff --git a/sharc/antenna/antenna_element_imt_m2101.py b/sharc/antenna/antenna_element_imt_m2101.py index bf51b30fb..71cae812c 100644 --- a/sharc/antenna/antenna_element_imt_m2101.py +++ b/sharc/antenna/antenna_element_imt_m2101.py @@ -9,6 +9,7 @@ from sharc.support.named_tuples import AntennaPar + class AntennaElementImtM2101(object): """ Implements a single element of an IMT antenna array. @@ -22,7 +23,7 @@ class AntennaElementImtM2101(object): sla_v (float): element vertical sidelobe attenuation """ - def __init__(self,par: AntennaPar): + def __init__(self, par: AntennaPar): """ Constructs an AntennaElementImt object. @@ -39,7 +40,7 @@ def __init__(self,par: AntennaPar): self.am = par.element_am self.sla_v = par.element_sla_v - def horizontal_pattern(self,phi: np.array) -> np.array: + def horizontal_pattern(self, phi: np.array) -> np.array: """ Calculates the horizontal radiation pattern. @@ -51,9 +52,9 @@ def horizontal_pattern(self,phi: np.array) -> np.array: ------- a_h (np.array): horizontal radiation pattern gain value """ - return -1.0*np.minimum(self.multiplication_factor*(phi/self.phi_3db)**2,self.am) + return -1.0 * np.minimum(self.multiplication_factor * (phi / self.phi_3db)**2, self.am) - def vertical_pattern(self,theta: np.array) -> np.array: + def vertical_pattern(self, theta: np.array) -> np.array: """ Calculates the vertical radiation pattern. @@ -65,7 +66,7 @@ def vertical_pattern(self,theta: np.array) -> np.array: ------- a_v (np.array): vertical radiation pattern gain value """ - return -1.0*np.minimum(self.multiplication_factor*((theta-90.0)/self.theta_3db)**2,self.sla_v) + return -1.0 * np.minimum(self.multiplication_factor * ((theta - 90.0) / self.theta_3db)**2, self.sla_v) def element_pattern(self, phi: np.array, theta: np.array) -> np.array: """ @@ -80,8 +81,10 @@ def element_pattern(self, phi: np.array, theta: np.array) -> np.array: ------- gain (np.array): element radiation pattern gain value [dBi] """ - att = -1.0*(self.horizontal_pattern(phi) + \ - self.vertical_pattern(theta)) - gain = self.g_max - np.minimum(att,self.am) + att = -1.0 * ( + self.horizontal_pattern(phi) + + self.vertical_pattern(theta) + ) + gain = self.g_max - np.minimum(att, self.am) return gain diff --git a/sharc/antenna/antenna_f1245.py b/sharc/antenna/antenna_f1245.py index ce16a5ace..b613bc0a3 100644 --- a/sharc/antenna/antenna_f1245.py +++ b/sharc/antenna/antenna_f1245.py @@ -1,1925 +1,151 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SHARC/antenna_f1245.py at dev-haps-antennas ยท SIMULATOR-WG/SHARC - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Skip to content -
- - - - - - - - - - -
- -
- - -
-
-
-

- The password you provided is weak and can be easily guessed. - To increase your security, you must update your password. After September 1st, 2019 we will automatically reset your password. Change your password on the settings page. -

-

Read our documentation on safer password practices.

-
-
- - -
- - - -
-
-
- - - - - - - - - - - - - - - - -
-
- -
    - - - - -
  • - -
    - -
    - - - Unwatch - - -
    - Notifications -
    -
    - - - - - - - -
    -
    -
    - -
    -
  • - -
  • -
    -
    - - -
    -
    - - -
    - -
  • - -
  • -
    - - Fork - -
    - -

    Fork SHARC

    -
    -
    - -
    -

    If this dialog fails to load, you can visit the fork page directly.

    -
    -
    -
    -
    - - -
  • -
- -

- - /SHARC - - -

- -
- - - - - - -
-
-
- - - - - - - - - Permalink - - - - -
- - -
- - Branch: - dev-haps-antenโ€ฆ - - - - - - - -
- -
- - Find file - - - Copy path - -
-
- - -
- - Find file - - - Copy path - -
-
- - - - -
-
- - @CalilQ - CalilQ - - Integrate GW and CPE antennas to simulator - - - - 4655726 - Apr 6, 2018 - -
- -
-
- - 1 contributor - - -
- -

- Users who have contributed to this file -

-
- -
-
-
-
- - - - - -
- -
- -
- 175 lines (128 sloc) - - 5.54 KB -
- -
- -
- Raw - Blame - History -
- - -
- - - - -
- -
-
- -
-
-
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 4 17:08:00 2018
@author: Calil
"""
-
from sharc.antenna.antenna import Antenna
from sharc.parameters.parameters_imt import ParametersImt
from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
-
import numpy as np
import math
-
class AntennaF1245(Antenna):
"""
Implements reference radiation patterns for HAPS gateway antennas
for use in coordination studies and interference assessment in the
frequency range from 1 GHz to about 70 GHz. (ITU-R F.1245-2)
"""
-
def __init__(self, param: ParametersImt, param_ant: ParametersAntennaImt):
super().__init__()
self.peak_gain = param_ant.peak_gain
lmbda = 3e8 / ( param.frequency * 1e6 )
self.d_lmbda = param_ant.diameter / lmbda
-
self.g_l = 2 + 15 * math.log10(self.d_lmbda)
self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l)
self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6)
-
def calculate_gain(self, *args, **kwargs) -> np.array:
phi_vec = np.absolute(kwargs["phi_vec"])
theta_vec = np.absolute(kwargs["theta_vec"])
beams_l = np.absolute(kwargs["beams_l"])
off_axis = self.calculate_off_axis_angle(phi_vec,theta_vec)
-
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[idx_max_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):
self.beams_list.append((phi, theta))
def calculate_off_axis_angle(self,Az,b):
Az0 = self.beams_list[0][0]
-
a = 90 - self.beams_list[0][1]
C = Az0 - Az
-
off_axis_rad = np.arccos(np.cos(np.radians(a))*np.cos(np.radians(b)) \
+ np.sin(np.radians(a))*np.sin(np.radians(b))*np.cos(np.radians(C)))
off_axis_deg = np.degrees(off_axis_rad)
return off_axis_deg
-
-
if __name__ == '__main__':
import matplotlib.pyplot as plt
-
phi = np.linspace(0.1, 180, num = 100000)
theta = 90*np.ones_like(phi)
beams_idx = np.zeros_like(phi, dtype=int)
-
# initialize antenna parameters
param = ParametersImt()
param.frequency = 10700
param_gt = ParametersAntennaImt()
param_gt.peak_gain = 49.8
param_gt.diameter = 3
antenna_gt = AntennaF1245(param,param_gt)
antenna_gt.add_beam(0,0)
-
gain_gt = antenna_gt.calculate_gain(phi_vec = phi,
theta_vec = theta,
beams_l = beams_idx)
param.frequency = 27500
param_lt = ParametersAntennaImt()
param_lt.peak_gain = 36.9
param_lt.diameter = 0.3
antenna_lt = AntennaF1245(param,param_lt)
antenna_lt.add_beam(0,0)
gain_lt = antenna_lt.calculate_gain(phi_vec = phi,
theta_vec = theta,
beams_l = beams_idx)
-
fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
-
plt.semilogx(phi, gain_gt, "-b", label = "$f = 10.7$ $GHz,$ $D = 3$ $m$")
plt.semilogx(phi, gain_lt, "-r", label = "$f = 27.5$ $GHz,$ $D = 0.3$ $m$")
-
plt.title("ITU-R F.1245 antenna radiation pattern")
plt.xlabel("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()
- - - -
- -
- - - -
- - -
- - -
-
- - - -
-
- -
-
- - -
- - - - - - -
- - - You canโ€™t perform that action at this time. -
- - - - - - - - - - - - - - -
- - - - +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 4 17:08:00 2018 +@author: Calil +""" +import matplotlib.pyplot as plt +from sharc.antenna.antenna import Antenna +from sharc.parameters.imt.parameters_imt import ParametersImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt +import numpy as np +import math + + +class AntennaF1245(Antenna): + """ + Implements reference radiation patterns for HAPS gateway antennas + for use in coordination studies and interference assessment in the + frequency range from 1 GHz to about 70 GHz. (ITU-R F.1245-2) + """ + + def __init__(self, param: ParametersImt, param_ant: ParametersAntennaImt): + super().__init__() + self.peak_gain = param_ant.peak_gain + lmbda = 3e8 / (param.frequency * 1e6) + self.d_lmbda = param_ant.diameter / lmbda + self.g_l = 2 + 15 * math.log10(self.d_lmbda) + self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l) + self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6) + + def calculate_gain(self, *args, **kwargs) -> np.array: + phi_vec = np.absolute(kwargs["phi_vec"]) + theta_vec = np.absolute(kwargs["theta_vec"]) + beams_l = np.absolute(kwargs["beams_l"]) + off_axis = self.calculate_off_axis_angle(phi_vec, theta_vec) + 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[idx_max_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): + self.beams_list.append((phi, theta)) + + def calculate_off_axis_angle(self, Az, b): + Az0 = self.beams_list[0][0] + a = 90 - self.beams_list[0][1] + C = Az0 - Az + off_axis_rad = np.arccos( + np.cos(np.radians(a)) * np.cos(np.radians(b)) + np.sin(np.radians(a)) * + np.sin(np.radians(b)) * np.cos(np.radians(C)), + ) + off_axis_deg = np.degrees(off_axis_rad) + return off_axis_deg + + +if __name__ == '__main__': + phi = np.linspace(0.1, 180, num=100000) + theta = 90 * np.ones_like(phi) + beams_idx = np.zeros_like(phi, dtype=int) + # initialize antenna parameters + param = ParametersImt() + param.frequency = 10700 + param_gt = ParametersAntennaImt() + param_gt.peak_gain = 49.8 + param_gt.diameter = 3 + antenna_gt = AntennaF1245(param, param_gt) + antenna_gt.add_beam(0, 0) + gain_gt = antenna_gt.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=beams_idx, + ) + param.frequency = 27500 + param_lt = ParametersAntennaImt() + param_lt.peak_gain = 36.9 + param_lt.diameter = 0.3 + antenna_lt = AntennaF1245(param, param_lt) + antenna_lt.add_beam(0, 0) + gain_lt = antenna_lt.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=beams_idx, + ) + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + plt.semilogx(phi, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 3$ $m$") + plt.semilogx(phi, gain_lt, "-r", label="$f = 27.5$ $GHz,$ $D = 0.3$ $m$") + plt.title("ITU-R 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_f1891.py b/sharc/antenna/antenna_f1891.py index 8b9b8d103..530f7f44f 100644 --- a/sharc/antenna/antenna_f1891.py +++ b/sharc/antenna/antenna_f1891.py @@ -10,6 +10,7 @@ import numpy as np + class AntennaF1891(Antenna): """ Implements the antenna radiation pattern described in ITU-R F.1891. This is @@ -21,13 +22,13 @@ class AntennaF1891(Antenna): def __init__(self, param: ParametersHaps): super().__init__() self.peak_gain = param.antenna_gain - self.psi_b = np.sqrt(7442/np.power(10, 0.1 * self.peak_gain)) + self.psi_b = np.sqrt(7442 / np.power(10, 0.1 * self.peak_gain)) self.l_n = param.antenna_l_n self.l_f = self.peak_gain - 73 - self.psi_1 = self.psi_b * np.sqrt(-self.l_n/3) + self.psi_1 = self.psi_b * np.sqrt(-self.l_n / 3) self.psi_2 = 3.745 * self.psi_b - self.x = self.peak_gain + self.l_n + 60*np.log10(self.psi_2) - self.psi_3 = np.power(10, (self.x - self.l_f)/60) + self.x = self.peak_gain + self.l_n + 60 * np.log10(self.psi_2) + self.psi_3 = np.power(10, (self.x - self.l_f) / 60) def calculate_gain(self, *args, **kwargs) -> np.array: psi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -35,13 +36,13 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain = self.l_f * np.ones(len(psi)) idx_0 = np.where(psi <= self.psi_1)[0] - gain[idx_0] = self.peak_gain - 3*np.power(psi[idx_0]/self.psi_b, 2) + gain[idx_0] = self.peak_gain - 3 * np.power(psi[idx_0] / self.psi_b, 2) idx_1 = np.where((self.psi_1 < psi) & (psi <= self.psi_2))[0] gain[idx_1] = self.peak_gain + self.l_n idx_2 = np.where((self.psi_2 < psi) & (psi <= self.psi_3))[0] - gain[idx_2] = self.x - 60*np.log10(psi[idx_2]) + gain[idx_2] = self.x - 60 * np.log10(psi[idx_2]) return gain @@ -53,17 +54,20 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param = ParametersHaps() param.antenna_pattern = "ITU-R F.1891" param.antenna_l_n = -25 - psi = np.linspace(0, 90, num = 10001) + psi = np.linspace(0, 90, num=10001) param.antenna_gain = 47 antenna47 = AntennaF1891(param) - gain47 = antenna47.calculate_gain(off_axis_angle_vec = psi) + gain47 = antenna47.calculate_gain(off_axis_angle_vec=psi) param.antenna_gain = 30 antenna30 = AntennaF1891(param) - gain30 = antenna30.calculate_gain(off_axis_angle_vec = psi) + gain30 = antenna30.calculate_gain(off_axis_angle_vec=psi) - fig = plt.figure(figsize=(16,6), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(16, 6), facecolor='w', + edgecolor='k', + ) # create a figure object ax1 = fig.add_subplot(121) ax1.plot(psi, gain47, "-b", label="$G_m = 47$ dB") diff --git a/sharc/antenna/antenna_f699.py b/sharc/antenna/antenna_f699.py index 47fe1b6b7..85e4b4043 100644 --- a/sharc/antenna/antenna_f699.py +++ b/sharc/antenna/antenna_f699.py @@ -11,6 +11,7 @@ import numpy as np import math + class AntennaF699(Antenna): """ Implements reference radiation patterns for fixed wireless system antennas @@ -21,7 +22,7 @@ class AntennaF699(Antenna): def __init__(self, param: ParametersFs): super().__init__() self.peak_gain = param.antenna_gain - lmbda = 3e8 / ( param.frequency * 1e6 ) + lmbda = 3e8 / (param.frequency * 1e6) self.d_lmbda = param.diameter / lmbda self.g_l = 2 + 15 * math.log10(self.d_lmbda) @@ -56,7 +57,8 @@ def calculate_gain_greater(self, phi: float) -> np.array: 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) + 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 < self.phi_r))[0] gain[idx_1] = self.g_l @@ -87,13 +89,15 @@ def calculate_gain_less(self, phi: float) -> np.array: 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) + 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 < 100 / self.d_lmbda))[0] gain[idx_1] = self.g_l idx_2 = np.where((100 / self.d_lmbda <= phi) & (phi < 48))[0] - gain[idx_2] = 52 - 10 * math.log10(self.d_lmbda) - 25 * np.log10(phi[idx_2]) + gain[idx_2] = 52 - 10 * \ + math.log10(self.d_lmbda) - 25 * np.log10(phi[idx_2]) idx_3 = np.where((48 <= phi) & (phi <= 180))[0] gain[idx_3] = 10 - 10 * math.log10(self.d_lmbda) @@ -101,11 +105,10 @@ def calculate_gain_less(self, phi: float) -> np.array: return gain - if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0.1, 180, num = 100000) + phi = np.linspace(0.1, 180, num=100000) # initialize antenna parameters param_gt = ParametersFs() @@ -115,7 +118,7 @@ def calculate_gain_less(self, phi: float) -> np.array: param_gt.diameter = 3 antenna_gt = AntennaF699(param_gt) - gain_gt = antenna_gt.calculate_gain(off_axis_angle_vec = phi) + gain_gt = antenna_gt.calculate_gain(off_axis_angle_vec=phi) param_lt = ParametersFs() param_lt.antenna_pattern = "ITU-R F.699" @@ -123,23 +126,26 @@ def calculate_gain_less(self, phi: float) -> np.array: param_lt.antenna_gain = 36.9 param_lt.diameter = 0.3 antenna_lt = AntennaF699(param_lt) - gain_lt = antenna_lt.calculate_gain(off_axis_angle_vec = phi) + gain_lt = antenna_lt.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object - plt.semilogx(phi, gain_gt, "-b", label = "$f = 10.7$ $GHz,$ $D = 3$ $m$") - plt.semilogx(phi, gain_lt, "-r", label = "$f = 27.5$ $GHz,$ $D = 0.3$ $m$") + plt.semilogx(phi, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 3$ $m$") + plt.semilogx(phi, gain_lt, "-r", label="$f = 27.5$ $GHz,$ $D = 0.3$ $m$") plt.title("ITU-R F.699 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Gain relative to $G_m$ [dB]") plt.legend(loc="lower left") plt.xlim((phi[0], phi[-1])) plt.ylim((-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()) + # 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_fss_ss.py b/sharc/antenna/antenna_fss_ss.py index e4e94f360..c21b42b1c 100644 --- a/sharc/antenna/antenna_fss_ss.py +++ b/sharc/antenna/antenna_fss_ss.py @@ -11,6 +11,7 @@ import numpy as np import sys + class AntennaFssSs(Antenna): """ Implements the antenna pattern for FSS space station according to Report on @@ -37,14 +38,16 @@ def __init__(self, param: ParametersFssSs): elif self.l_s == -30: self.a = 3.16 else: - sys.stderr.write("ERROR\nInvalid AntennaFssSs L_s parameter: " + self.l_s) + sys.stderr.write( + "ERROR\nInvalid AntennaFssSs L_s parameter: " + self.l_s, + ) sys.exit(1) self.b = 6.32 - self.psi_0 = param.antenna_3_dB/2 - self.psi_1 = self.psi_0 * np.power(10, (self.peak_gain + self.l_s + 20)/25) - + self.psi_0 = param.antenna_3_dB / 2 + self.psi_1 = self.psi_0 * \ + np.power(10, (self.peak_gain + self.l_s + 20) / 25) def calculate_gain(self, *args, **kwargs) -> np.array: psi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -52,13 +55,16 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain = np.zeros(len(psi)) - 3 idx_1 = np.where(psi <= self.a * self.psi_0)[0] - gain[idx_1] = self.peak_gain - 12 * np.power(psi[idx_1]/(2*self.psi_0), 2) + gain[idx_1] = self.peak_gain - 12 * \ + np.power(psi[idx_1] / (2 * self.psi_0), 2) - idx_2 = np.where((self.a * self.psi_0 < psi) & (psi <= self.b * self.psi_0 ))[0] + idx_2 = np.where((self.a * self.psi_0 < psi) & + (psi <= self.b * self.psi_0))[0] gain[idx_2] = self.peak_gain + self.l_s - 3 idx_3 = np.where((self.b * self.psi_0 < psi) & (psi <= self.psi_1))[0] - gain[idx_3] = self.peak_gain + self.l_s + 20 - 25 * np.log10(psi[idx_3]/self.psi_0) - 3 + gain[idx_3] = self.peak_gain + self.l_s + 20 - \ + 25 * np.log10(psi[idx_3] / self.psi_0) - 3 return gain @@ -67,7 +73,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: import matplotlib.pyplot as plt from sharc.antenna.antenna_s672 import AntennaS672 - psi = np.linspace(0.1, 180, num = 100000) + psi = np.linspace(0.1, 180, num=100000) # initialize antenna parameters param_ss = ParametersFssSs() @@ -76,7 +82,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param_ss.antenna_3_dB = 0.8 param_ss.antenna_l_s = -25 antenna_fss = AntennaFssSs(param_ss) - gain_fss = antenna_fss.calculate_gain(off_axis_angle_vec = psi) + gain_fss = antenna_fss.calculate_gain(off_axis_angle_vec=psi) param_672 = ParametersFssSs() param_672.antenna_gain = 51 @@ -84,9 +90,12 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param_672.antenna_3_dB = 0.65 param_672.antenna_l_s = -20 antenna_672 = AntennaS672(param_672) - gain_672 = antenna_672.calculate_gain(off_axis_angle_vec = psi) + gain_672 = antenna_672.calculate_gain(off_axis_angle_vec=psi) - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object plt.semilogx(psi, gain_672, "-b", label="carrier #06") plt.semilogx(psi, gain_fss, "-r", label="carrier #13") @@ -99,8 +108,13 @@ def calculate_gain(self, *args, **kwargs) -> np.array: plt.legend(loc="upper right") ax = plt.gca() - #ax.set_yticks([-40, -30, -20, -10, 0]) - ax.set_xticks(np.linspace(0.1, 0.9, 9).tolist() + np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + # ax.set_yticks([-40, -30, -20, -10, 0]) + ax.set_xticks( + np.linspace(0.1, 0.9, 9).tolist() + np.linspace( + 1, + 9, 9, + ).tolist() + np.linspace(10, 100, 10).tolist(), + ) plt.grid() plt.show() diff --git a/sharc/antenna/antenna_m1466.py b/sharc/antenna/antenna_m1466.py index 77bd604bf..65f7ba32d 100644 --- a/sharc/antenna/antenna_m1466.py +++ b/sharc/antenna/antenna_m1466.py @@ -9,18 +9,19 @@ import numpy as np + class AntennaM1466(Antenna): """ - Implements the antenna pattern for radionavigation radars according to + Implements the antenna pattern for radionavigation radars according to Rec. ITU-R M.1466-1 """ - + def __init__(self, gain, azimuth: float, elevation: float): """ Constructs an AntennaM1466 object. Does not receive angles in local coordinate system. Elevation taken wrt x-y plane. - + Parameters --------- azimuth (float): antenna's physical azimuth inclination @@ -32,30 +33,29 @@ def __init__(self, gain, azimuth: float, elevation: float): self.azimuth = azimuth self.elevation = elevation - def calculate_gain(self, *args, **kwargs) -> np.array: """ Calculates the gain in the given direction. - + Parameters ---------- phi_vec (np.array): azimuth angles [degrees] theta_vec (np.array): elevation angles [degrees] - + Returns ------- gain (np.array): gain corresponding to each of the given directions """ phi = np.asarray(kwargs["phi_vec"]) theta = np.asarray(kwargs["theta_vec"]) - + phi_l, theta_l = self.to_local_coord(phi, theta) - - gain = self.peak_gain + self.get_gain_az(phi_l) + self.get_gain_elev(theta_l) - + + gain = self.peak_gain + \ + self.get_gain_az(phi_l) + self.get_gain_elev(theta_l) + return gain - - + def get_gain_az(self, phi: np.array) -> np.array: """ Returns the antenna gain in the azimuth plane for the given direction. @@ -68,8 +68,7 @@ def get_gain_az(self, phi: np.array) -> np.array: gain[(pa >= 5) & (pa < 40)] = -27.5 gain[(pa >= 40)] = -35 return gain - - + def get_gain_elev(self, theta: np.array) -> np.array: """ Returns the antenna gain in the elevation plane for the given direction. @@ -81,38 +80,36 @@ def get_gain_elev(self, theta: np.array) -> np.array: gain[(theta >= 5) & (theta < 10)] = -20 gain[theta >= 10] = -35 return gain - - + def to_local_coord(self, phi: np.array, theta: np.array) -> tuple: """ Converts the azimuth and elevation angles to the local coordinate system, taking into account the physical orientation of the antenna. """ - + phi_l = phi - self.azimuth theta_l = 90 - theta - self.elevation - + return phi_l, theta_l - - + if __name__ == '__main__': import matplotlib.pyplot as plt - azimuth = np.linspace(-90, 90, num = 100000) - elevation = np.linspace(-80, 20, num = 100000) - + azimuth = np.linspace(-90, 90, num=100000) + elevation = np.linspace(-80, 20, num=100000) + gain = 30 azimuth_l = 0 elevation_l = 0 - + antenna = AntennaM1466(gain, azimuth_l, elevation_l) - + gain_az = gain + antenna.get_gain_az(azimuth) gain_elev = gain + antenna.get_gain_elev(elevation) - fig = plt.figure(figsize=(15,5), facecolor='w', edgecolor='k') - #plt.title("Rec. ITU-R M.1466 antenna pattern") + fig = plt.figure(figsize=(15, 5), facecolor='w', edgecolor='k') + # plt.title("Rec. ITU-R M.1466 antenna pattern") ax1 = fig.add_subplot(121) ax1.plot(azimuth, gain_az) ax1.grid(True) @@ -120,7 +117,7 @@ def to_local_coord(self, phi: np.array, theta: np.array) -> tuple: ax1.set_ylabel("Antenna gain [dBi]") ax1.set_xlim([-90, 90]) ax1.set_ylim([-10, 35]) - + ax2 = fig.add_subplot(122) ax2.plot(elevation, gain_elev) ax2.grid(True) @@ -129,4 +126,4 @@ def to_local_coord(self, phi: np.array, theta: np.array) -> tuple: ax2.set_xlim([-80, 20]) ax2.set_ylim([-10, 35]) - plt.show() \ No newline at end of file + plt.show() diff --git a/sharc/antenna/antenna_modified_s465.py b/sharc/antenna/antenna_modified_s465.py index 6fa25ccfe..449d1b107 100644 --- a/sharc/antenna/antenna_modified_s465.py +++ b/sharc/antenna/antenna_modified_s465.py @@ -9,7 +9,7 @@ from sharc.parameters.parameters_fss_es import ParametersFssEs import numpy as np -import math + class AntennaModifiedS465(Antenna): """ @@ -20,7 +20,7 @@ class AntennaModifiedS465(Antenna): def __init__(self, param: ParametersFssEs): super().__init__() self.peak_gain = param.antenna_gain - self.phi_min = 5.0 # degrees + self.phi_min = 5.0 # degrees self.envelope_gain = param.antenna_envelope_gain self.envelope_angle = 10**((32 - self.envelope_gain) / 25.) @@ -33,7 +33,8 @@ def calculate_gain(self, *args, **kwargs) -> np.array: idx_0 = np.where(phi < self.phi_min)[0] gain[idx_0] = self.peak_gain - idx_1 = np.where((self.phi_min <= phi) & (phi < self.envelope_angle))[0] + idx_1 = np.where((self.phi_min <= phi) & + (phi < self.envelope_angle))[0] gain[idx_1] = 32 - 25 * np.log10(phi[idx_1]) idx_2 = np.where((self.envelope_angle <= phi) & (phi <= 180))[0] @@ -41,10 +42,11 @@ def calculate_gain(self, *args, **kwargs) -> np.array: return gain + if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0.1, 180, num = 100000) + phi = np.linspace(0.1, 180, num=100000) # initialize antenna parameters param0 = ParametersFssEs() @@ -52,39 +54,41 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param0.antenna_gain = 32 param0.antenna_envelope_gain = 0 antenna0 = AntennaModifiedS465(param0) - gain0 = antenna0.calculate_gain(off_axis_angle_vec = phi) + gain0 = antenna0.calculate_gain(off_axis_angle_vec=phi) param4 = ParametersFssEs() param4.frequency = 3628 param4.antenna_gain = 32 param4.antenna_envelope_gain = -4 antenna4 = AntennaModifiedS465(param4) - gain4 = antenna4.calculate_gain(off_axis_angle_vec = phi) + gain4 = antenna4.calculate_gain(off_axis_angle_vec=phi) param10 = ParametersFssEs() param10.frequency = 3628 param10.antenna_gain = 32 param10.antenna_envelope_gain = -10 antenna10 = AntennaModifiedS465(param10) - gain10 = antenna10.calculate_gain(off_axis_angle_vec = phi) - - - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object + gain10 = antenna10.calculate_gain(off_axis_angle_vec=phi) + + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object - plt.semilogx(phi, gain0, "-b", label = "$G_{env}$ = 0 dBi") - plt.semilogx(phi, gain4, "-r", label = "$G_{env}$ = -4 dBi") - plt.semilogx(phi, gain10, "-g", label = "$G_{env}$ = -10 dBi") + plt.semilogx(phi, gain0, "-b", label="$G_{env}$ = 0 dBi") + plt.semilogx(phi, gain4, "-r", label="$G_{env}$ = -4 dBi") + plt.semilogx(phi, gain10, "-g", label="$G_{env}$ = -10 dBi") plt.title("Modified ITU-R S.465-6 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Gain relative to $G_m$ [dB]") plt.legend(loc="upper right") plt.xlim((1, 180)) plt.ylim((-20, 40)) - #ax = plt.gca() - #ax.set_yticks([-30, -20, -10, 0]) - #ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + # ax = plt.gca() + # ax.set_yticks([-30, -20, -10, 0]) + # ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) plt.grid() plt.show() diff --git a/sharc/antenna/antenna_omni.py b/sharc/antenna/antenna_omni.py index f9524fe8f..ec1d9efe3 100644 --- a/sharc/antenna/antenna_omni.py +++ b/sharc/antenna/antenna_omni.py @@ -8,6 +8,7 @@ from sharc.antenna.antenna import Antenna import numpy as np + class AntennaOmni(Antenna): """ This is an omnidirectional antenna @@ -35,5 +36,4 @@ def calculate_gain(self, *args, **kwargs) -> np.array: else: phi_vec = np.asarray(kwargs["off_axis_angle_vec"]) - return self.gain*np.ones(len(phi_vec)) - + return self.gain * np.ones(len(phi_vec)) diff --git a/sharc/antenna/antenna_res122.py b/sharc/antenna/antenna_res122.py index 23b9e3e3f..f2440e923 100644 --- a/sharc/antenna/antenna_res122.py +++ b/sharc/antenna/antenna_res122.py @@ -1,1670 +1,107 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SHARC/antenna_res122.py at dev-haps-antennas ยท SIMULATOR-WG/SHARC - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Skip to content -
- - - - - - - - - - -
- -
- - -
-
-
-

- The password you provided is weak and can be easily guessed. - To increase your security, you must update your password. After September 1st, 2019 we will automatically reset your password. Change your password on the settings page. -

-

Read our documentation on safer password practices.

-
-
- - -
- - - -
-
-
- - - - - - - - - - - - - - - - -
-
- -
    - - - - -
  • - -
    - -
    - - - Unwatch - - -
    - Notifications -
    -
    - - - - - - - -
    -
    -
    - -
    -
  • - -
  • -
    -
    - - -
    -
    - - -
    - -
  • - -
  • -
    - - Fork - -
    - -

    Fork SHARC

    -
    -
    - -
    -

    If this dialog fails to load, you can visit the fork page directly.

    -
    -
    -
    -
    - - -
  • -
- -

- - /SHARC - - -

- -
- - - - - - -
-
-
- - - - - - - - - Permalink - - - - -
- - -
- - Branch: - dev-haps-antenโ€ฆ - - - - - - - -
- -
- - Find file - - - Copy path - -
-
- - -
- - Find file - - - Copy path - -
-
- - - - -
- Fetching contributors… -
- -
- - Cannot retrieve contributors at this time -
-
- - - - -
- -
- -
- 120 lines (88 sloc) - - 3.97 KB -
- -
- -
- Raw - Blame - History -
- - -
- - - - -
- -
-
- -
-
-
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 4 17:08:00 2018
@author: Calil
"""
-
from sharc.antenna.antenna import Antenna
from sharc.parameters.parameters_imt import ParametersImt
from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt
-
import numpy as np
import math
-
class AntennaRes122(Antenna):
"""
Implements reference radiation patterns for HAPS gateway antennas
for use in coordination studies and interference assessment in the
frequency ranges 47.2-47.5 GHz & 47.9-48.2 GHz. (ITU RR Res 133, Resolve 3)
"""
-
def __init__(self, param: ParametersImt, param_ant: ParametersAntennaImt):
super().__init__()
self.peak_gain = param_ant.peak_gain
lmbda = 3e8 / ( param.frequency * 1e6 )
self.d_lmbda = param_ant.diameter / lmbda
-
self.g_l = 2 + 15 * math.log10(self.d_lmbda)
self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l)
self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6)
-
def calculate_gain(self, *args, **kwargs) -> np.array:
phi_vec = np.absolute(kwargs["phi_vec"])
theta_vec = np.absolute(kwargs["theta_vec"])
beams_l = np.absolute(kwargs["beams_l"])
off_axis = self.calculate_off_axis_angle(phi_vec,theta_vec)
-
gain = np.zeros(off_axis.shape)
-
idx_0 = np.where(off_axis < self.phi_m)[0]
gain[idx_0] = self.peak_gain - 0.0025 * np.power(self.d_lmbda * off_axis[idx_0], 2)
-
idx_1 = np.where((self.phi_m <= off_axis) & (off_axis < 48))[0]
gain[idx_1] = 39 - 5 * math.log10(self.d_lmbda) - 25 * np.log10(off_axis[idx_1])
-
idx_2 = np.where((48 <= off_axis) & (off_axis < 180))[0]
gain[idx_2] = -3 - 5 * math.log10(self.d_lmbda)
idx_max_gain = np.where(beams_l == -1)[0]
gain[idx_max_gain] = self.peak_gain
-
return gain
-
def add_beam(self, phi: float, theta: float):
self.beams_list.append((phi, theta))
def calculate_off_axis_angle(self,Az,b):
Az0 = self.beams_list[0][0]
-
a = 90 - self.beams_list[0][1]
C = Az0 - Az
-
off_axis_rad = np.arccos(np.cos(np.radians(a))*np.cos(np.radians(b)) \
+ np.sin(np.radians(a))*np.sin(np.radians(b))*np.cos(np.radians(C)))
off_axis_deg = np.degrees(off_axis_rad)
return off_axis_deg
-
-
if __name__ == '__main__':
import matplotlib.pyplot as plt
-
phi = np.linspace(0.1, 180, num = 100000)
theta = 90*np.ones_like(phi)
beams_idx = np.zeros_like(phi, dtype=int)
-
# initialize antenna parameters
param = ParametersImt()
param.frequency = 10700
param_gt = ParametersAntennaImt()
param_gt.peak_gain = 49.8
param_gt.diameter = 3
antenna_gt = AntennaRes122(param,param_gt)
antenna_gt.add_beam(0,0)
-
gain_gt = antenna_gt.calculate_gain(phi_vec = phi,
theta_vec = theta,
beams_l = beams_idx)
-
param.frequency = 27500
param_lt = ParametersAntennaImt()
param_lt.peak_gain = 36.9
param_lt.diameter = 0.3
antenna_lt = AntennaRes122(param,param_lt)
antenna_lt.add_beam(0,0)
gain_lt = antenna_lt.calculate_gain(phi_vec = phi,
theta_vec = theta,
beams_l = beams_idx)
-
fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object
-
plt.semilogx(phi, gain_gt, "-b", label = "$f = 10.7$ $GHz,$ $D = 3$ $m$")
plt.semilogx(phi, gain_lt, "-r", label = "$f = 27.5$ $GHz,$ $D = 0.3$ $m$")
-
plt.title("ITU-R F.1245 antenna radiation pattern")
plt.xlabel("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()
- - - -
- -
- - - -
- - -
- - -
-
- - - -
-
- -
-
- - -
- - - - - - -
- - - You canโ€™t perform that action at this time. -
- - - - - - - - - - - - - - -
- - - - +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 4 17:08:00 2018 +@author: Calil +""" +from sharc.antenna.antenna import Antenna +from sharc.parameters.imt.parameters_imt import ParametersImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt +import numpy as np +import math + + +class AntennaRes122(Antenna): + """ + Implements reference radiation patterns for HAPS gateway antennas + for use in coordination studies and interference assessment in the + frequency ranges 47.2-47.5 GHz & 47.9-48.2 GHz. (ITU RR Res 133, Resolve 3) + """ + + def __init__(self, param: ParametersImt, param_ant: ParametersAntennaImt): + super().__init__() + self.peak_gain = param_ant.peak_gain + lmbda = 3e8 / (param.frequency * 1e6) + self.d_lmbda = param_ant.diameter / lmbda + self.g_l = 2 + 15 * math.log10(self.d_lmbda) + self.phi_m = (20 / self.d_lmbda) * math.sqrt(self.peak_gain - self.g_l) + self.phi_r = 12.02 * math.pow(self.d_lmbda, -0.6) + + def calculate_gain(self, *args, **kwargs) -> np.array: + phi_vec = np.absolute(kwargs["phi_vec"]) + theta_vec = np.absolute(kwargs["theta_vec"]) + beams_l = np.absolute(kwargs["beams_l"]) + off_axis = self.calculate_off_axis_angle(phi_vec, theta_vec) + gain = np.zeros(off_axis.shape) + idx_0 = np.where(off_axis < self.phi_m)[0] + gain[idx_0] = self.peak_gain - 0.0025 * \ + np.power(self.d_lmbda * off_axis[idx_0], 2) + idx_1 = np.where((self.phi_m <= off_axis) & (off_axis < 48))[0] + gain[idx_1] = 39 - 5 * \ + math.log10(self.d_lmbda) - 25 * np.log10(off_axis[idx_1]) + idx_2 = np.where((48 <= off_axis) & (off_axis < 180))[0] + gain[idx_2] = -3 - 5 * math.log10(self.d_lmbda) + idx_max_gain = np.where(beams_l == -1)[0] + gain[idx_max_gain] = self.peak_gain + return gain + + def add_beam(self, phi: float, theta: float): + self.beams_list.append((phi, theta)) + + def calculate_off_axis_angle(self, Az, b): + Az0 = self.beams_list[0][0] + a = 90 - self.beams_list[0][1] + C = Az0 - Az + off_axis_rad = np.arccos( + np.cos(np.radians(a)) * np.cos(np.radians(b)) + + np.sin(np.radians(a)) * np.sin(np.radians(b)) * np.cos(np.radians(C)), + ) + off_axis_deg = np.degrees(off_axis_rad) + return off_axis_deg + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + phi = np.linspace(0.1, 180, num=100000) + theta = 90 * np.ones_like(phi) + beams_idx = np.zeros_like(phi, dtype=int) + # initialize antenna parameters + param = ParametersImt() + param.frequency = 10700 + param_gt = ParametersAntennaImt() + param_gt.peak_gain = 49.8 + param_gt.diameter = 3 + antenna_gt = AntennaRes122(param, param_gt) + antenna_gt.add_beam(0, 0) + gain_gt = antenna_gt.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=beams_idx, + ) + param.frequency = 27500 + param_lt = ParametersAntennaImt() + param_lt.peak_gain = 36.9 + param_lt.diameter = 0.3 + antenna_lt = AntennaRes122(param, param_lt) + antenna_lt.add_beam(0, 0) + gain_lt = antenna_lt.calculate_gain( + phi_vec=phi, + theta_vec=theta, + beams_l=beams_idx, + ) + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + plt.semilogx(phi, gain_gt, "-b", label="$f = 10.7$ $GHz,$ $D = 3$ $m$") + plt.semilogx(phi, gain_lt, "-r", label="$f = 27.5$ $GHz,$ $D = 0.3$ $m$") + plt.title("ITU-R R.122 antenna radiation pattern") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") + plt.ylabel("Gain relative to $G_m$ [dB]") + plt.legend(loc="lower left") + plt.xlim((phi[0], phi[-1])) + plt.ylim((-20, 50)) + # ax = plt.gca() + # ax.set_yticks([-30, -20, -10, 0]) + # ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + plt.grid() + plt.show() diff --git a/sharc/antenna/antenna_rs1813.py b/sharc/antenna/antenna_rs1813.py index d9ae05685..b2b779928 100644 --- a/sharc/antenna/antenna_rs1813.py +++ b/sharc/antenna/antenna_rs1813.py @@ -6,37 +6,43 @@ """ from sharc.antenna.antenna import Antenna -from sharc.parameters.parameters_eess_passive import ParametersEessPassive +from sharc.parameters.parameters_eess_ss import ParametersEessSS import numpy as np import math + class AntennaRS1813(Antenna): """ - Implements the reference antenna pattern described in - Recommendation ITU-R RS.1813-1. This is the antenna pattern for - Earth exploration-satellite service (EESS) passive sensors to be + Implements the reference antenna pattern described in + Recommendation ITU-R RS.1813-1. This is the antenna pattern for + Earth exploration-satellite service (EESS) passive sensors to be used in compatibility studies in the frequency range 1.4-100 GHz. - + This implementation is in accordance with recommends 2, which refers to - the case where a few interference sources dominate, or where peak - interference values are required in the analysis, the following equations - for the antenna pattern for spaceborne passive sensors should be used, + the case where a few interference sources dominate, or where peak + interference values are required in the analysis, the following equations + for the antenna pattern for spaceborne passive sensors should be used, for antenna diameters greater than 2 times the wavelength. """ - def __init__(self, param: ParametersEessPassive): + def __init__(self, param: ParametersEessSS): super().__init__() - self.lmbda = 3e8 / ( param.frequency * 1e6 ) + self.lmbda = 3e8 / (param.frequency * 1e6) self.d_lmbda = param.antenna_diameter / self.lmbda - + # for sensor F3 with n = 60% and D = 2.2 m, G_max = 52.7 dBi - self.peak_gain = 10 * math.log10(param.antenna_efficiency * math.pow(math.pi * self.d_lmbda, 2)) - #self.peak_gain = param.antenna_gain - - self.phi_m = 22 / self.d_lmbda * \ - math.sqrt(5.5 + 5 * math.log10(math.pow(param.antenna_efficiency, 2) * self.d_lmbda)) + self.peak_gain = 10 * \ + math.log10( + param.antenna_efficiency * + math.pow(math.pi * self.d_lmbda, 2), + ) + # self.peak_gain = param.antenna_gain + self.phi_m = 22 / self.d_lmbda * \ + math.sqrt( + 5.5 + 5 * math.log10(math.pow(param.antenna_efficiency, 2) * self.d_lmbda), + ) def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -44,15 +50,18 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain = np.zeros(phi.shape) id0 = np.where(phi <= 69)[0] - gain[id0] = self.peak_gain - 0.0018 * np.power(self.d_lmbda * phi[id0], 2) - + gain[id0] = self.peak_gain - 0.0018 * \ + np.power(self.d_lmbda * phi[id0], 2) + id1 = np.where((self.phi_m < phi) & (phi <= 69))[0] - gain[id1] = np.maximum( gain[id1], 33 - 5*math.log10(self.d_lmbda) - 25*np.log10(phi[id1]) ) - + gain[id1] = np.maximum( + gain[id1], 33 - 5 * math.log10(self.d_lmbda) - 25 * np.log10(phi[id1]), + ) + id2 = np.where((69 < phi) & (phi <= 180))[0] - gain[id2] = -13 - 5*math.log10(self.d_lmbda) - - gain = np.maximum( -23, gain ) + gain[id2] = -13 - 5 * math.log10(self.d_lmbda) + + gain = np.maximum(-23, gain) return gain @@ -60,10 +69,10 @@ def calculate_gain(self, *args, **kwargs) -> np.array: if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0.1, 180, num = 10000) + phi = np.linspace(0.1, 180, num=10000) # initialize antenna parameters - param = ParametersEessPassive() + param = ParametersEessSS() param.antenna_pattern = "ITU-R RS.1813-1" param.frequency = 23900 param.antenna_gain = 52 @@ -71,13 +80,16 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param.antenna_efficiency = 0.6 antenna = AntennaRS1813(param) - gain = antenna.calculate_gain(off_axis_angle_vec = phi) + gain = antenna.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object - plt.semilogx(phi, gain - param.antenna_gain, "-b", label = "$f = 23.9$ GHz") + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + plt.semilogx(phi, gain - param.antenna_gain, "-b", label="$f = 23.9$ GHz") plt.title("ITU-R RS.1813-1 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Normalized antenna gain [dBi]") plt.legend(loc="lower left") plt.xlim((phi[0], phi[-1])) diff --git a/sharc/antenna/antenna_rs1861_9a.py b/sharc/antenna/antenna_rs1861_9a.py index d27b9be20..86aad9e0d 100644 --- a/sharc/antenna/antenna_rs1861_9a.py +++ b/sharc/antenna/antenna_rs1861_9a.py @@ -6,56 +6,60 @@ """ from sharc.antenna.antenna import Antenna -from sharc.parameters.parameters_eess_passive import ParametersEessPassive +from sharc.parameters.parameters_eess_ss import ParametersEessSS import numpy as np + class AntennaRS1861_9A(Antenna): """ Implements the reference antenna pattern described in Figure 9a from - Recommendation ITU-R RS.1861. + Recommendation ITU-R RS.1861. """ - def __init__(self, param: ParametersEessPassive): + def __init__(self, param: ParametersEessSS): super().__init__() self.peak_gain = param.antenna_gain - def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.absolute(kwargs["off_axis_angle_vec"]) gain = np.zeros(phi.shape) id0 = np.where(phi <= 2.5)[0] - gain[id0] = self.peak_gain - 5.2*np.power(phi[id0], 2) - 0.6*(phi[id0]) - + gain[id0] = self.peak_gain - 5.2 * \ + np.power(phi[id0], 2) - 0.6 * (phi[id0]) + id1 = np.where((2.5 < phi) & (phi <= 10))[0] - gain[id1] = self.peak_gain - 3.47*phi[id1] - 25.3 - + gain[id1] = self.peak_gain - 3.47 * phi[id1] - 25.3 + id2 = np.where((phi > 10))[0] gain[id2] = self.peak_gain - 60 - + return gain if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0, 18, num = 10000) + phi = np.linspace(0, 18, num=10000) # initialize antenna parameters - param = ParametersEessPassive() + param = ParametersEessSS() param.antenna_pattern = "ITU-R RS.1861 Fig 9a" param.antenna_gain = 40 antenna = AntennaRS1861_9A(param) - gain = antenna.calculate_gain(off_axis_angle_vec = phi) + gain = antenna.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 5), facecolor='w', + edgecolor='k', + ) # create a figure object plt.plot(phi, gain - param.antenna_gain, "-b") plt.title("ITU-R RS.1861 Fig 9a antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Normalized antenna gain [dBi]") plt.xlim((phi[0], phi[-1])) plt.ylim((-60, 0)) diff --git a/sharc/antenna/antenna_rs1861_9b.py b/sharc/antenna/antenna_rs1861_9b.py index 64dd89d66..91fc4d9c6 100644 --- a/sharc/antenna/antenna_rs1861_9b.py +++ b/sharc/antenna/antenna_rs1861_9b.py @@ -6,56 +6,60 @@ """ from sharc.antenna.antenna import Antenna -from sharc.parameters.parameters_eess_passive import ParametersEessPassive +from sharc.parameters.parameters_eess_ss import ParametersEessSS import numpy as np + class AntennaRS1861_9B(Antenna): """ Implements the reference antenna pattern described in Figure 9b from - Recommendation ITU-R RS.1861. + Recommendation ITU-R RS.1861. """ - def __init__(self, param: ParametersEessPassive): + def __init__(self, param: ParametersEessSS): super().__init__() self.peak_gain = param.antenna_gain - def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.absolute(kwargs["off_axis_angle_vec"]) gain = np.zeros(phi.shape) id0 = np.where(phi <= 1.25)[0] - gain[id0] = self.peak_gain - 22.8*np.power(phi[id0], 2) - 0.7*(phi[id0]) - + gain[id0] = self.peak_gain - 22.8 * \ + np.power(phi[id0], 2) - 0.7 * (phi[id0]) + id1 = np.where((1.25 < phi) & (phi <= 10))[0] - gain[id1] = self.peak_gain - 3.257*phi[id1] - 32.429 - + gain[id1] = self.peak_gain - 3.257 * phi[id1] - 32.429 + id2 = np.where((phi > 10))[0] gain[id2] = self.peak_gain - 65 - + return gain if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0, 12, num = 10000) + phi = np.linspace(0, 12, num=10000) # initialize antenna parameters - param = ParametersEessPassive() + param = ParametersEessSS() param.antenna_pattern = "ITU-R RS.1861 Fig 9b" param.antenna_gain = 46.7 antenna = AntennaRS1861_9B(param) - gain = antenna.calculate_gain(off_axis_angle_vec = phi) + gain = antenna.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 5), facecolor='w', + edgecolor='k', + ) # create a figure object plt.plot(phi, gain - param.antenna_gain, "-b") plt.title("ITU-R RS.1861 Fig 9b antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Normalized antenna gain [dBi]") plt.xlim((phi[0], phi[-1])) plt.ylim((-70, 0)) diff --git a/sharc/antenna/antenna_rs1861_9c.py b/sharc/antenna/antenna_rs1861_9c.py index d5919124a..ae3a07139 100644 --- a/sharc/antenna/antenna_rs1861_9c.py +++ b/sharc/antenna/antenna_rs1861_9c.py @@ -9,133 +9,135 @@ import numpy as np + class AntennaRS1861_9C(Antenna): """ Implements the reference antenna pattern described in Figure 9c from - Recommendation ITU-R RS.1861. + Recommendation ITU-R RS.1861. """ def __init__(self): super().__init__() self.angle = np.linspace(-178.5, 173, 704) - self.gain = np.array([-44.99, -45.02, -45.00, -44.90, -44.72, -44.58, - -44.41, -44.19, -43.94, -43.64, -43.29, -42.88, - -42.42, -41.91, -41.35, -40.76, -40.16, -39.58, - -39.03, -38.53, -38.09, -37.37, -35.86, -36.22, - -36.59, -35.48, -36.03, -37.46, -37.35, -37.14, - -39.58, -40.71, -40.53, -40.53, -40.57, -40.61, - -40.66, -41.51, -41.98, -42.50, -42.98, -43.37, - -43.58, -42.34, -43.21, -44.54, -41.24, -40.92, - -38.55, -42.53, -39.90, -39.94, -40.54, -41.38, - -43.37, -43.09, -42.53, -42.46, -42.35, -42.17, - -41.90, -41.51, -38.74, -39.84, -41.42, -42.00, - -41.89, -41.54, -41.09, -40.65, -40.34, -40.54, - -41.62, -42.07, -42.31, -42.49, -42.62, -42.70, - -42.76, -42.61, -43.01, -43.62, -43.78, -43.91, - -44.02, -44.10, -44.16, -44.18, -44.18, -44.22, - -44.26, -44.30, -44.35, -44.40, -44.45, -44.48, - -44.51, -44.53, -44.53, -44.51, -44.47, -44.41, - -44.33, -44.24, -44.14, -44.03, -43.92, -43.41, - -43.55, -44.00, -44.24, -44.25, -44.26, -44.25, - -44.24, -44.23, -44.20, -44.17, -44.14, -44.11, - -44.07, -44.04, -44.00, -43.96, -43.93, -43.89, - -43.86, -43.84, -43.79, -43.51, -42.83, -44.48, - -45.35, -43.95, -42.38, -41.08, -42.21, -43.02, - -43.53, -43.54, -43.52, -42.96, -42.32, -41.92, - -41.87, -41.97, -42.00, -40.43, -38.29, -37.30, - -36.16, -34.96, -33.75, -32.62, -31.64, -30.80, - -33.59, -37.23, -44.58, -43.50, -39.41, -37.12, - -36.79, -37.05, -37.45, -37.52, -39.46, -38.97, - -34.51, -31.88, -34.21, -36.81, -34.96, -34.09, - -33.20, -32.80, -34.27, -34.46, -34.41, -34.69, - -35.38, -35.40, -31.33, -28.54, -28.24, -34.10, - -38.21, -36.14, -31.82, -27.22, -24.92, -25.85, - -25.66, -25.61, -27.98, -27.19, -26.83, -26.97, - -27.13, -25.93, -25.41, -24.69, -25.57, -26.66, - -26.03, -24.96, -26.03, -27.23, -27.76, -28.49, - -29.35, -30.31, -31.30, -32.65, -34.56, -34.14, - -35.88, -37.02, -35.69, -34.56, -33.54, -33.55, - -32.70, -29.91, -28.79, -27.74, -26.86, -25.98, - -27.85, -27.84, -24.98, -28.11, -31.29, -34.43, - -36.55, -32.95, -30.07, -30.52, -30.95, -31.31, - -31.56, -31.64, -31.51, -31.12, -30.42, -29.22, - -27.73, -26.09, -24.45, -22.97, -21.81, -21.11, - -21.53, -23.84, -26.45, -25.60, -24.63, -22.21, - -24.94, -21.85, -22.14, -22.42, -20.61, -18.24, - -16.33, -15.85, -17.24, -17.36, -21.61, -25.45, - -20.42, -22.94, -20.06, -14.79, -17.40, -13.46, - -14.10, -12.54, -12.10, -11.62, -11.11, -10.57, - -10.02, -9.48, -8.94, -8.43, -7.95, -7.51, - -7.13, -6.82, -6.88, -8.11, -11.17, -12.35, - -13.68, -15.07, -16.43, -17.68, -18.74, -19.52, - -19.96, -19.84, -19.13, -18.08, -16.97, -16.07, - -16.00, -15.08, -13.85, -12.46, -11.17, -10.27, - -10.03, -10.73, -12.37, -14.68, -17.33, -20.03, - -22.47, -25.97, -24.22, -25.91, -27.09, -26.43, - -19.74, -10.09, -7.06, -7.26, -8.66, -10.51, - -9.71, -8.90, -7.64, -5.13, -4.77, -4.60, - -4.54, -5.12, -9.01, -10.72, -8.52, -5.68, - -5.16, -4.34, -4.88, -3.50, 0.30, 4.92, - 9.96, 15.04, 19.76, 23.72, 28.78, 29.68, 31.00, - 32.00, 33.00, 34.40, 31.00, 28.90, 27.26, 24.64, - 23.74, 22.84, 19.39, 15.44, 12.15, 6.36, -1.75, - -10.11, -15.67, -10.96, -5.37, -3.10, -3.68, - -7.87, -17.43, -29.34, -22.33, -18.07, -23.14, - -21.89, -33.38, -35.00, -20.28, -16.43, -16.20, - -17.35, -19.21, -22.32, -22.40, -19.40, -15.77, - -12.03, -10.67, -10.44, -9.31, -8.27, -8.38, - -8.41, -12.85, -11.45, -9.65, -7.73, -6.21, - -6.44, -6.82, -6.80, -6.15, -5.39, -6.53, - -18.72, -12.85, -11.70, -11.42, -11.01, -10.63, - -10.46, -10.66, -11.39, -13.65, -17.35, -21.36, - -24.72, -25.50, -25.27, -25.88, -25.83, -22.67, - -20.65, -20.56, -19.68, -18.80, -18.71, -19.78, - -20.04, -20.58, -22.58, -24.90, -27.03, -29.09, - -28.45, -28.31, -28.19, -32.10, -29.01, -20.88, - -21.45, -24.33, -25.21, -25.52, -26.47, -33.62, - -32.81, -22.49, -16.86, -17.71, -17.69, -17.52, - -17.31, -17.21, -17.64, -18.96, -20.86, -22.82, - -24.64, -25.87, -25.98, -26.26, -26.62, -26.98, - -27.30, -27.49, -27.49, -27.24, -22.06, -26.26, - -23.21, -20.29, -19.36, -19.03, -18.97, -18.89, - -19.30, -20.33, -21.54, -22.91, -24.23, -26.67, - -26.13, -25.47, -25.41, -25.04, -24.92, -24.99, - -25.08, -25.21, -25.36, -25.53, -27.37, -29.54, - -25.92, -26.72, -28.23, -28.74, -28.55, -28.00, - -27.38, -27.88, -28.97, -30.21, -31.07, -29.28, - -24.89, -23.30, -22.23, -22.09, -23.27, -24.93, - -26.79, -27.61, -28.34, -28.00, -28.97, -30.84, - -33.27, -36.07, -42.84, -28.77, -27.17, -28.22, - -25.00, -24.41, -24.80, -24.70, -24.71, -25.87, - -26.70, -27.72, -28.93, -30.34, -32.52, -36.41, - -39.71, -41.12, -40.40, -38.97, -33.59, -36.20, - -34.81, -33.93, -32.75, -31.40, -29.99, -28.66, - -26.95, -30.14, -28.23, -29.60, -31.93, -32.96, - -40.79, -43.19, -42.76, -42.01, -41.19, -40.35, - -39.46, -38.40, -37.03, -34.78, -32.51, -32.41, - -31.74, -30.93, -31.91, -33.37, -35.39, -38.52, - -39.64, -40.78, -41.82, -43.07, -41.41, -40.59, - -39.60, -38.30, -36.87, -35.52, -34.33, -34.59, - -35.14, -35.70, -36.24, -36.69, -37.01, -36.38, - -40.25, -39.16, -40.97, -41.71, -40.35, -38.54, - -37.17, -39.18, -39.07, -38.28, -36.92, -35.11, - -33.70, -33.14, -35.18, -37.16, -39.40, -41.61, - -43.46, -44.75, -43.97, -41.15, -41.18, -37.08, - -39.43, -38.04, -38.53, -38.68, -38.76, -38.88, - -38.86, -38.39, -38.96, -39.76, -39.18, -38.34, - -37.39, -36.59, -36.15, -36.35, -37.51, -39.28, - -41.21, -42.86, -43.65, -43.99, -44.06, -43.93, - -43.64, -43.26, -42.83, -41.59, -43.01, -44.55, - -44.87, -45.05, -45.13, -45.13, -45.08, -45.01, - -44.96, -45.12, -43.85, -42.62, -42.87, -42.48, - -41.70, -42.37, -43.00, -43.55, -43.82, -43.33, - -40.58, -40.51, -34.91, -29.58, -32.33, -32.17, - -31.31, -30.80, -31.71, -34.14, -37.80, -41.68, - -44.42, -42.80, -41.95, -41.26, -40.40, -39.44, - -38.48, -37.59, -36.87, -36.18, -36.64, -37.05, - -38.90, -41.09, -43.16, -44.64, -45.12, -44.86, - -44.13, -43.22, -42.38, -42.27, -42.06]), - + self.gain = np.array([ + -44.99, -45.02, -45.00, -44.90, -44.72, -44.58, + -44.41, -44.19, -43.94, -43.64, -43.29, -42.88, + -42.42, -41.91, -41.35, -40.76, -40.16, -39.58, + -39.03, -38.53, -38.09, -37.37, -35.86, -36.22, + -36.59, -35.48, -36.03, -37.46, -37.35, -37.14, + -39.58, -40.71, -40.53, -40.53, -40.57, -40.61, + -40.66, -41.51, -41.98, -42.50, -42.98, -43.37, + -43.58, -42.34, -43.21, -44.54, -41.24, -40.92, + -38.55, -42.53, -39.90, -39.94, -40.54, -41.38, + -43.37, -43.09, -42.53, -42.46, -42.35, -42.17, + -41.90, -41.51, -38.74, -39.84, -41.42, -42.00, + -41.89, -41.54, -41.09, -40.65, -40.34, -40.54, + -41.62, -42.07, -42.31, -42.49, -42.62, -42.70, + -42.76, -42.61, -43.01, -43.62, -43.78, -43.91, + -44.02, -44.10, -44.16, -44.18, -44.18, -44.22, + -44.26, -44.30, -44.35, -44.40, -44.45, -44.48, + -44.51, -44.53, -44.53, -44.51, -44.47, -44.41, + -44.33, -44.24, -44.14, -44.03, -43.92, -43.41, + -43.55, -44.00, -44.24, -44.25, -44.26, -44.25, + -44.24, -44.23, -44.20, -44.17, -44.14, -44.11, + -44.07, -44.04, -44.00, -43.96, -43.93, -43.89, + -43.86, -43.84, -43.79, -43.51, -42.83, -44.48, + -45.35, -43.95, -42.38, -41.08, -42.21, -43.02, + -43.53, -43.54, -43.52, -42.96, -42.32, -41.92, + -41.87, -41.97, -42.00, -40.43, -38.29, -37.30, + -36.16, -34.96, -33.75, -32.62, -31.64, -30.80, + -33.59, -37.23, -44.58, -43.50, -39.41, -37.12, + -36.79, -37.05, -37.45, -37.52, -39.46, -38.97, + -34.51, -31.88, -34.21, -36.81, -34.96, -34.09, + -33.20, -32.80, -34.27, -34.46, -34.41, -34.69, + -35.38, -35.40, -31.33, -28.54, -28.24, -34.10, + -38.21, -36.14, -31.82, -27.22, -24.92, -25.85, + -25.66, -25.61, -27.98, -27.19, -26.83, -26.97, + -27.13, -25.93, -25.41, -24.69, -25.57, -26.66, + -26.03, -24.96, -26.03, -27.23, -27.76, -28.49, + -29.35, -30.31, -31.30, -32.65, -34.56, -34.14, + -35.88, -37.02, -35.69, -34.56, -33.54, -33.55, + -32.70, -29.91, -28.79, -27.74, -26.86, -25.98, + -27.85, -27.84, -24.98, -28.11, -31.29, -34.43, + -36.55, -32.95, -30.07, -30.52, -30.95, -31.31, + -31.56, -31.64, -31.51, -31.12, -30.42, -29.22, + -27.73, -26.09, -24.45, -22.97, -21.81, -21.11, + -21.53, -23.84, -26.45, -25.60, -24.63, -22.21, + -24.94, -21.85, -22.14, -22.42, -20.61, -18.24, + -16.33, -15.85, -17.24, -17.36, -21.61, -25.45, + -20.42, -22.94, -20.06, -14.79, -17.40, -13.46, + -14.10, -12.54, -12.10, -11.62, -11.11, -10.57, + -10.02, -9.48, -8.94, -8.43, -7.95, -7.51, + -7.13, -6.82, -6.88, -8.11, -11.17, -12.35, + -13.68, -15.07, -16.43, -17.68, -18.74, -19.52, + -19.96, -19.84, -19.13, -18.08, -16.97, -16.07, + -16.00, -15.08, -13.85, -12.46, -11.17, -10.27, + -10.03, -10.73, -12.37, -14.68, -17.33, -20.03, + -22.47, -25.97, -24.22, -25.91, -27.09, -26.43, + -19.74, -10.09, -7.06, -7.26, -8.66, -10.51, + -9.71, -8.90, -7.64, -5.13, -4.77, -4.60, + -4.54, -5.12, -9.01, -10.72, -8.52, -5.68, + -5.16, -4.34, -4.88, -3.50, 0.30, 4.92, + 9.96, 15.04, 19.76, 23.72, 28.78, 29.68, 31.00, + 32.00, 33.00, 34.40, 31.00, 28.90, 27.26, 24.64, + 23.74, 22.84, 19.39, 15.44, 12.15, 6.36, -1.75, + -10.11, -15.67, -10.96, -5.37, -3.10, -3.68, + -7.87, -17.43, -29.34, -22.33, -18.07, -23.14, + -21.89, -33.38, -35.00, -20.28, -16.43, -16.20, + -17.35, -19.21, -22.32, -22.40, -19.40, -15.77, + -12.03, -10.67, -10.44, -9.31, -8.27, -8.38, + -8.41, -12.85, -11.45, -9.65, -7.73, -6.21, + -6.44, -6.82, -6.80, -6.15, -5.39, -6.53, + -18.72, -12.85, -11.70, -11.42, -11.01, -10.63, + -10.46, -10.66, -11.39, -13.65, -17.35, -21.36, + -24.72, -25.50, -25.27, -25.88, -25.83, -22.67, + -20.65, -20.56, -19.68, -18.80, -18.71, -19.78, + -20.04, -20.58, -22.58, -24.90, -27.03, -29.09, + -28.45, -28.31, -28.19, -32.10, -29.01, -20.88, + -21.45, -24.33, -25.21, -25.52, -26.47, -33.62, + -32.81, -22.49, -16.86, -17.71, -17.69, -17.52, + -17.31, -17.21, -17.64, -18.96, -20.86, -22.82, + -24.64, -25.87, -25.98, -26.26, -26.62, -26.98, + -27.30, -27.49, -27.49, -27.24, -22.06, -26.26, + -23.21, -20.29, -19.36, -19.03, -18.97, -18.89, + -19.30, -20.33, -21.54, -22.91, -24.23, -26.67, + -26.13, -25.47, -25.41, -25.04, -24.92, -24.99, + -25.08, -25.21, -25.36, -25.53, -27.37, -29.54, + -25.92, -26.72, -28.23, -28.74, -28.55, -28.00, + -27.38, -27.88, -28.97, -30.21, -31.07, -29.28, + -24.89, -23.30, -22.23, -22.09, -23.27, -24.93, + -26.79, -27.61, -28.34, -28.00, -28.97, -30.84, + -33.27, -36.07, -42.84, -28.77, -27.17, -28.22, + -25.00, -24.41, -24.80, -24.70, -24.71, -25.87, + -26.70, -27.72, -28.93, -30.34, -32.52, -36.41, + -39.71, -41.12, -40.40, -38.97, -33.59, -36.20, + -34.81, -33.93, -32.75, -31.40, -29.99, -28.66, + -26.95, -30.14, -28.23, -29.60, -31.93, -32.96, + -40.79, -43.19, -42.76, -42.01, -41.19, -40.35, + -39.46, -38.40, -37.03, -34.78, -32.51, -32.41, + -31.74, -30.93, -31.91, -33.37, -35.39, -38.52, + -39.64, -40.78, -41.82, -43.07, -41.41, -40.59, + -39.60, -38.30, -36.87, -35.52, -34.33, -34.59, + -35.14, -35.70, -36.24, -36.69, -37.01, -36.38, + -40.25, -39.16, -40.97, -41.71, -40.35, -38.54, + -37.17, -39.18, -39.07, -38.28, -36.92, -35.11, + -33.70, -33.14, -35.18, -37.16, -39.40, -41.61, + -43.46, -44.75, -43.97, -41.15, -41.18, -37.08, + -39.43, -38.04, -38.53, -38.68, -38.76, -38.88, + -38.86, -38.39, -38.96, -39.76, -39.18, -38.34, + -37.39, -36.59, -36.15, -36.35, -37.51, -39.28, + -41.21, -42.86, -43.65, -43.99, -44.06, -43.93, + -43.64, -43.26, -42.83, -41.59, -43.01, -44.55, + -44.87, -45.05, -45.13, -45.13, -45.08, -45.01, + -44.96, -45.12, -43.85, -42.62, -42.87, -42.48, + -41.70, -42.37, -43.00, -43.55, -43.82, -43.33, + -40.58, -40.51, -34.91, -29.58, -32.33, -32.17, + -31.31, -30.80, -31.71, -34.14, -37.80, -41.68, + -44.42, -42.80, -41.95, -41.26, -40.40, -39.44, + -38.48, -37.59, -36.87, -36.18, -36.64, -37.05, + -38.90, -41.09, -43.16, -44.64, -45.12, -44.86, + -44.13, -43.22, -42.38, -42.27, -42.06, + ]), def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.array(kwargs["off_axis_angle_vec"]) @@ -143,24 +145,27 @@ def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.minimum(phi, 173) gain = np.interp(phi, self.angle, self.gain[0]) - + return gain if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(-180, 180, num = 1000) + phi = np.linspace(-180, 180, num=1000) antenna = AntennaRS1861_9C() - gain = antenna.calculate_gain(off_axis_angle_vec = phi) + gain = antenna.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,5), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 5), facecolor='w', + edgecolor='k', + ) # create a figure object plt.plot(phi, gain, "-b") plt.title("ITU-R RS.1861 Fig 9c antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Antenna gain [dBi]") plt.xlim((phi[0], phi[-1])) plt.ylim((-50, 40)) diff --git a/sharc/antenna/antenna_rs2043.py b/sharc/antenna/antenna_rs2043.py new file mode 100644 index 000000000..3a402a3fa --- /dev/null +++ b/sharc/antenna/antenna_rs2043.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +""" +@Created: Luciano Camilo on Tue Aug 10 19:42:00 2021 + +""" + +import numpy as np + + +class AntennaRS2043(object): + """ + Implements the reference antenna pattern described in Table 9 from Recommendation + ITU-R RS.2043. + + Attributes + ---------- + antenna_gain (float): maximum gain of FS omni antenna + """ + + def calculate_gain(self, **kwargs) -> np.array: + phi = np.asarray(kwargs["off_axis_angle_vec"]) + theta = np.asarray(kwargs["theta_vec"]) + gain = self.get_gain_az(phi) + self.get_gain_el(theta) + + return gain + + def get_gain_az(self, phi: np.array) -> np.array: + """ + Returns the antenna gain in the azimuth plane for the given direction. + """ + gh = np.zeros(phi.shape) + + for i in range(len(phi)): + if -0.542 <= phi[i] <= 0.542: + gh[i] = 0 - 45.53 * ((phi[i]) ** 2) + + if 0.542 < phi[i] <= 5.053: + gh[i] = -11.210 - 4.0220 * phi[i] + + if -0.542 > phi[i] >= -5.053: + gh[i] = -11.210 + 4.0220 * phi[i] + + if 5.053 < phi[i] <= 14.708: + gh[i] = -26.720 - 0.9530 * phi[i] + + if -5.053 > phi[i] >= -14.708: + gh[i] = -26.720 + 0.9530 * phi[i] + + if 14.708 < phi[i] <= 30: + gh[i] = -35.031 - 0.3880 * phi[i] + + if -14.708 > phi[i] >= -30: + gh[i] = -35.031 + 0.3880 * phi[i] + + if 30 < phi[i] <= 59.915: + gh[i] = -41.836 - 0.1580 * phi[i] + + if -30 > phi[i] >= -59.915: + gh[i] = -41.836 + 0.1580 * phi[i] + + if phi[i] > 59.915: + gh[i] = -51.387 + + if phi[i] < -59.915: + gh[i] = -51.387 + + return gh + + def get_gain_el(self, theta: np.array) -> np.array: + """ + Returns the antenna gain in the elevation plane for the given direction. + """ + gv = np.zeros(len(theta)) + + for i in range(len(theta)): + if -1.149 < theta[i] < 1.149: + gv[i] = 47 - 9.91 * ((theta[i]) ** 2) + + if 1.149 <= theta[i] <= 9.587: + gv[i] = 35.189 - 1.9440 * theta[i] + + if -1.149 >= theta[i] >= -9.587: + gv[i] = 35.189 + 1.9440 * theta[i] + + if 9.587 <= theta[i] <= 29.976: + gv[i] = 21.043 - 0.4680 * theta[i] + + if -9.587 >= theta[i] >= -29.976: + gv[i] = 21.043 + 0.4680 * theta[i] + + if 29.976 <= theta[i] <= 50: + gv[i] = 12.562 - 0.1850 * theta[i] + + if -29.976 >= theta[i] >= -50: + gv[i] = 12.562 + 0.1850 * theta[i] + + if theta[i] > 50: + gv[i] = 3.291 + + if theta[i] < -50: + gv[i] = 3.291 + + return gv + + +if __name__ == '__main__': + + from matplotlib import pyplot as plt + """ + Test routine - Comparison between SHARC code and Table 9 of ITU R - RS.2043-0 + """ + + antenna = AntennaRS2043() + phi = np.arange(-90, 90, step=0.01) + theta = np.arange(-40, 40, step=0.01) + # phi = np.arange(0, 180, step=0.01) + # theta = np.arange(0, 90, step=0.01) + csfont = {'fontname': 'Times New Roman'} + hfont = {'fontname': 'Times New Roman'} + + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 6)) + + # ax1.semilogx(theta, antenna.get_gain_el(theta=theta), '-',color='blue', label='ITU-R RS.2043-0 (Table 9)') + # ax2.semilogx(phi, antenna.get_gain_az(phi=phi), '-', color='darkorange', label='ITU-R RS.2043-0 (Table 9)') + + ax1.plot( + theta, antenna.get_gain_el(theta=theta), '-', + color='blue', label='ITU-R RS.2043-0 (Table 9)', + ) + ax2.plot( + phi, antenna.get_gain_az(phi=phi) + 47, '-', + color='darkorange', label='ITU-R RS.2043-0 (Table 9)', + ) + ax1.grid() + ax2.grid() + ax1.legend() + ax2.legend() + + ax1.set_title('ITU-R RS.2043-0 - Vertical Antenna Pattern') + ax2.set_title('ITU-R RS.2043-0 - Horizontal Antenna Pattern') + ax1.set_xlabel('Elevation angle (degrees)', fontsize=12, color='black') + ax1.set_ylabel('Gain (dBi)', fontsize=12, color='black') + ax2.set_xlabel('Azimuth (degrees)', fontsize=12, color='black') + ax2.set_ylabel('Gain (dBi)', fontsize=12, color='black') + + plt.show() diff --git a/sharc/antenna/antenna_s1528.py b/sharc/antenna/antenna_s1528.py index a4e52ba58..fe133ebd6 100644 --- a/sharc/antenna/antenna_s1528.py +++ b/sharc/antenna/antenna_s1528.py @@ -4,22 +4,138 @@ @author: edgar """ - +import sys from sharc.antenna.antenna import Antenna -from sharc.parameters.parameters_fss_ss import ParametersFssSs - +from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 +from sharc.parameters.constants import SPEED_OF_LIGHT import math import numpy as np -import sys +from scipy.special import jn, jn_zeros + + +class AntennaS1528Taylor(Antenna): + """ + Implements Recommendation ITU-R S.1528-0 Section 1.4: Satellite antenna reference pattern given by an analytical + function which models the side lobes of the non-GSO satellite operating in the fixed-satellite service below 30 GHz. + It is proposed to use a circular Taylor illumination function which gives the maximum flexibility to + adapt the theoretical pattern to the real one. It takes into account the side-lobes effect of an antenna + diagram. + """ + + def __init__(self, param: ParametersAntennaS1528): + # Gmax + self.peak_gain = param.antenna_gain + self.frequency_mhz = param.frequency + self.bandwidth_mhz = param.bandwidth + # Wavelength of the lowest frequency of the band of interest (in meters). + self.lamb = (SPEED_OF_LIGHT / 1e6) / (self.frequency_mhz - self.bandwidth_mhz / 2) + # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum + # gain and the gain at the peak of the first side lobe. + self.slr = param.slr + # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function) + self.n_side_lobes = param.n_side_lobes + # Radial and transverse sizes of the effective radiating area of the satellite transmit antenna (m). + self.l_r = param.l_r + self.l_t = param.l_t + + self.roll_off = param.roll_off + + # Beam roll-off (difference between the maximum gain and the gain at the edge of the illuminated beam) + # Possible values are 3, 5, and 7 + if int(self.roll_off) not in [3, 5, 7]: + raise ValueError( + f"AntennaS1528Taylor: Invalid value for roll_off factor {self.roll_off}") + self.roll_off = int(self.roll_off) + + def calculate_gain(self, *args, **kwargs) -> np.array: + phi = np.abs(np.radians(kwargs.get('phi', 0))) + theta = np.abs(np.radians(kwargs.get('theta', 0))) + + # Intermediary variables + A = (1 / np.pi) * np.arccosh(10 ** (self.slr / 20)) + j1_roots = jn_zeros(1, self.n_side_lobes) / np.pi + sigma = j1_roots[-1] / np.sqrt(A ** 2 + (self.n_side_lobes - 1 / 2) ** 2) + u = (np.pi / self.lamb) * np.sqrt((self.l_r * np.sin(theta) * np.cos(phi)) ** 2 + + (self.l_t * np.sin(theta) * np.sin(phi)) ** 2) + + mu = jn_zeros(1, self.n_side_lobes - 1) / np.pi + v = np.ones(u.shape + (self.n_side_lobes - 1,)) + + for i, ui in enumerate(mu): + v[..., i] = (1 - u ** 2 / (np.pi ** 2 * sigma ** 2 * + (A ** 2 + (i + 1 - 0.5) ** 2))) / (1 - (u / (np.pi * ui)) ** 2) + + # Take care of divide-by-zero + with np.errstate(divide='ignore', invalid='ignore'): + gain = self.peak_gain + 20 * \ + np.log10(np.abs((2 * jn(1, u) / u) * np.prod(v, axis=-1))) + + # Replace undefined values with -inf (or other desired value) + gain = np.nan_to_num(gain, nan=-np.inf) + + return gain + + +class AntennaS1528Leo(Antenna): + """ + Implements Recommendation ITU-R S.1528-0 Section 1.3 - LEO: Satellite antenna radiation + patterns for LEO orbit satellite antennas operating in the + fixed-satellite service below 30 GHz. + """ + + def __init__(self, param: ParametersAntennaS1528): + super().__init__() + self.peak_gain = param.antenna_gain + self.psi_b = param.antenna_3_dB / 2 + # near-in-side-lobe level (dB) relative to the peak gain required by + # the system design + self.l_s = -6.75 + # for elliptical antennas, this is the ratio major axis/minor axis + # we assume circular antennas, so z = 1 + # self.z = 1 + # far-out side-lobe level [dBi] + self.l_f = 5 + self.y = 1.5 * self.psi_b + self.z = self.y * np.power(10, 0.04 * (self.peak_gain + self.l_s - self.l_f)) + + def calculate_gain(self, *args, **kwargs) -> np.array: + """ + Calculates the gain in the given direction. + + Parameters + ---------- + off_axis_angle_vec (np.array): azimuth angles (phi_vec) [degrees] + + Returns + ------- + gain (np.array): gain corresponding to each of the given directions + """ + psi = np.absolute(kwargs["off_axis_angle_vec"]) + gain = np.zeros(len(psi)) + + idx_0 = np.where((psi <= self.y))[0] + gain[idx_0] = self.peak_gain - 3 * \ + np.power(psi[idx_0] / self.psi_b, 2) + + idx_1 = np.where((self.y < psi) & (psi <= self.z))[0] + gain[idx_1] = self.peak_gain + self.l_s - \ + 25 * np.log10(psi[idx_1] / self.y) + + idx_3 = np.where((self.z < psi) & (psi <= 180))[0] + gain[idx_3] = self.l_f + + return gain + class AntennaS1528(Antenna): """ Implements Recommendation ITU-R S.1528-0: Satellite antenna radiation patterns for non-geostationary orbit satellite antennas operating in the - fixed-satellite service below 30 GHz + fixed-satellite service below 30 GHz. + This implementation refers to the pattern described in S.1528-0 Section 1.2 """ - def __init__(self, param: ParametersFssSs): + def __init__(self, param: ParametersAntennaS1528): super().__init__() self.peak_gain = param.antenna_gain @@ -35,28 +151,34 @@ def __init__(self, param: ParametersFssSs): self.l_f = 0 # back-lobe level - self.l_b = np.maximum(0, 15 + self.l_s + 0.25*self.peak_gain + 5*math.log10(self.z)) - + self.l_b = np.maximum( + 0, 15 + self.l_s + 0.25 * + self.peak_gain + 5 * math.log10(self.z), + ) # one-half the 3 dB beamwidth in the plane of interest - self.psi_b = param.antenna_3_dB/2 + self.psi_b = param.antenna_3_dB / 2 if self.l_s == -15: - self.a = 2.58*math.sqrt(1 - 1.4*math.log10(self.z)) + self.a = 2.58 * math.sqrt(1 - 1.4 * math.log10(self.z)) elif self.l_s == -20: - self.a = 2.58*math.sqrt(1 - 1.0*math.log10(self.z)) + self.a = 2.58 * math.sqrt(1 - 1.0 * math.log10(self.z)) elif self.l_s == -25: - self.a = 2.58*math.sqrt(1 - 0.6*math.log10(self.z)) + self.a = 2.58 * math.sqrt(1 - 0.6 * math.log10(self.z)) elif self.l_s == -30: - self.a = 2.58*math.sqrt(1 - 0.4*math.log10(self.z)) + self.a = 2.58 * math.sqrt(1 - 0.4 * math.log10(self.z)) else: - sys.stderr.write("ERROR\nInvalid AntennaS1528 L_s parameter: " + str(self.l_s)) + sys.stderr.write( + "ERROR\nInvalid AntennaS1528 L_s parameter: " + str(self.l_s), + ) sys.exit(1) self.b = 6.32 self.alpha = 1.5 - self.x = self.peak_gain + self.l_s + 25*math.log10(self.b * self.psi_b) - self.y = self.b * self.psi_b * math.pow(10, 0.04 * (self.peak_gain + self.l_s - self.l_f)) + self.x = self.peak_gain + self.l_s + \ + 25 * math.log10(self.b * self.psi_b) + self.y = self.b * self.psi_b * \ + math.pow(10, 0.04 * (self.peak_gain + self.l_s - self.l_f)) def calculate_gain(self, *args, **kwargs) -> np.array: psi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -64,12 +186,15 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain = np.zeros(len(psi)) idx_0 = np.where(psi < self.a * self.psi_b)[0] - gain[idx_0] = self.peak_gain - 3 * np.power(psi[idx_0] / self.psi_b, self.alpha) + gain[idx_0] = self.peak_gain - 3 * \ + np.power(psi[idx_0] / self.psi_b, self.alpha) - idx_1 = np.where((self.a * self.psi_b < psi) & (psi <= 0.5 * self.b * self.psi_b))[0] + idx_1 = np.where((self.a * self.psi_b < psi) & + (psi <= 0.5 * self.b * self.psi_b))[0] gain[idx_1] = self.peak_gain + self.l_s + 20 * math.log10(self.z) - idx_2 = np.where((0.5 * self.b * self.psi_b < psi) & (psi <= self.b * self.psi_b))[0] + idx_2 = np.where((0.5 * self.b * self.psi_b < psi) & + (psi <= self.b * self.psi_b))[0] gain[idx_2] = self.peak_gain + self.l_s idx_3 = np.where((self.b * self.psi_b < psi) & (psi <= self.y))[0] @@ -87,46 +212,115 @@ def calculate_gain(self, *args, **kwargs) -> np.array: if __name__ == '__main__': import matplotlib.pyplot as plt + ## Plot gains for ITU-R-S.1528-SECTION1.2 # initialize antenna parameters - param = ParametersFssSs() - param.antenna_gain = 39 - param.antenna_pattern = "ITU-R S.1528-0" - param.antenna_3_dB = 2 - psi = np.linspace(0, 30, num = 1000) + param = ParametersAntennaS1528() + param.antenna_gain = 30 + param.antenna_pattern = "ITU-R-S.1528-SECTION1.2" + param.antenna_3_dB = 4.4127 + + psi = np.linspace(0, 30, num=1000) param.antenna_l_s = -15 antenna = AntennaS1528(param) - gain15 = antenna.calculate_gain(off_axis_angle_vec = psi) + gain15 = antenna.calculate_gain(off_axis_angle_vec=psi) param.antenna_l_s = -20 antenna = AntennaS1528(param) - gain20 = antenna.calculate_gain(off_axis_angle_vec = psi) + gain20 = antenna.calculate_gain(off_axis_angle_vec=psi) param.antenna_l_s = -25 antenna = AntennaS1528(param) - gain25 = antenna.calculate_gain(off_axis_angle_vec = psi) + gain25 = antenna.calculate_gain(off_axis_angle_vec=psi) param.antenna_l_s = -30 antenna = AntennaS1528(param) - gain30 = antenna.calculate_gain(off_axis_angle_vec = psi) - - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object + gain30 = antenna.calculate_gain(off_axis_angle_vec=psi) - plt.plot(psi, gain15 - param.antenna_gain, "-b", label="$L_S = -15$ dB") - plt.plot(psi, gain20 - param.antenna_gain, "-r", label="$L_S = -20$ dB") - plt.plot(psi, gain25 - param.antenna_gain, "-g", label="$L_S = -25$ dB") - plt.plot(psi, gain30 - param.antenna_gain, "-k", label="$L_S = -30$ dB") + ## Plot gains for ITU-R-S.1528-LEO + # initialize antenna parameters + param = ParametersAntennaS1528() + param.antenna_gain = 30 + param.antenna_pattern = "ITU-R-S.1528-LEO" + param.antenna_3_dB = 1.6 + psi = np.linspace(0, 20, num=1000) + + param.antenna_l_s = -6.75 + antenna = AntennaS1528Leo(param) + gain_leo = antenna.calculate_gain(off_axis_angle_vec=psi) + + fig = plt.figure(figsize=(8, 7), facecolor='w', + edgecolor='k') # create a figure object + + psi_norm = psi / (param.antenna_3_dB / 2) + plt.plot(psi_norm, gain15 - param.antenna_gain, "-b", label="$L_S = -15$ dB") + plt.plot(psi_norm, gain20 - param.antenna_gain, "-r", label="$L_S = -20$ dB") + plt.plot(psi_norm, gain25 - param.antenna_gain, "-g", label="$L_S = -25$ dB") + plt.plot(psi_norm, gain30 - param.antenna_gain, "-k", label="$L_S = -30$ dB") + plt.plot(psi_norm, gain_leo - param.antenna_gain, "-c", label="$L_S = -6.75$ dB (R1.3 - LEO)") plt.ylim((-40, 10)) - plt.xlim((0, 30)) + plt.xlim((0, np.max(psi_norm))) + plt.xticks(np.arange(np.floor(np.max(psi_norm)))) plt.title("ITU-R S.1528-0 antenna radiation pattern") - plt.xlabel("Relative off-axis angle, $\psi/\psi_{3dB}$") - plt.ylabel("Gain relative to $G_{max}$ [dB]") + plt.xlabel(r"Relative off-axis angle, $\psi/\psi_{3dB}$") + plt.ylabel(r"Gain relative to $G_{max}$ [dB]") plt.legend(loc="upper right") + plt.grid() -# ax = plt.gca() -# ax.set_yticks([-30, -20, -10, 0]) -# ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) - + ## Plot gains for ITU-R-S.1528-LEO + # initialize antenna parameters + param = ParametersAntennaS1528() + param.antenna_gain = 35 + param.antenna_pattern = "ITU-R-S.1528-LEO" + param.antenna_3_dB = 1.6 + psi = np.linspace(0, 20, num=1000) + + param.antenna_l_s = -6.75 + antenna = AntennaS1528Leo(param) + gain_leo = antenna.calculate_gain(off_axis_angle_vec=psi) + + fig = plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k') # create a figure object + psi_norm = psi / (param.antenna_3_dB / 2) + plt.plot(psi_norm, gain_leo, "-b", label="$L_S = -6.75$ dB") + + # plt.ylim((-40, 10)) + plt.xlim((0, np.max(psi_norm))) + plt.xticks(np.arange(np.floor(np.max(psi_norm)))) + plt.title("ITU-R S.1528-0 LEO antenna radiation pattern") + plt.xlabel(r"Relative off-axis angle, $\psi/\psi_{3dB}$") + plt.ylabel(r"Gain relative to $G_{max}$ [dB]") + plt.legend(loc="upper right") plt.grid() + + # Section 1.4 (Taylor) + params = ParametersAntennaS1528( + antenna_gain=0, + frequency=6000, + bandwidth=500, + slr=20, + n_side_lobes=4, + l_r=0.5, + l_t=0.5, + roll_off=3 + ) + + # Create an instance of AntennaS1528Taylor + antenna = AntennaS1528Taylor(params) + print(f"Taylor antenna.lamb = {antenna.lamb}") + + # Define phi angles from 0 to 60 degrees for plotting + theta_angles = np.linspace(0, 60, 600) + + # Calculate gains for each phi angle at a fixed theta angle (e.g., theta=0) + gain = antenna.calculate_gain(theta=theta_angles, phi=np.zeros_like(theta_angles)) + + # Plot the antenna gain as a function of phi angle + plt.figure(figsize=(10, 6)) + plt.plot(theta_angles, gain) + plt.xlabel('Theta (degrees)') + plt.ylabel('Gain (dB)') + plt.title('Normalized Antenna - Section 1.4') + plt.grid(True) + plt.show() diff --git a/sharc/antenna/antenna_s1855.py b/sharc/antenna/antenna_s1855.py index 320c0cd31..b762cd9b8 100644 --- a/sharc/antenna/antenna_s1855.py +++ b/sharc/antenna/antenna_s1855.py @@ -11,6 +11,7 @@ from sharc.antenna.antenna import Antenna from sharc.parameters.parameters_fss_ss import ParametersFssSs + class AntennaS1855(Antenna): """" Implements amntenna radiation pattern for Earth Station according @@ -40,7 +41,6 @@ def __init__(self, params: ParametersFssSs): self.frequency = params.frequency self.antenna_gain = params.antenna_gain - def calculate_gain(self, *args, **kwargs) -> np.array: """ Calculates the gain of the antenna af arrays of angles. @@ -62,15 +62,14 @@ def calculate_gain(self, *args, **kwargs) -> np.array: phi_list = kwargs["off_axis_angle_vec"] theta_list = kwargs["theta_vec"] - gain = np.empty(phi_list.shape, dtype = np.float) + gain = np.empty(phi_list.shape, dtype=float) for i in range(len(phi_list)): gain[i] = self.get_gain_pair(phi_list[i], theta_list[i]) return gain - - def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float: + def get_gain_pair(self, phi: float, theta: float) -> float: """ Calculates the gain of the antenna of a pair of angles. @@ -88,7 +87,7 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float: """ gain = None wavelength = 3e8 / (self.frequency * 1000000) - d_to_wavel = self.diameter/wavelength + d_to_wavel = self.diameter / wavelength phimin1 = 15.85 * math.pow(d_to_wavel, -0.6) phimin2 = 118 * math.pow(d_to_wavel, -1.06) if phimin1 > phimin2: @@ -96,25 +95,29 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float: else: phimin = phimin2 - if d_to_wavel >= 46.8: - if phi < phimin: + if phi < phimin: gain = self.antenna_gain elif phi >= phimin and phi <= 7: - gain = 29 + 3 * np.power(np.sin(theta * math.pi / 180) , 2) - 25 * np.log10(phi) + gain = 29 + 3 * \ + np.power(np.sin(theta * math.pi / 180), 2) - \ + 25 * np.log10(phi) elif phi > 7 and phi <= 9.2: - gain = 7.9 + (3 * np.power(np.sin(theta * np.pi / 180),2)) * (9.2 - phi) / 2.2 + gain = 7.9 + \ + (3 * np.power(np.sin(theta * np.pi / 180), 2)) * (9.2 - phi) / 2.2 elif phi > 9.2 and phi <= 48: gain = 32 - 25 * np.log10(phi) else: return -10 elif d_to_wavel < 46.8 and d_to_wavel >= 15: - if phi < phimin: + if phi < phimin: gain = self.antenna_gain elif phi >= phimin and phi <= 7: - gain = 29 + 3 * np.pow(np.sin(theta * np.pi / 180),2) - 25 * np.log10(phi) + gain = 29 + 3 * \ + np.pow(np.sin(theta * np.pi / 180), 2) - 25 * np.log10(phi) elif phi > 7 and phi <= 9.2: - gain = 7.9 + (3 * np.pow(np.sin(theta * np.pi / 180)),2) * (9.2 - phi) / 2.2 + gain = 7.9 + \ + (3 * np.pow(np.sin(theta * np.pi / 180)), 2) * (9.2 - phi) / 2.2 elif phi > 9.2 and phi <= 30.2: gain = 32 - 25 * np.log10(phi) elif phi > 30.2 and phi <= 70: @@ -138,16 +141,20 @@ def get_gain_pair(self, phi: np.float, theta: np.float) -> np.float: antenna = AntennaS1855(params_fss_ss) # Plot radiation pattern for theta = 90 degrees - off_axis_angle_vec = np.linspace(0.01, 180, num = 10000) - theta_90 = 90*np.ones(off_axis_angle_vec.shape) - theta = 0*np.ones(off_axis_angle_vec.shape) - - gain_90 = antenna.calculate_gain(off_axis_angle_vec = off_axis_angle_vec, theta_vec = theta_90) - gain = antenna.calculate_gain(off_axis_angle_vec = off_axis_angle_vec, theta_vec = theta) - - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') - plt.semilogx(off_axis_angle_vec, gain_90, "-b", label = "$\\theta = 90$ deg") - plt.semilogx(off_axis_angle_vec, gain, "-r", label = "$\\theta = 0$ deg") + off_axis_angle_vec = np.linspace(0.01, 180, num=10000) + theta_90 = 90 * np.ones(off_axis_angle_vec.shape) + theta = 0 * np.ones(off_axis_angle_vec.shape) + + gain_90 = antenna.calculate_gain( + off_axis_angle_vec=off_axis_angle_vec, theta_vec=theta_90, + ) + gain = antenna.calculate_gain( + off_axis_angle_vec=off_axis_angle_vec, theta_vec=theta, + ) + + fig = plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k') + plt.semilogx(off_axis_angle_vec, gain_90, "-b", label="$\\theta = 90$ deg") + plt.semilogx(off_axis_angle_vec, gain, "-r", label="$\\theta = 0$ deg") plt.xlabel("Off-axis angle, $\\varphi$ [deg]") plt.ylabel("Gain [dBi]") diff --git a/sharc/antenna/antenna_s465.py b/sharc/antenna/antenna_s465.py index 23826435e..4ae77d868 100644 --- a/sharc/antenna/antenna_s465.py +++ b/sharc/antenna/antenna_s465.py @@ -11,6 +11,7 @@ import numpy as np import math + class AntennaS465(Antenna): """ Implements the Earth station antenna pattern in the fixed-satellite service @@ -20,7 +21,7 @@ class AntennaS465(Antenna): def __init__(self, param: ParametersFssEs): super().__init__() self.peak_gain = param.antenna_gain - lmbda = 3e8 / ( param.frequency * 1e6 ) + lmbda = 3e8 / (param.frequency * 1e6) D_lmbda = param.diameter / lmbda if D_lmbda >= 50: @@ -40,7 +41,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain[idx_1] = 32 - 25 * np.log10(phi[idx_1]) idx_2 = np.where((48 <= phi) & (phi <= 180))[0] - gain [idx_2] = -10 + gain[idx_2] = -10 return gain @@ -48,7 +49,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0.1, 100, num = 100000) + phi = np.linspace(0.1, 100, num=100000) # initialize antenna parameters param27 = ParametersFssEs() @@ -58,7 +59,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param27.diameter = 0.45 antenna27 = AntennaS465(param27) - gain27 = antenna27.calculate_gain(off_axis_angle_vec = phi) + gain27 = antenna27.calculate_gain(off_axis_angle_vec=phi) param43 = ParametersFssEs() param43.antenna_pattern = "ITU-R S.465-6" @@ -66,23 +67,32 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param43.antenna_gain = 50 param43.diameter = 1.8 antenna43 = AntennaS465(param43) - gain43 = antenna43.calculate_gain(off_axis_angle_vec = phi) - - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object - - plt.semilogx(phi, gain27 - param27.antenna_gain, "-b", label = "$f = 27$ $GHz,$ $D = 0.45$ $m$") - plt.semilogx(phi, gain43 - param43.antenna_gain, "-r", label = "$f = 43$ $GHz,$ $D = 1.8$ $m$") + gain43 = antenna43.calculate_gain(off_axis_angle_vec=phi) + + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + + plt.semilogx( + phi, gain27 - param27.antenna_gain, "-b", + label="$f = 27$ $GHz,$ $D = 0.45$ $m$", + ) + plt.semilogx( + phi, gain43 - param43.antenna_gain, "-r", + label="$f = 43$ $GHz,$ $D = 1.8$ $m$", + ) plt.title("ITU-R S.465-6 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Gain relative to $G_m$ [dB]") plt.legend(loc="lower left") plt.xlim((phi[0], phi[-1])) plt.ylim((-80, 10)) - #ax = plt.gca() - #ax.set_yticks([-30, -20, -10, 0]) - #ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + # ax = plt.gca() + # ax.set_yticks([-30, -20, -10, 0]) + # ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) plt.grid() plt.show() diff --git a/sharc/antenna/antenna_s580.py b/sharc/antenna/antenna_s580.py index c2fc9fcd3..0a3213b9a 100644 --- a/sharc/antenna/antenna_s580.py +++ b/sharc/antenna/antenna_s580.py @@ -10,6 +10,7 @@ import numpy as np + class AntennaS580(Antenna): """ Implements the Earth station antenna pattern in the EESS/ISS service @@ -19,11 +20,11 @@ class AntennaS580(Antenna): def __init__(self, param: ParametersFssEs): super().__init__() self.peak_gain = param.antenna_gain - lmbda = 3e8 / ( param.frequency * 1e6 ) + lmbda = 3e8 / (param.frequency * 1e6) self.phi_min = 1 if 100 * lmbda / param.diameter > 1: - self.phi_min = 100 * lmbda / param.diameter + self.phi_min = 100 * lmbda / param.diameter def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -45,7 +46,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: if __name__ == '__main__': import matplotlib.pyplot as plt - phi = np.linspace(0.1, 100, num = 100000) + phi = np.linspace(0.1, 100, num=100000) # initialize antenna parameters param27 = ParametersFssEs() @@ -55,7 +56,7 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param27.diameter = 9.6 antenna27 = AntennaS580(param27) - gain27 = antenna27.calculate_gain(off_axis_angle_vec = phi) + gain27 = antenna27.calculate_gain(off_axis_angle_vec=phi) param = ParametersFssEs() param.antenna_pattern = "ITU-R S.580-6" @@ -63,23 +64,32 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param.antenna_gain = 50 param.diameter = 0.45 antenna = AntennaS580(param) - gain = antenna.calculate_gain(off_axis_angle_vec = phi) - - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object - - plt.semilogx(phi, gain27 - param27.antenna_gain, "-b", label = "$f = 27$ $GHz,$ $D = 9.6$ $m$") - plt.semilogx(phi, gain - param.antenna_gain, "-r", label = "$f = 27$ $GHz,$ $D = 0.45$ $m$") + gain = antenna.calculate_gain(off_axis_angle_vec=phi) + + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + + plt.semilogx( + phi, gain27 - param27.antenna_gain, "-b", + label="$f = 27$ $GHz,$ $D = 9.6$ $m$", + ) + plt.semilogx( + phi, gain - param.antenna_gain, "-r", + label="$f = 27$ $GHz,$ $D = 0.45$ $m$", + ) plt.title("ITU-R S.580 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Gain relative to $G_m$ [dB]") plt.legend(loc="lower left") plt.xlim((phi[0], phi[-1])) plt.ylim((-80, 10)) - #ax = plt.gca() - #ax.set_yticks([-30, -20, -10, 0]) - #ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + # ax = plt.gca() + # ax.set_yticks([-30, -20, -10, 0]) + # ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) plt.grid() plt.show() diff --git a/sharc/antenna/antenna_s672.py b/sharc/antenna/antenna_s672.py index f71c13a63..9c5eb62bb 100644 --- a/sharc/antenna/antenna_s672.py +++ b/sharc/antenna/antenna_s672.py @@ -11,6 +11,7 @@ import numpy as np import sys + class AntennaS672(Antenna): """ Implements the satellite antenna pattern in the fixed-satellite service @@ -29,14 +30,16 @@ def __init__(self, param: ParametersFssSs): elif self.l_s == -30: self.a = 3.16 else: - sys.stderr.write("ERROR\nInvalid AntennaS672 L_s parameter: " + self.l_s) + sys.stderr.write( + "ERROR\nInvalid AntennaS672 L_s parameter: " + self.l_s, + ) sys.exit(1) self.b = 6.32 - self.psi_0 = param.antenna_3_dB/2 - self.psi_1 = self.psi_0 * np.power(10, (self.peak_gain + self.l_s + 20)/25) - + self.psi_0 = param.antenna_3_dB / 2 + self.psi_1 = self.psi_0 * \ + np.power(10, (self.peak_gain + self.l_s + 20) / 25) def calculate_gain(self, *args, **kwargs) -> np.array: psi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -47,13 +50,15 @@ def calculate_gain(self, *args, **kwargs) -> np.array: gain[idx_0] = self.peak_gain idx_1 = np.where((self.psi_0 <= psi) & (psi <= self.a * self.psi_0))[0] - gain[idx_1] = self.peak_gain - 3 * np.power(psi[idx_1]/self.psi_0, 2) + gain[idx_1] = self.peak_gain - 3 * np.power(psi[idx_1] / self.psi_0, 2) - idx_2 = np.where((self.a * self.psi_0 < psi) & (psi <= self.b * self.psi_0))[0] + idx_2 = np.where((self.a * self.psi_0 < psi) & + (psi <= self.b * self.psi_0))[0] gain[idx_2] = self.peak_gain + self.l_s idx_3 = np.where((self.b * self.psi_0 < psi) & (psi <= self.psi_1))[0] - gain[idx_3] = self.peak_gain + self.l_s + 20 - 25 * np.log10(psi[idx_3]/self.psi_0) + gain[idx_3] = self.peak_gain + self.l_s + \ + 20 - 25 * np.log10(psi[idx_3] / self.psi_0) return gain @@ -66,36 +71,51 @@ def calculate_gain(self, *args, **kwargs) -> np.array: param.antenna_gain = 50 param.antenna_pattern = "ITU-R S.672-4" param.antenna_3_dB = 2 - psi = np.linspace(1, 30, num = 1000) + psi = np.linspace(1, 30, num=1000) param.antenna_l_s = -20 antenna = AntennaS672(param) - gain20 = antenna.calculate_gain(off_axis_angle_vec = psi) + gain20 = antenna.calculate_gain(off_axis_angle_vec=psi) param.antenna_l_s = -25 antenna = AntennaS672(param) - gain25 = antenna.calculate_gain(off_axis_angle_vec = psi) + gain25 = antenna.calculate_gain(off_axis_angle_vec=psi) param.antenna_l_s = -30 antenna = AntennaS672(param) - gain30 = antenna.calculate_gain(off_axis_angle_vec = psi) - - fig = plt.figure(figsize=(12,7), facecolor='w', edgecolor='k') # create a figure object - - plt.semilogx(psi, gain20 - param.antenna_gain, "-b", label="$L_S = -20$ dB") - plt.semilogx(psi, gain25 - param.antenna_gain, "-r", label="$L_S = -25$ dB") - plt.semilogx(psi, gain30 - param.antenna_gain, "-g", label="$L_S = -30$ dB") + gain30 = antenna.calculate_gain(off_axis_angle_vec=psi) + + fig = plt.figure( + figsize=(12, 7), facecolor='w', + edgecolor='k', + ) # create a figure object + + plt.semilogx( + psi, gain20 - param.antenna_gain, + "-b", label="$L_S = -20$ dB", + ) + plt.semilogx( + psi, gain25 - param.antenna_gain, + "-r", label="$L_S = -25$ dB", + ) + plt.semilogx( + psi, gain30 - param.antenna_gain, + "-g", label="$L_S = -30$ dB", + ) plt.ylim((-33.8, 0)) plt.xlim((1, 100)) plt.title("ITU-R S.672-4 antenna radiation pattern") - plt.xlabel("Relative off-axis angle, $\psi/\psi_0$") + plt.xlabel(r"Relative off-axis angle, $\psi/\psi_0$") plt.ylabel("Gain relative to $G_m$ [dB]") plt.legend(loc="upper right") ax = plt.gca() ax.set_yticks([-30, -20, -10, 0]) - ax.set_xticks(np.linspace(1, 9, 9).tolist() + np.linspace(10, 100, 10).tolist()) + ax.set_xticks( + np.linspace(1, 9, 9).tolist() + + np.linspace(10, 100, 10).tolist(), + ) plt.grid() plt.show() diff --git a/sharc/antenna/antenna_sa509.py b/sharc/antenna/antenna_sa509.py index 0f000bfe7..b26ca374b 100644 --- a/sharc/antenna/antenna_sa509.py +++ b/sharc/antenna/antenna_sa509.py @@ -4,11 +4,12 @@ @author: Calil """ +import numpy as np from sharc.antenna.antenna import Antenna from sharc.parameters.parameters_ras import ParametersRas +from sharc.parameters.constants import SPEED_OF_LIGHT -import numpy as np class AntennaSA509(Antenna): """ @@ -21,19 +22,21 @@ def __init__(self, param: ParametersRas): # Set basic attributes self.diameter = param.diameter self.efficiency = param.antenna_efficiency - self.wavelength = param.SPEED_OF_LIGHT/(param.frequency*1e6) + self.wavelength = SPEED_OF_LIGHT / (param.frequency * 1e6) # Effective area - self.effective_area = self.efficiency*(np.pi*self.diameter**2)/4 + self.effective_area = self.efficiency * (np.pi * self.diameter**2) / 4 # Diagram parameters - self.g_0 = 10*np.log10(self.efficiency*\ - (np.pi*self.diameter/self.wavelength)**2) - self.phi_0 = 20*np.sqrt(3)/(self.diameter/self.wavelength) + self.g_0 = 10 * np.log10( + self.efficiency * + (np.pi * self.diameter / self.wavelength)**2, + ) + self.phi_0 = 20 * np.sqrt(3) / (self.diameter / self.wavelength) # Limit parameters - self.phi_1 = self.phi_0*np.sqrt(20/3) - self.phi_2 = 10**((49-self.g_0)/25) + self.phi_1 = self.phi_0 * np.sqrt(20 / 3) + self.phi_2 = 10**((49 - self.g_0) / 25) def calculate_gain(self, *args, **kwargs) -> np.array: phi = np.absolute(kwargs["off_axis_angle_vec"]) @@ -42,13 +45,17 @@ def calculate_gain(self, *args, **kwargs) -> np.array: # First part interval_idx = np.where(np.logical_and(phi >= 0, phi < self.phi_1)) - gain[interval_idx] = self.g_0 - 3*(phi[interval_idx]/self.phi_0)**2 + gain[interval_idx] = self.g_0 - 3 * (phi[interval_idx] / self.phi_0)**2 # Second part - interval_idx = np.where(np.logical_and(phi >= self.phi_1, phi < self.phi_2)) + interval_idx = np.where( + np.logical_and( + phi >= self.phi_1, phi < self.phi_2, + ), + ) gain[interval_idx] = self.g_0 - 20 # Third part interval_idx = np.where(np.logical_and(phi >= self.phi_2, phi < 48)) - gain[interval_idx] = 29 - 25*np.log10(phi[interval_idx]) + gain[interval_idx] = 29 - 25 * np.log10(phi[interval_idx]) # Fourth part interval_idx = np.where(np.logical_and(phi >= 48, phi < 80)) gain[interval_idx] = -13 @@ -61,14 +68,14 @@ def calculate_gain(self, *args, **kwargs) -> np.array: return gain + if __name__ == '__main__': import matplotlib.pyplot as plt - par = ParametersRas(); + par = ParametersRas() par.diameter = 1 par.antenna_efficiency = 1 par.frequency = 43000 - par.SPEED_OF_LIGHT = 3e8 antenna1 = AntennaSA509(par) par.diameter = 7 @@ -76,23 +83,25 @@ def calculate_gain(self, *args, **kwargs) -> np.array: par.diameter = 10 antenna10 = AntennaSA509(par) - phi = np.linspace(0.1, 180, num = 100000) - gain1 = antenna1.calculate_gain(off_axis_angle_vec = phi) - gain7 = antenna7.calculate_gain(off_axis_angle_vec = phi) - gain10 = antenna10.calculate_gain(off_axis_angle_vec = phi) + phi = np.linspace(0.1, 180, num=100000) + gain1 = antenna1.calculate_gain(off_axis_angle_vec=phi) + gain7 = antenna7.calculate_gain(off_axis_angle_vec=phi) + gain10 = antenna10.calculate_gain(off_axis_angle_vec=phi) - fig = plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 7), facecolor='w', + edgecolor='k', + ) # create a figure object - plt.semilogx(phi, gain1, "-b", label = "$f = 43$ $GHz,$ $D = 1$ $m$") - plt.semilogx(phi, gain7, "-r", label = "$f = 43$ $GHz,$ $D = 7$ $m$") - plt.semilogx(phi, gain10, "-k", label = "$f = 43$ $GHz,$ $D = 10$ $m$") + plt.semilogx(phi, gain1, "-b", label="$f = 43$ $GHz,$ $D = 1$ $m$") + plt.semilogx(phi, gain7, "-r", label="$f = 43$ $GHz,$ $D = 7$ $m$") + plt.semilogx(phi, gain10, "-k", label="$f = 43$ $GHz,$ $D = 10$ $m$") plt.title("ITU-R SA.509-3 antenna radiation pattern") - plt.xlabel("Off-axis angle $\phi$ [deg]") + plt.xlabel(r"Off-axis angle $\phi$ [deg]") plt.ylabel("Gain [dBi]") plt.legend(loc="lower left") plt.xlim((phi[0], phi[-1])) plt.grid() plt.show() - diff --git a/sharc/antenna/beamforming_normalization/__init__.py b/sharc/antenna/beamforming_normalization/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/antenna/beamforming_normalization/__init__.py +++ b/sharc/antenna/beamforming_normalization/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/antenna/beamforming_normalization/beamforming_normalizer.py b/sharc/antenna/beamforming_normalization/beamforming_normalizer.py index d9db97119..f95e01ce9 100644 --- a/sharc/antenna/beamforming_normalization/beamforming_normalizer.py +++ b/sharc/antenna/beamforming_normalization/beamforming_normalizer.py @@ -39,6 +39,7 @@ class BeamformingNormalizer(object): antenna (AntennaBeamformingImt): antenna to which calculate normalization """ + def __init__(self, res_deg: float, tol: float): """ Class constructor @@ -61,16 +62,22 @@ def __init__(self, res_deg: float, tol: float): self.theta_min_rad = np.deg2rad(self.theta_min_deg) self.theta_max_rad = np.deg2rad(self.theta_max_deg) - self.phi_vals_deg = np.arange(self.phi_min_deg, - self.phi_max_deg,res_deg) - self.theta_vals_deg = np.arange(self.theta_min_deg, - self.theta_max_deg,res_deg) + self.phi_vals_deg = np.arange( + self.phi_min_deg, + self.phi_max_deg, res_deg, + ) + self.theta_vals_deg = np.arange( + self.theta_min_deg, + self.theta_max_deg, res_deg, + ) self.antenna = None - def generate_correction_matrix(self, - par: AntennaPar, - file_name: str, - testing = False): + def generate_correction_matrix( + self, + par: AntennaPar, + file_name: str, + testing=False, + ): """ Generates the correction factor matrix and saves it in a file @@ -80,34 +87,44 @@ def generate_correction_matrix(self, file_name (str): name of file to which save the correction matrix """ # Create antenna object - azi = 0 # Antenna azimuth: 0 degrees for simplicity - ele = 0 # Antenna elevation: 0 degrees as well - self.antenna = AntennaBeamformingImt(par,azi,ele) + azi = 0 # Antenna azimuth: 0 degrees for simplicity + ele = 0 # Antenna elevation: 0 degrees as well + self.antenna = AntennaBeamformingImt(par, azi, ele) # For co-channel beamforming # Correction factor numpy array - correction_factor_co = np.zeros((len(self.phi_vals_deg),len(self.theta_vals_deg))) - error_co = np.empty((len(self.phi_vals_deg),len(self.theta_vals_deg)), dtype=tuple) + correction_factor_co = np.zeros( + (len(self.phi_vals_deg), len(self.theta_vals_deg)), + ) + error_co = np.empty( + (len(self.phi_vals_deg), len(self.theta_vals_deg)), dtype=tuple, + ) # Loop throug all the possible beams for phi_idx, phi in enumerate(self.phi_vals_deg): - if not testing: print('\n' + str(100*phi_idx/len(self.phi_vals_deg)) + '%') + if not testing: + print('\n' + str(100 * phi_idx / len(self.phi_vals_deg)) + '%') for theta_idx, theta in enumerate(self.theta_vals_deg): s = '\tphi = ' + str(phi) + ', theta = ' + str(theta) - if not testing: print(s) + if not testing: + print(s) stdout.flush() - correction_factor_co[phi_idx,theta_idx], error_co[phi_idx,theta_idx] = self.calculate_correction_factor(phi,theta,True) - + correction_factor_co[phi_idx, theta_idx], error_co[phi_idx, theta_idx] = \ + self.calculate_correction_factor(phi, theta, True) - correction_factor_adj, error_adj = self.calculate_correction_factor(0,0,False) + correction_factor_adj, error_adj = self.calculate_correction_factor( + 0, 0, False, + ) # Save in file - self._save_files(correction_factor_co, - error_co, - correction_factor_adj, - error_adj, - par, - file_name) + self._save_files( + correction_factor_co, + error_co, + correction_factor_adj, + error_adj, + par, + file_name, + ) def calculate_correction_factor(self, phi_beam: float, theta_beam: float, c_chan: bool): """ @@ -125,26 +142,29 @@ def calculate_correction_factor(self, phi_beam: float, theta_beam: float, c_chan error (tuple): upper and lower error bounds [dB] """ if c_chan: - self.antenna.add_beam(phi_beam,theta_beam) + self.antenna.add_beam(phi_beam, theta_beam) beam = int(len(self.antenna.beams_list) - 1) - int_f = lambda t,p: \ - np.power(10,self.antenna._beam_gain(np.rad2deg(p),np.rad2deg(t),beam)/10)*np.sin(t) + + def int_f(t, p): + return np.power(10, self.antenna._beam_gain(np.rad2deg(p), np.rad2deg(t), beam) / 10) * np.sin(t) else: - int_f = lambda t,p: \ - np.power(10,self.antenna.element.element_pattern(np.rad2deg(p),np.rad2deg(t))/10)*np.sin(t) + def int_f(t, p): + return np.power(10, self.antenna.element.element_pattern(np.rad2deg(p), np.rad2deg(t)) / 10) * np.sin(t) - integral_val, err = dblquad(int_f,self.phi_min_rad,self.phi_max_rad, - lambda p: self.theta_min_rad, - lambda p: self.theta_max_rad, - epsabs=self.tolerance, - epsrel=0.0) + integral_val, err = dblquad( + int_f, self.phi_min_rad, self.phi_max_rad, + lambda p: self.theta_min_rad, + lambda p: self.theta_max_rad, + epsabs=self.tolerance, + epsrel=0.0, + ) - correction_factor = -10*np.log10(integral_val/(4*np.pi)) + correction_factor = -10 * np.log10(integral_val / (4 * np.pi)) - hig_bound = -10*np.log10((integral_val - err)/(4*np.pi)) - low_bound = -10*np.log10((integral_val + err)/(4*np.pi)) + hig_bound = -10 * np.log10((integral_val - err) / (4 * np.pi)) + low_bound = -10 * np.log10((integral_val + err) / (4 * np.pi)) - return correction_factor, (low_bound,hig_bound) + return correction_factor, (low_bound, hig_bound) def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name): """ @@ -182,15 +202,18 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name): par (AtennaPar): antenna parameters used in normalization file_name (str): name of file to which save normalization data """ - np.savez(file_name, - resolution = self.resolution_deg, - phi_range = (self.phi_min_deg, self.phi_max_deg), - theta_range = (self.theta_min_deg, self.theta_max_deg), - correction_factor_co_channel = cf_co, - error_co_channel = err_co, - correction_factor_adj_channel = cf_adj, - error_adj_channel = err_adj, - parameters = par) + np.savez( + file_name, + resolution=self.resolution_deg, + phi_range=(self.phi_min_deg, self.phi_max_deg), + theta_range=(self.theta_min_deg, self.theta_max_deg), + correction_factor_co_channel=cf_co, + error_co_channel=err_co, + correction_factor_adj_channel=cf_adj, + error_adj_channel=err_adj, + parameters=par, + ) + if __name__ == '__main__': """ @@ -202,7 +225,7 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name): # Create normalizer object resolution = 5 tolerance = 1e-1 - norm = BeamformingNormalizer(resolution,tolerance) + norm = BeamformingNormalizer(resolution, tolerance) # Antenna parameters adjacent_antenna_model = "SINGLE_ELEMENT" @@ -220,32 +243,36 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name): vert_spacing = 0.5 minimum_array_gain = -200 down_tilt = 0 - par = AntennaPar(normalization, - norm_file, - element_pattern, - element_max_g, - element_phi_deg_3db, - element_theta_deg_3db, - element_am, - element_sla_v, - n_rows, - n_columns, - horiz_spacing, - vert_spacing, - down_tilt) + par = AntennaPar( + normalization, + norm_file, + element_pattern, + element_max_g, + element_phi_deg_3db, + element_theta_deg_3db, + element_am, + element_sla_v, + n_rows, + n_columns, + horiz_spacing, + vert_spacing, + down_tilt, + ) # Set range of values & calculate correction factor norm.theta_vals_deg = np.array([90]) file_name = 'main_test.npz' - norm.generate_correction_matrix(par,file_name) + norm.generate_correction_matrix(par, file_name) data = np.load(file_name) correction_factor = data['correction_factor_co_channel'] err_low, err_high = zip(*np.ravel(data['error_co_channel'])) - plt.plot(norm.phi_vals_deg,correction_factor, - norm.phi_vals_deg,err_low,'r--', - norm.phi_vals_deg,err_high,'r--') - plt.xlim(-180,180) + plt.plot( + norm.phi_vals_deg, correction_factor, + norm.phi_vals_deg, err_low, 'r--', + norm.phi_vals_deg, err_high, 'r--', + ) + plt.xlim(-180, 180) plt.ylabel(r"Correction factor [dB]") plt.xlabel(r"Azimuth angle $\phi$ [deg]") plt.title(r"Elevation angle $\theta$ = 90 deg") @@ -253,21 +280,22 @@ def _save_files(self, cf_co, err_co, cf_adj, err_adj, par, file_name): data.close() # Set range of values & calculate correction factor - norm = BeamformingNormalizer(resolution,tolerance) + norm = BeamformingNormalizer(resolution, tolerance) norm.phi_vals_deg = np.array([0]) - norm.generate_correction_matrix(par,file_name) + norm.generate_correction_matrix(par, file_name) data = np.load(file_name) correction_factor = data['correction_factor_co_channel'] err_low, err_high = zip(*np.ravel(data['error_co_channel'])) - plt.plot(norm.theta_vals_deg,np.transpose(correction_factor), - norm.theta_vals_deg,np.transpose(err_low),'r--', - norm.theta_vals_deg,np.transpose(err_high),'r--') - plt.xlim(0,180) + plt.plot( + norm.theta_vals_deg, np.transpose(correction_factor), + norm.theta_vals_deg, np.transpose(err_low), 'r--', + norm.theta_vals_deg, np.transpose(err_high), 'r--', + ) + plt.xlim(0, 180) plt.ylabel(r"Correction factor [dB]") plt.xlabel(r"Elevation angle $\theta$ [deg]") plt.title(r"Azimuth angle $\phi$ = 0 deg") plt.show() data.close() os.remove(file_name) - diff --git a/sharc/antenna/beamforming_normalization/normalize_script.py b/sharc/antenna/beamforming_normalization/normalize_script.py index 3bfa0cb53..71084570c 100644 --- a/sharc/antenna/beamforming_normalization/normalize_script.py +++ b/sharc/antenna/beamforming_normalization/normalize_script.py @@ -67,18 +67,18 @@ parameters (AntennaPar): antenna parameters used in the normalization """ +from sharc.antenna.beamforming_normalization.beamforming_normalizer import BeamformingNormalizer +from sharc.support.named_tuples import AntennaPar import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "..")) -from sharc.support.named_tuples import AntennaPar -from sharc.antenna.beamforming_normalization.beamforming_normalizer import BeamformingNormalizer if __name__ == "__main__": ########################################################################### - ## List of antenna parameters to which calculate the normalization factors. - adjacent_antenna_model = "" # not needed here + # List of antenna parameters to which calculate the normalization factors. + adjacent_antenna_model = "" # not needed here normalization = False # not needed here normalization_data = None # not needed here element_pattern = "M2101" @@ -94,36 +94,40 @@ multiplication_factor = 12 minimum_array_gain = -200 downtilt = 0 - + file_names = ["bs_norm_8x8_050.npz"] - param_list = [AntennaPar(adjacent_antenna_model, - normalization, - normalization_data, - element_pattern, - element_max_g, - element_phi_3db, - element_theta_3db, - element_am, - element_sla_v, - n_rows, - n_columns, - element_horiz_spacing, - element_vert_spacing, - multiplication_factor, - minimum_array_gain, - downtilt)] + param_list = [ + AntennaPar( + adjacent_antenna_model, + normalization, + normalization_data, + element_pattern, + element_max_g, + element_phi_3db, + element_theta_3db, + element_am, + element_sla_v, + n_rows, + n_columns, + element_horiz_spacing, + element_vert_spacing, + multiplication_factor, + minimum_array_gain, + downtilt, + ), + ] ########################################################################### - ## Setup + # Setup # General parameters resolution = 5 tolerance = 1e-2 - + # Create object norm = BeamformingNormalizer(resolution, tolerance) ########################################################################### - ## Normalize and save + # Normalize and save for par, file in zip(param_list, file_names): s = 'Generating ' + file print(s) - - norm.generate_correction_matrix(par,file) + + norm.generate_correction_matrix(par, file) diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md b/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md new file mode 100644 index 000000000..b84dfe69e --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/README.md @@ -0,0 +1,39 @@ + +# Spectrum Sharing Study: IMT HIBS vs. Radio Astronomy (2.6 GHz) +This directory holds the code and data for a simulation study investigating spectrum sharing between: + +IMT HIBS as a (NTN) system and Radio Astronomy Service (RAS) operating in the 2.6 GHz band. + +Each campaing puts the RAS station farther from the IMT BS nadir point over the Earth's surface. + +Main campaign parameters: +- IMT topolgy: NTN with IMT BS at 20km of altitude +- IMT @2680MHz/20MHz BW +- RAS @2695/10MHz BW +- Channel model: P.619 for both IMT and IMT-RAS links. + +# Folder Structure +inputs: This folder contains parameter files used to configure the simulation campaigns. Each file defines specific scenarios for the NTN and RAS systems. +scripts: This folder holds post-processing and plotting scripts used to analyze the simulation data. These scripts generate performance metrics and visualizations based on the simulation outputs. + +# Dependencies +This project may require additional software or libraries to run the simulations and post-processing scripts. Please refer to the individual script files for specific dependencies. + +# Running the Simulations +`python3 main_cli.py -p campaigns/imt_hibs_ras_2600_MHz/input/` +or on root +`python3 sharc/main_cli.py -p sharc/campaigns/imt_hibs_ras_2600_MHz/input/` + +# Running the scripts + +## For plotting + +`python3 sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py` + +## For starting simulation multi thread + +`sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py` + +## For starting simulation single thread + +`sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py` \ No newline at end of file diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml new file mode 100644 index 000000000..056387dbe --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.yaml @@ -0,0 +1,367 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots : 10 + ########################################################################### + # 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: FSS_SS + ########################################################################### + # 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: MACROCELL + 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 : 7000 + ########################################################################### + # 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 : P1411 + 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 +ras: + ########################################################################### + # frequency [MHz] + frequency: 2695 + ########################################################################### + # bandwidth [MHz] + bandwidth: 10 + ########################################################################### + # Station noise temperature [K] + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + antenna: + pattern: OMNI + gain: 0 + channel_model: P619 + + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 15 + # The RAS station lat + earth_station_lat_deg: -23.17889 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml new file mode 100644 index 000000000..770557325 --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45km.yaml @@ -0,0 +1,366 @@ +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: FSS_SS + ########################################################################### + # 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: MACROCELL + 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 : 7000 + ########################################################################### + # 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 : P1411 + 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 +fss_ss: + ########################################################################### + # 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: P1411 + + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 15 + # The RAS station lat + earth_station_lat_deg: -23.17889 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml new file mode 100644 index 000000000..948e721ea --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_500km.yaml @@ -0,0 +1,366 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + # frequency [MHz] + frequency: 2695 + ########################################################################### + # bandwidth [MHz] + bandwidth: 10 + ########################################################################### + # Station noise temperature [K] + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 500000 + ########################################################################### + y: 0 + antenna: + pattern: OMNI + gain: 0 + channel_model: P619 + + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 15 + # The RAS station lat + earth_station_lat_deg: -23.17889 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml new file mode 100644 index 000000000..aab85cb3c --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90km.yaml @@ -0,0 +1,366 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + # frequency [MHz] + frequency: 2695 + ########################################################################### + # bandwidth [MHz] + bandwidth: 10 + ########################################################################### + # Station noise temperature [K] + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 90000 + ########################################################################### + y: 0 + antenna: + pattern: OMNI + gain: 0 + channel_model: P619 + + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 15 + # The RAS station lat + earth_station_lat_deg: -23.17889 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md b/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md new file mode 100644 index 000000000..aef44111d --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/output/output-folder.md @@ -0,0 +1,3 @@ +# output folder +This folder holds simulation output data. +Don't push the output files to the repository. \ No newline at end of file diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py new file mode 100644 index 000000000..2d842fd32 --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from sharc.results import Results +# import plotly.graph_objects as go +from sharc.post_processor import PostProcessor + +post_processor = PostProcessor() + +# Add a legend to results in folder that match the pattern +# This could easily come from a config file +post_processor\ + .add_plot_legend_pattern( + dir_name_contains="_0km", + legend="0 Km" + ).add_plot_legend_pattern( + dir_name_contains="_45km", + legend="45 Km" + ).add_plot_legend_pattern( + dir_name_contains="_90km", + legend="90 Km" + ).add_plot_legend_pattern( + dir_name_contains="_500km", + legend="500 Km" + ) + +campaign_base_dir = str((Path(__file__) / ".." / "..").resolve()) + +many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True) +# ^: typing.List[Results] + +post_processor.add_results(many_results) + +plots = post_processor.generate_cdf_plots_from_results( + many_results +) + +post_processor.add_plots(plots) + +# # This function aggregates IMT downlink and uplink +# aggregated_results = PostProcessor.aggregate_results( +# downlink_result=post_processor.get_results_by_output_dir("MHz_60deg_dl"), +# uplink_result=post_processor.get_results_by_output_dir("MHz_60deg_ul"), +# ul_tdd_factor=(3, 4), +# n_bs_sim=7 * 19 * 3 * 3, +# n_bs_actual=int +# ) + +# Add a protection criteria line: +# protection_criteria = 160 + +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .add_vline(protection_criteria, line_dash="dash") + +# Show a single plot: +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .show() + +# Plot every plot: +for plot in plots: + plot.show() + +for result in many_results: + # This generates the mean, median, variance, etc + stats = PostProcessor.generate_statistics( + result=result + ).write_to_results_dir() + # # do whatever you want here: + # if "fspl_45deg" in stats.results_output_dir: + # get some stat and do something + +# # example on how to aggregate results and add it to plot: +# dl_res = post_processor.get_results_by_output_dir("1_cluster") +# aggregated_results = PostProcessor.aggregate_results( +# dl_samples=dl_res.system_dl_interf_power, +# ul_samples=ul_res.system_ul_interf_power, +# ul_tdd_factor=0.75, +# n_bs_sim=1 * 19 * 3 * 3, +# n_bs_actual=7 * 19 * 3 * 3 +# ) + +# relevant = post_processor\ +# .get_plot_by_results_attribute_name("system_ul_interf_power") + +# aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results) + +# relevant.add_trace( +# go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',), +# ) + +# relevant.show() diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py new file mode 100644 index 000000000..3851af953 --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns_mut_thread import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hibs_ras_2600_MHz" + +# Run the campaigns +# This function will execute the campaign with the given name. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py new file mode 100644 index 000000000..468e76764 --- /dev/null +++ b/sharc/campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hibs_ras_2600_MHz" + +# Run the campaign in single-thread mode +# This function will execute the campaign with the given name in a single-threaded manner. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv b/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv new file mode 100644 index 000000000..65ce1b706 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/comparison/contribution_20.csv @@ -0,0 +1,78 @@ +# Contrib 20 INR +x,y +-11.358820993101236,-0.000281063728990949 +-11.138772461094177,-0.00022228782340016906 +-10.918723929087118,-0.00004596010662760719 +-10.69867539708006,-0.00022228782340016906 +-10.478626865073,0.0009532302884172061 +-10.258578333065941,0.002892835172915831 +-10.038529801058882,0.005361423207732585 +-9.818481269051821,0.009416960693502618 +-9.598432737044762,0.017586811570633465 +-9.378384205037703,0.030399958989443254 +-9.188342291031606,0.04291922688029848 +-9.048311407027114,0.05725075186020567 +-8.928284935023264,0.07169003266702956 +-8.808258463019413,0.08647951991133218 +-8.720461321461041,0.10189758281264838 +-8.646794280442805,0.11634712607917863 +-8.568205519011713,0.1316561503461149 +-8.498190077009466,0.14625706489331347 +-8.42817463500722,0.16285146223846914 +-8.358159193004974,0.1800923945451245 +-8.29814595700305,0.19604025692878047 +-8.238132721001124,0.21209587513935335 +-8.178119484999199,0.22944456327292517 +-8.118106248997274,0.24700876306033026 +-8.049519693566502,0.2664355992844364 +-7.988077570993102,0.2838612558658058 +-7.948068746991819,0.29765400171113 +-7.918062128990856,0.3116191568795207 +-7.878053304989573,0.32778253091701004 +-7.8180400689876475,0.34506656555443194 +-7.748024626985401,0.36279239908221184 +-7.718018008984439,0.3791174068600761 +-7.674675116316383,0.39366444349381646 +-7.634666292315099,0.4093967942236393 +-7.594657468313816,0.4252369007803789 +-7.5546486443125325,0.4402149607217857 +-7.510472234477781,0.4571865034611494 +-7.447958446975775,0.475936017344637 +-7.405449071474411,0.49508961557906184 +-7.354604524306115,0.5120880972751547 +-7.31459570030483,0.5267428897358117 +-7.274586876303546,0.5419364613310517 +-7.234578052302263,0.5579920795416243 +-7.186472204395958,0.5751098623318035 +-7.147892266966149,0.5917196533665188 +-7.107883442964866,0.6066977133079254 +-7.067874618963582,0.6215680174224156 +-7.027865794962299,0.6358995424023228 +-6.997859176961336,0.6492397137745973 +-6.946419260388257,0.6647011355681728 +-6.909268209529921,0.6822499416660184 +-6.857828292956844,0.6987642918254646 +-6.817819468955561,0.7152078310129371 +-6.7578062329536355,0.7330952982810919 +-6.69779299695171,0.7520603238184126 +-6.637779760949785,0.7696245236058177 +-6.57776652494786,0.7883740374893052 +-6.517753288945933,0.8053994581421273 +-6.457740052944008,0.8204852739104507 +-6.387724610941762,0.8370257933421481 +-6.307706962939195,0.8552095891343235 +-6.227689314936628,0.8722619487438747 +-6.147671666934061,0.8881020553006143 +-6.092659533932297,0.9005478533094811 +-5.997638576929248,0.9182467478805318 +-5.88761431092572,0.9344909387882085 +-5.757585632921547,0.9490841564906275 +-5.597550336916413,0.9638005237095225 +-5.397506216909996,0.9776912293974982 +-5.177457684902937,0.9869778224808556 +-4.9574091528958775,0.9917386708337161 +-4.737360620888818,0.9944423624908962 +-4.517312088881759,0.9956766565083044 +-4.2972635568747,0.9966170709977584 +-4.077215024867641,0.9972636059592579 +-3.937184140863149,0.9976358533613334 diff --git a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml new file mode 100644 index 000000000..06747adbf --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_dl.yaml @@ -0,0 +1,376 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 2400 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: DOWNLINK + ########################################################################### + # The chosen system for sharing study + # EESS_SS, FSS_SS, FSS_ES, FS, RAS + system: EESS_SS + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: TRUE + enable_adjacent_channel: FALSE + ########################################################################### + # Seed for random number generator + seed: 31 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: FALSE + ########################################################################### + # output destination folder - this is relative SHARC/sharc directory + output_dir: campaigns/imt_hotspot_eess_active/output/ + ########################################################################### + # output folder prefix + output_dir_prefix: output_imt_hotspot_eess_active_beam_small_dl +imt: + ########################################################################### + # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + topology: + type: HOTSPOT + hotspot: + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 1 + ########################################################################### + # Maximum 2D distance between hotspot and UE [m] + # This is the hotspot radius + max_dist_hotspot_ue: 100 + ########################################################################### + # Minimum 2D distance between macro cell base station and hotspot [m] + min_dist_bs_hotspot: 0 + ########################################################################### + # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies + wrap_around: FALSE + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Inter-site distance in macrocell network topology [m] + #intersite_distance = 1740 + #intersite_distance = 665 + intersite_distance: 660 + ########################################################################### + # Minimum 2D separation distance from BS to UE [m] + minimum_separation_distance_bs_ue: 0 + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 10250 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # IMT resource block bandwidth [MHz] + rb_bandwidth: 0.180 + ########################################################################### + # IMT spectrum emission mask. Options are: + # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 + # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in + # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) + spectral_mask: IMT-2020 + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.1 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: BEAMFORMING + bs: + ########################################################################### + # The load probability (or activity factor) models the statistical + # variation of the network load by defining the number of fully loaded + # base stations that are simultaneously transmitting + load_probability: .2 + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 16 + ########################################################################### + # Base station height [m] + height: 6 + ########################################################################### + # Base station noise figure [dB] + noise_figure: 10 + ########################################################################### + # User equipment noise temperature [K] + noise_temperature: 290 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 0 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for BS + normalization: FALSE + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: M2101 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 10 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5.5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 120 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 120 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 30 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + uplink: + ########################################################################### + # Uplink attenuation factor used in link-to-system mapping + attenuation_factor: 1.0 + ########################################################################### + # Uplink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Uplink maximum SINR of the code set [dB] + sinr_max: 22 + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: RAYLEIGH + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 23 + ########################################################################### + # UE power dynamic range [dB] + # The minimum transmit power of a UE is (p_cmax - dynamic_range) + power_dynamic_range: 63 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # User equipment noise figure [dB] + noise_figure: 10 + ########################################################################### + # User equipment feed loss [dB] + ohmic_loss: 0 + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for UE + normalization: FALSE + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: FIXED + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: -4 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 360 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 180 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 1 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 1 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 25 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + downlink: + ########################################################################### + # Downlink attenuation factor used in link-to-system mapping + attenuation_factor: 1.0 + ########################################################################### + # Downlink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Downlink maximum SINR of the code set [dB] + sinr_max: 30 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "CI" (close-in FS reference distance) + # "UMa" (Urban Macro - 3GPP) + # "UMi" (Urban Micro - 3GPP) + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "ABG" (Alpha-Beta-Gamma) + channel_model: UMi + ########################################################################### + # Adjustment factor for LoS probability in UMi path loss model. + # Original value: 18 (3GPP) + los_adjustment_factor: 29 + ########################################################################### + # If shadowing should be applied or not + shadowing: TRUE + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 500 +eess_ss: + ########################################################################### + # sensor center frequency [MHz] + frequency: 9800 + ########################################################################### + # sensor bandwidth [MHz] + bandwidth: 1200 + ###########Creates a statistical distribution of nadir angle############### + ##############following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: OFF + distribution_type: UNIFORM + nadir_angle_distribution: 18.6,49.4 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle: 18 + ########################################################################### + # sensor altitude [m] + altitude: 514000.0 + ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "ITU-R RS.2043" + # "OMNI" + antenna_pattern: ITU-R RS.2043 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 1.0 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 2.2 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 47 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: P619 + # Parameters for the P.619 propagation model + # space_station_alt_m - altitude of space station + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 1172 + earth_station_lat_deg: -15.8 + earth_station_long_diff_deg: 0.0 + season: SUMMER diff --git a/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml new file mode 100644 index 000000000..ccb1d74f2 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/input/parameters_imt_hotspot_eess_active_beam_small_ul.yaml @@ -0,0 +1,371 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 2400 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: UPLINK + ########################################################################### + # The chosen system for sharing study + # EESS_SS, FSS_SS, FSS_ES, FS, RAS + system: EESS_SS + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: TRUE + enable_adjacent_channel: FALSE + ########################################################################### + # Seed for random number generator + seed: 101 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: FALSE + ########################################################################### + # output destination folder - this is relative SHARC/sharc directory + output_dir: campaigns/imt_hotspot_eess_active/output/ + ########################################################################### + # output folder prefix + output_dir_prefix: output_imt_hotspot_eess_active_beam_small_ul +imt: + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: SINGLE_ELEMENT + ########################################################################### + # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + topology: + type: HOTSPOT + hotspot: + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 1 + ########################################################################### + # Maximum 2D distance between hotspot and UE [m] + # This is the hotspot radius + max_dist_hotspot_ue: 100 + ########################################################################### + # Minimum 2D distance between macro cell base station and hotspot [m] + min_dist_bs_hotspot: 0 + ########################################################################### + # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies + wrap_around: FALSE + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Inter-site distance in macrocell network topology [m] + intersite_distance: 660 + ########################################################################### + # Minimum 2D separation distance from BS to UE [m] + minimum_separation_distance_bs_ue: 0 + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 10250 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # IMT resource block bandwidth [MHz] + rb_bandwidth: 0.180 + ########################################################################### + # IMT spectrum emission mask. Options are: + # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 + # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in + # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) + spectral_mask: IMT-2020 + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.1 + bs: + ########################################################################### + # The load probability (or activity factor) models the statistical + # variation of the network load by defining the number of fully loaded + # base stations that are simultaneously transmitting + load_probability: .2 + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 16 + ########################################################################### + # Base station height [m] + height: 6 + ########################################################################### + # Base station noise figure [dB] + noise_figure: 10 + ########################################################################### + # User equipment noise temperature [K] + noise_temperature: 290 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 3 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for BS + normalization: FALSE + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: M2101 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 10 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5.5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 90 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 90 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 30 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + uplink: + ########################################################################### + # Uplink attenuation factor used in link-to-system mapping + attenuation_factor: 1.0 + ########################################################################### + # Uplink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Uplink maximum SINR of the code set [dB] + sinr_max: 22 + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: RAYLEIGH + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 23 + ########################################################################### + # UE power dynamic range [dB] + # The minimum transmit power of a UE is (p_cmax - dynamic_range) + power_dynamic_range: 63 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # User equipment noise figure [dB] + noise_figure: 10 + ########################################################################### + # User equipment feed loss [dB] + ohmic_loss: 0 + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for UE + normalization: FALSE + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: FIXED + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: -4 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 360 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 180 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 1 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 1 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 25 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + downlink: + ########################################################################### + # Downlink attenuation factor used in link-to-system mapping + attenuation_factor: 1.0 + ########################################################################### + # Downlink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Downlink maximum SINR of the code set [dB] + sinr_max: 30 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "CI" (close-in FS reference distance) + # "UMa" (Urban Macro - 3GPP) + # "UMi" (Urban Micro - 3GPP) + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "ABG" (Alpha-Beta-Gamma) + channel_model: UMi + ########################################################################### + # Adjustment factor for LoS probability in UMi path loss model. + # Original value: 18 (3GPP) + los_adjustment_factor: 29 + ########################################################################### + # If shadowing should be applied or not + shadowing: TRUE + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 500 +eess_ss: + ########################################################################### + # sensor center frequency [MHz] + frequency: 9800 + ########################################################################### + # sensor bandwidth [MHz] + bandwidth: 1200 + ###########Creates a statistical distribution of nadir angle############### + ##############following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: OFF + distribution_type: UNIFORM + nadir_angle_distribution: 18.6,49.4 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle: 18 + ########################################################################### + # sensor altitude [m] + altitude: 514000.0 + ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "ITU-R RS.2043" + # "OMNI" + antenna_pattern: ITU-R RS.2043 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 0.7 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 2.2 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 47 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: P619 + # Parameters for the P.619 propagation model + # space_station_alt_m - altitude of space station + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 1172 + earth_station_lat_deg: -15.8 + earth_station_long_diff_deg: 0.0 + season: SUMMER diff --git a/sharc/campaigns/imt_hotspot_eess_active/readme.md b/sharc/campaigns/imt_hotspot_eess_active/readme.md new file mode 100644 index 000000000..66a2e8738 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/readme.md @@ -0,0 +1,15 @@ +# IMT Hotspot EESS Active Campaign + +This campaign's objective is to try and replicate Luciano's contribution 20. + +Using the script `plot_results.py` automatically aggregates results and compares it to Luciano's INR results in +contrib 20 + +The document that should be referenced is +"HIBS and IMT-2020 Coexistence with other Space/Terrestrial +Communications and Radar Systems", a Doctoral Thesis by Luciano Camilo Alexandre publicated on Dec 2022 + +The document can be downloaded found [here](https://www2.inatel.br/biblioteca/teses-de-doutorado) +and +[downloaded here](https://biblioteca.inatel.br/cict/acervo%20publico/sumarios/Teses%20de%20Doutorado%20do%20Inatel/Luciano%20Camilo%20Alexandre.pdf) + diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py new file mode 100644 index 000000000..861738050 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/plot_results.py @@ -0,0 +1,77 @@ +import os +from pathlib import Path +from sharc.results import Results +import plotly.graph_objects as go +from sharc.post_processor import PostProcessor +import pandas + +post_processor = PostProcessor() + +# Add a legend to results in folder that match the pattern +# This could easily come from a config file +post_processor\ + .add_plot_legend_pattern( + dir_name_contains="beam_small_dl", + legend="Small Beam DL" + ).add_plot_legend_pattern( + dir_name_contains="beam_small_ul", + legend="Small Beam UL" + ) + +campaign_base_dir = str((Path(__file__) / ".." / "..").resolve()) + +many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True) + +post_processor.add_results(many_results) + +plots = post_processor.generate_cdf_plots_from_results( + many_results +) + +post_processor.add_plots(plots) + +# This function aggregates IMT downlink and uplink +aggregated_results = PostProcessor.aggregate_results( + dl_samples=post_processor.get_results_by_output_dir("_dl").system_inr, + ul_samples=post_processor.get_results_by_output_dir("_ul").system_inr, + ul_tdd_factor=0.25, + # SF is not exactly 1, but approx + n_bs_sim=1, + n_bs_actual=1 +) + +# Add a protection criteria line: +# protection_criteria = int + +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .add_vline(protection_criteria, line_dash="dash") + +# Show a single plot: +relevant = post_processor\ + .get_plot_by_results_attribute_name("system_inr") + +aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results) + +relevant.add_trace( + go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',), +) + +compare_to = pandas.read_csv( + os.path.join(campaign_base_dir, "comparison", "contribution_20.csv"), + skiprows=1 +) + +comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1]) + +relevant.add_trace( + go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Contrib 20 INR',), +) + +relevant.show() + +for result in many_results: + # This generates the mean, median, variance, etc + stats = PostProcessor.generate_statistics( + result=result + ).write_to_results_dir() diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py new file mode 100644 index 000000000..dd0ad2c54 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_multi_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns_mut_thread import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hotspot_eess_active" + +# Run the campaigns +# This function will execute the campaign with the given name. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py new file mode 100644 index 000000000..9dce5cd36 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_active/scripts/start_simulations_single_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hotspot_eess_active" + +# Run the campaign in single-thread mode +# This function will execute the campaign with the given name in a single-threaded manner. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv new file mode 100644 index 000000000..cd95f43fe --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv @@ -0,0 +1,37 @@ +# Fig 15 +x, y +-159.05775075987842, 0.9633492947617223 +-158.48024316109422, 0.9543984596408218 +-157.75075987841944, 0.9633492947617223 +-156.99088145896656, 0.9194190830166598 +-156.50455927051672, 0.8453314502151946 +-156.20060790273556, 0.7699924848865801 +-155.95744680851064, 0.6570033251510908 +-155.80547112462006, 0.5658518012594294 +-155.62310030395136, 0.49191706844917393 +-155.56231003039514, 0.43165336925959 +-155.44072948328267, 0.3787724458933014 +-155.34954407294833, 0.3201882609885405 +-155.22796352583586, 0.2583224566423367 +-155.10638297872342, 0.20647375917915298 +-154.98480243161094, 0.1531563949210873 +-154.89361702127658, 0.13190773718012405 +-154.83282674772036, 0.11574800476981631 +-154.74164133738603, 0.0850608522508284 +-154.68085106382978, 0.06611047389133969 +-154.62006079027356, 0.05333680870333699 +-154.52887537993922, 0.041842885079015825 +-154.43768996960486, 0.032520870510413455 +-154.40729483282675, 0.024808162306509743 +-154.28571428571428, 0.018061622323899008 +-154.25531914893617, 0.014708472321254355 +-154.19452887537994, 0.012318009665808635 +-154.19452887537994, 0.010609029955405396 +-154.16413373860183, 0.009137151183368584 +-154.10334346504558, 0.007869478368773596 +-154.07294832826747, 0.005267707132875599 +-154.01215805471125, 0.004370594758137502 +-153.9209726443769, 0.0028984256351332048 +-153.82978723404256, 0.001958357122672717 +-153.76899696048633, 0.0014526539259467812 +-153.7386018237082, 0.0010284002230430917 \ No newline at end of file diff --git a/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv new file mode 100644 index 000000000..729134dc3 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/comparison/Fig. 8 EESS (Passive) Sensor.csv @@ -0,0 +1,27 @@ +# Results collected from luciano's contribution 21, Figure 8. +x,y +-160.4357066950053,0.9613501484827673 +-159.798087141339,0.9613501484827673 +-158.84165781083954,0.9613501484827673 +-157.778958554729,0.9519233878378311 +-156.58342189160467,0.9333462695554301 +-155.3347502656748,0.8797619862955459 +-153.9798087141339,0.7663916856958233 +-153.182784272051,0.6231307348505225 +-152.4123273113709,0.45910450561838223 +-151.96068012752391,0.344987324360697 +-151.5356004250797,0.25417657344926176 +-151.29649309245482,0.20064350554165133 +-150.8182784272051,0.12877841672100215 +-150.57917109458023,0.1026626022035896 +-150.26036131774708,0.06334492703460955 +-149.9415515409139,0.044426921419985395 +-149.70244420828905,0.02768378019158192 +-149.43676939426143,0.01794415314903341 +-149.0913921360255,0.010333925713156772 +-148.93198724760893,0.00631371983651216 +-148.63974495217855,0.003530115158319504 +-148.50690754516472,0.0026008856310466007 +-148.34750265674813,0.0017194043905352397 +-148.1615302869288,0.0011592951222250565 +-148.1615302869288,0.0011592951222250565 diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml new file mode 100644 index 000000000..9440c604d --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL.yaml @@ -0,0 +1,270 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 10000 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: DOWNLINK + ########################################################################### + # The chosen system for sharing study + # EESS_SS, FSS_SS, FSS_ES, FS, RAS + system: EESS_SS + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: FALSE + enable_adjacent_channel: TRUE + ########################################################################### + # Seed for random number generator + seed: 39 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: FALSE + ########################################################################### + # output destination folder - this is relative SHARC/sharc directory + output_dir: campaigns/imt_hotspot_eess_passive/output/ + ########################################################################### + # output folder prefix + output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_DL +imt: + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 10250 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.1 + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: BEAMFORMING + bs: + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 16 + ########################################################################### + # Base station height [m] + height: 6 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 0 + antenna: + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 10 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 120.0 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 90.0 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: M2101 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5.5 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5195 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5195 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 30 + enable_beamsteering_vertical_limit: "ON" + beamsteering_vertical_limit: 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + topology: + ########################################################################### + # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + type: HOTSPOT + hotspot: + ########################################################################### + # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies + wrap_around: FALSE + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 1 + ########################################################################### + # Inter-site distance in macrocell network topology [m] + # 1,14884 / 1,000,000 m2 + intersite_distance: 4647 + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 3 + # ########################################################################### + # # Maximum 2D distance between hotspot and UE [m] + # # This is the hotspot radius + # max_dist_hotspot_ue: 100 + # ########################################################################### + # # Minimum 2D distance between macro cell base station and hotspot [m] + # min_dist_bs_hotspot: 0 + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: RAYLEIGH + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 23 + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + antenna: + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 1 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 1 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 360 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 180 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: FIXED + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: -4 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 +eess_ss: + # sensor C7 + ########################################################################### + # satellite altitude [m] + altitude: 407000.0 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle: 48.6 + ########################################################################### + # satellite center frequency [MHz] + frequency: 10650 + ########################################################################### + # satellite bandwidth [MHz] + bandwidth: 100 + ###########Creates a statistical distribution of nadir angle############### + ##############following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: OFF + # # distribution_type = UNIFORM + # # nadir_angle_distribution = 18.6,49.4 + # ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "ITU-R RS.2043" + # "OMNI" + antenna_pattern: ITU-R RS.1813 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 0.606 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 1.1 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 39.6 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: P619 + # earth station at Brasรญlia - Brasil + earth_station_alt_m: 6 + earth_station_lat_deg: -15.8 + earth_station_long_diff_deg: 0.0 + season: SUMMER diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml new file mode 100644 index 000000000..82c6210ce --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_DL_alternative.yaml @@ -0,0 +1,278 @@ +# ALTERNATIVE: +# set the following attributes to use default: +# bs_element_phi_3db: 90.0 +# bs_element_theta_3db: 90.0 +# element_spacing: 5 +# REASONING: +# Figure 6 presents element pattern found by using the above config + +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 10000 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: DOWNLINK + ########################################################################### + # The chosen system for sharing study + # EESS_SS, FSS_SS, FSS_ES, FS, RAS + system: EESS_SS + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: FALSE + enable_adjacent_channel: TRUE + ########################################################################### + # Seed for random number generator + seed: 39 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: FALSE + ########################################################################### + # output destination folder - this is relative SHARC/sharc directory + output_dir: campaigns/imt_hotspot_eess_passive/output/ + ########################################################################### + # output folder prefix + output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_DL_alternative +imt: + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 10250 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.1 + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: BEAMFORMING + bs: + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 16 + ########################################################################### + # Base station height [m] + height: 6 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 0 + antenna: + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 10 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 90.0 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 90.0 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: M2101 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5.5 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + topology: + ########################################################################### + # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + type: HOTSPOT + hotspot: + ########################################################################### + # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies + wrap_around: FALSE + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 1 + ########################################################################### + # Inter-site distance in macrocell network topology [m] + # 1,14884 / 1,000,000 m2 + intersite_distance: 4647 + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 3 + # ########################################################################### + # # Maximum 2D distance between hotspot and UE [m] + # # This is the hotspot radius + # max_dist_hotspot_ue: 100 + # ########################################################################### + # # Minimum 2D distance between macro cell base station and hotspot [m] + # min_dist_bs_hotspot: 0 + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: RAYLEIGH + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 23 + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + antenna: + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 1 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 1 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 360 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 180 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: FIXED + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: -4 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + + +eess_ss: + # sensor C7 + ########################################################################### + # satellite altitude [m] + altitude: 407000.0 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle: 48.6 + ########################################################################### + # satellite center frequency [MHz] + frequency: 10650 + ########################################################################### + # satellite bandwidth [MHz] + bandwidth: 100 + ###########Creates a statistical distribution of nadir angle############### + ##############following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: OFF + # # distribution_type = UNIFORM + # # nadir_angle_distribution = 18.6,49.4 + # ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "ITU-R RS.2043" + # "OMNI" + antenna_pattern: ITU-R RS.1813 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 0.606 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 1.1 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 39.6 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: P619 + # earth station at Brasรญlia - Brasil + earth_station_alt_m: 6 + earth_station_lat_deg: -15.8 + earth_station_long_diff_deg: 0.0 + season: SUMMER diff --git a/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml new file mode 100644 index 000000000..ac91bca59 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/input/parameters_imt_macro_eess_passive_1_cluster_UL.yaml @@ -0,0 +1,271 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 10000 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: UPLINK + ########################################################################### + # The chosen system for sharing study + # EESS_SS, FSS_SS, FSS_ES, FS, RAS + system: EESS_SS + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: FALSE + enable_adjacent_channel: TRUE + ########################################################################### + # Seed for random number generator + seed: 57 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: FALSE + ########################################################################### + # output destination folder - this is relative SHARC/sharc directory + output_dir: campaigns/imt_hotspot_eess_passive/output/ + ########################################################################### + # output folder prefix + output_dir_prefix: output_imt_hotspot_eess_passive_1_cluster_UL +imt: + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 10250 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.1 + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: SINGLE_ELEMENT + bs: + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 16 + ########################################################################### + # Base station height [m] + height: 6 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 0 + antenna: + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 10 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 120.0 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 90.0 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: M2101 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5.5 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5195 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5195 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + ########################################################################### + # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + topology: + type: HOTSPOT + hotspot: + ########################################################################### + # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies + wrap_around: FALSE + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 1 + ########################################################################### + # Inter-site distance in macrocell network topology [m] + intersite_distance: 4647 + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 3 + # ########################################################################### + # # Maximum 2D distance between hotspot and UE [m] + # # This is the hotspot radius + # max_dist_hotspot_ue: 100 + # ########################################################################### + # # Minimum 2D distance between macro cell base station and hotspot [m] + # min_dist_bs_hotspot: 0 + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: RAYLEIGH + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 23 + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + antenna: + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 1 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 1 + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: 360 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 180 + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: FIXED + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: -4 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + # element_sla_v = 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 +eess_ss: + # sensor C7 + ########################################################################### + # satellite altitude [m] + altitude: 407000.0 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle: 48.6 + ########################################################################### + # satellite center frequency [MHz] + frequency: 10650 + ########################################################################### + # satellite bandwidth [MHz] + bandwidth: 100 + ###########Creates a statistical distribution of nadir angle############### + ##############following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: OFF + # # distribution_type = UNIFORM + # # nadir_angle_distribution = 18.6,49.4 + # ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "ITU-R RS.2043" + # "OMNI" + antenna_pattern: ITU-R RS.1813 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 0.606 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 1.1 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 39.6 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: P619 + # earth station at Brasรญlia - Brasil + earth_station_alt_m: 1.5 + earth_station_lat_deg: -15.8 + earth_station_long_diff_deg: 0.0 + season: SUMMER diff --git a/sharc/campaigns/imt_hotspot_eess_passive/readme.md b/sharc/campaigns/imt_hotspot_eess_passive/readme.md new file mode 100644 index 000000000..8867c10f8 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/readme.md @@ -0,0 +1,16 @@ +# IMT Hotspot EESS Passive Campaign + +This campaign's objective is to try and replicate Luciano's contribution 21. + +You can compare SHARC results against Luciano's results with `plot_results.py`. +It automatically aggregates UL and DL considering TDD and SF (segment factor). + +We did not take the time to process the Uplink analysis to try and reach the same results. + +The document that should be referenced is +"HIBS and IMT-2020 Coexistence with other Space/Terrestrial +Communications and Radar Systems", a Doctoral Thesis by Luciano Camilo Alexandre publicated on Dec 2022 + +The document can be downloaded found [here](https://www2.inatel.br/biblioteca/teses-de-doutorado) +and +[downloaded here](https://biblioteca.inatel.br/cict/acervo%20publico/sumarios/Teses%20de%20Doutorado%20do%20Inatel/Luciano%20Camilo%20Alexandre.pdf) diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py new file mode 100644 index 000000000..4c1f3d529 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/plot_results.py @@ -0,0 +1,125 @@ +import os +from pathlib import Path +from sharc.results import Results +import plotly.graph_objects as go +from sharc.post_processor import PostProcessor +import pandas + +post_processor = PostProcessor() + +# Add a legend to results in folder that match the pattern +# This could easily come from a config file +post_processor\ + .add_plot_legend_pattern( + dir_name_contains="1_cluster_DL_alternative", + legend="Alternative Downlink" + ).add_plot_legend_pattern( + dir_name_contains="1_cluster_DL", + legend="Downlink" + ).add_plot_legend_pattern( + dir_name_contains="1_cluster_UL", + legend="Uplink" + ) + +campaign_base_dir = str((Path(__file__) / ".." / "..").resolve()) + +many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True) + +post_processor.add_results(many_results) + +plots = post_processor.generate_cdf_plots_from_results( + many_results +) + +post_processor.add_plots(plots) + +uplink_interf_samples = post_processor.get_results_by_output_dir("_UL").system_ul_interf_power + +# This function aggregates IMT downlink and uplink +aggregated_results = PostProcessor.aggregate_results( + dl_samples=post_processor.get_results_by_output_dir("_DL_alternative").system_dl_interf_power, + ul_samples=uplink_interf_samples, + ul_tdd_factor=0.25, + # SF is not exactly 3, but approx + n_bs_sim=1, + n_bs_actual=3 +) + +# Add a protection criteria line: +# protection_criteria = int + +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .add_vline(protection_criteria, line_dash="dash") + +# Show a single plot: +relevant = post_processor\ + .get_plot_by_results_attribute_name("system_dl_interf_power") + +# Title of CDF updated because ul interf power will be included +relevant.update_layout( + title='CDF Plot for Interference', +) + +aggr_x, aggr_y = PostProcessor.cdf_from(uplink_interf_samples) + +relevant.add_trace( + go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Uplink',), +) + +aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results) + +relevant.add_trace( + go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',), +) + +# TODO: put some more stuff into PostProcessor if ends up being really used +compare_to = pandas.read_csv( + os.path.join(campaign_base_dir, "comparison", "Fig. 8 EESS (Passive) Sensor.csv"), + skiprows=1 +) + +comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1]) +# inverting given chart from P of I > x to P of I < x +comp_y = 1 - comp_y +# converting dB to dBm +comp_x = comp_x + 30 + +relevant.add_trace( + go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Fig. 8 EESS (Passive) Sensor',), +) + +compare_to = pandas.read_csv( + os.path.join(campaign_base_dir, "comparison", "Fig. 15 (IMT Uplink) EESS (Passive) Sensor.csv"), + skiprows=1 +) + +comp_x, comp_y = (compare_to.iloc[:, 0], compare_to.iloc[:, 1]) +# inverting given chart from P of I > x to P of I < x +comp_y = 1 - comp_y +# converting dB to dBm +comp_x = comp_x + 30 + +relevant.add_trace( + go.Scatter(x=comp_x, y=comp_y, mode='lines', name='Fig. 15 (IMT Uplink) EESS (Passive) Sensor',), +) + +relevant.show() + +post_processor\ + .get_plot_by_results_attribute_name("system_ul_interf_power").show() + +# for result in many_results: +# # This generates the mean, median, variance, etc +# stats = PostProcessor.generate_statistics( +# result=result +# ).write_to_results_dir() + +aggregated_res_statistics = PostProcessor.generate_sample_statistics( + "Aggregate Results Statistics", + aggregated_results +) + +print("\n###########################") + +print(aggregated_res_statistics) diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py new file mode 100644 index 000000000..0a480dee6 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_multi_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns_mut_thread import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hotspot_eess_passive" + +# Run the campaigns +# This function will execute the campaign with the given name. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py new file mode 100644 index 000000000..d77396906 --- /dev/null +++ b/sharc/campaigns/imt_hotspot_eess_passive/scripts/start_simulations_single_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_hotspot_eess_passive" + +# Run the campaign in single-thread mode +# This function will execute the campaign with the given name in a single-threaded manner. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml new file mode 100644 index 000000000..8e1482a79 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_30deg.yaml @@ -0,0 +1,380 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: P619 + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 1000 + # The RAS station lat + earth_station_lat_deg: -15.7801 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml new file mode 100644 index 000000000..4f0f9dd52 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_45deg.yaml @@ -0,0 +1,384 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: P619 + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 1000 + # The RAS station lat + earth_station_lat_deg: -15.7801 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml new file mode 100644 index 000000000..a31b55d42 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_60deg.yaml @@ -0,0 +1,384 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: P619 + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 1000 + # The RAS station lat + earth_station_lat_deg: -15.7801 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml new file mode 100644 index 000000000..1aa7ae245 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_90deg.yaml @@ -0,0 +1,384 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: P619 + param_p619: + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigth above sea level + earth_station_alt_m: 1000 + # The RAS station lat + earth_station_lat_deg: -15.7801 + # This parameter is ignored as it will be calculated from x,y positions in run time + earth_station_long_diff_deg: 0.0 + + season: SUMMER diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml new file mode 100644 index 000000000..e355d2f0a --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_30deg.yaml @@ -0,0 +1,370 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: FSPL diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml new file mode 100644 index 000000000..91ab3f366 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_45deg.yaml @@ -0,0 +1,370 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: FSPL diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml new file mode 100644 index 000000000..744d377b0 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_60deg.yaml @@ -0,0 +1,370 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + frequency: 2695 + ########################################################################### + bandwidth: 10 + ########################################################################### + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + #### + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + ########################################################################### + antenna: + pattern: OMNI + gain: 0 + + channel_model: FSPL diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml new file mode 100644 index 000000000..8cbd2b0a0 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_fspl_90deg.yaml @@ -0,0 +1,370 @@ +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 + ########################################################################### + # 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 +ras: + ########################################################################### + # frequency [MHz] + frequency: 2695 + ########################################################################### + # bandwidth [MHz] + bandwidth: 10 + ########################################################################### + # Station noise temperature [K] + noise_temperature: 90 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: 0 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 15 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + type: FIXED + fixed: -90 + ########################################################################### + # Elevation angle [degrees] + elevation: + type: FIXED + fixed: 45 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + type: FIXED + fixed: + ########################################################################### + x: 0 + ########################################################################### + y: 0 + antenna: + pattern: OMNI + gain: 0 + channel_model: FSPL diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py new file mode 100644 index 000000000..009aa54fb --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/plot_results.py @@ -0,0 +1,101 @@ +import os +from pathlib import Path +from sharc.results import Results +# import plotly.graph_objects as go +from sharc.post_processor import PostProcessor + +post_processor = PostProcessor() + +# Add a legend to results in folder that match the pattern +# This could easily come from a config file +post_processor\ + .add_plot_legend_pattern( + dir_name_contains="MHz_30deg", + legend="30 deg (P619)" + ).add_plot_legend_pattern( + dir_name_contains="MHz_45deg", + legend="45 deg (P619)" + ).add_plot_legend_pattern( + dir_name_contains="MHz_60deg", + legend="60 deg (P619)" + ).add_plot_legend_pattern( + dir_name_contains="MHz_90deg", + legend="90 deg (P619)" + ).add_plot_legend_pattern( + dir_name_contains="fspl_30deg", + legend="30 deg (FSPL)" + ).add_plot_legend_pattern( + dir_name_contains="fspl_45deg", + legend="45 deg (FSPL)" + ).add_plot_legend_pattern( + dir_name_contains="fspl_60deg", + legend="60 deg (FSPL)" + ).add_plot_legend_pattern( + dir_name_contains="fspl_90deg", + legend="90 deg (FSPL)" + ) + +campaign_base_dir = str((Path(__file__) / ".." / "..").resolve()) + +many_results = Results.load_many_from_dir(os.path.join(campaign_base_dir, "output"), only_latest=True) +# ^: typing.List[Results] + +post_processor.add_results(many_results) + +plots = post_processor.generate_cdf_plots_from_results( + many_results +) + +post_processor.add_plots(plots) + +# # This function aggregates IMT downlink and uplink +# aggregated_results = PostProcessor.aggregate_results( +# downlink_result=post_processor.get_results_by_output_dir("MHz_60deg_dl"), +# uplink_result=post_processor.get_results_by_output_dir("MHz_60deg_ul"), +# ul_tdd_factor=(3, 4), +# n_bs_sim=7 * 19 * 3 * 3, +# n_bs_actual=int +# ) + +# Add a protection criteria line: +# protection_criteria = 160 + +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .add_vline(protection_criteria, line_dash="dash") + +# Show a single plot: +# post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power")\ +# .show() + +# Plot every plot: +for plot in plots: + plot.show() + +for result in many_results: + # This generates the mean, median, variance, etc + stats = PostProcessor.generate_statistics( + result=result + ).write_to_results_dir() + +# # example on how to aggregate results and add it to plot: +# dl_res = post_processor.get_results_by_output_dir("1_cluster") +# aggregated_results = PostProcessor.aggregate_results( +# dl_samples=dl_res.system_dl_interf_power, +# ul_samples=ul_res.system_ul_interf_power, +# ul_tdd_factor=0.25, +# n_bs_sim=1 * 19 * 3 * 3, +# n_bs_actual=7 * 19 * 3 * 3 +# ) + +# relevant = post_processor\ +# .get_plot_by_results_attribute_name("system_dl_interf_power") + +# aggr_x, aggr_y = PostProcessor.cdf_from(aggregated_results) + +# relevant.add_trace( +# go.Scatter(x=aggr_x, y=aggr_y, mode='lines', name='Aggregate interference',), +# ) + +# relevant.show() diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py new file mode 100644 index 000000000..2300f1819 --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_multi_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns_mut_thread import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_mss_ras_2600_MHz" + +# Run the campaigns +# This function will execute the campaign with the given name. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py new file mode 100644 index 000000000..4a6551c3b --- /dev/null +++ b/sharc/campaigns/imt_mss_ras_2600_MHz/scripts/start_simulations_single_thread.py @@ -0,0 +1,10 @@ +from sharc.run_multiple_campaigns import run_campaign + +# Set the campaign name +# The name of the campaign to run. This should match the name of the campaign directory. +name_campaign = "imt_mss_ras_2600_MHz" + +# Run the campaign in single-thread mode +# This function will execute the campaign with the given name in a single-threaded manner. +# It will look for the campaign directory under the specified name and start the necessary processes. +run_campaign(name_campaign) diff --git a/sharc/controller.py b/sharc/controller.py index 064daf9cf..e1d37d64e 100644 --- a/sharc/controller.py +++ b/sharc/controller.py @@ -5,39 +5,40 @@ @author: edgar """ -from sharc.support.enumerations import Action +from sharc.support.enumerations import Action from sharc.model import Model from thread_simulation import ThreadSimulation + class Controller: """ - This is the Controller class of the simplified MVC model that is - implemented in this application. This class should define application + This is the Controller class of the simplified MVC model that is + implemented in this application. This class should define application behavior, map user actions to model updates and select view for response. """ - + def __init__(self): pass - + def set_model(self, model: Model): self.model = model def get_model(self): return self.model - + def action(self, *args, **kwargs): """ - Receives the user action that is captured by view and maps it to the + Receives the user action that is captured by view and maps it to the appropriate action. Currently, the only supported actions are the ones - that start and stop simulation. - + that start and stop simulation. + Parameters ---------- Action: this non-keyworded argument indicates the action to be taken """ action = kwargs["action"] - + if action is Action.START_SIMULATION: self.model.set_param_file(kwargs["param_file"]) self.simulation_thread = ThreadSimulation(self.model) @@ -46,8 +47,7 @@ def action(self, *args, **kwargs): self.model.set_param_file(kwargs["param_file"]) self.simulation_thread = ThreadSimulation(self.model) # call run method directly, without starting a new thread - self.simulation_thread.run() + self.simulation_thread.run() if action is Action.STOP_SIMULATION: if self.simulation_thread.is_alive(): self.simulation_thread.stop() - \ No newline at end of file diff --git a/sharc/gui/__init__.py b/sharc/gui/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/gui/__init__.py +++ b/sharc/gui/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/gui/thread_safe_scrolled_text.py b/sharc/gui/thread_safe_scrolled_text.py index 4ca95521c..305ac431e 100644 --- a/sharc/gui/thread_safe_scrolled_text.py +++ b/sharc/gui/thread_safe_scrolled_text.py @@ -8,6 +8,7 @@ import tkinter.scrolledtext import queue + class ThreadSafeScrolledText(tkinter.scrolledtext.ScrolledText): """ This is a subclass of ScrolledText that uses the Queue object and make the @@ -15,7 +16,7 @@ class ThreadSafeScrolledText(tkinter.scrolledtext.ScrolledText): interface code should be run in the main thread; other threads will write to the Queue object. """ - + def __init__(self, master, **options): """ Creates the Queue object and starts the update method. @@ -23,13 +24,13 @@ def __init__(self, master, **options): tkinter.Text.__init__(self, master, **options) self.__queue = queue.Queue() self.__update() - + def write(self, line: str): """ Puts in the queue the line to be displayed. """ self.__queue.put(line) - + def __update(self): """ Periodically checks if queue contains something to be printed. If queue @@ -38,13 +39,12 @@ def __update(self): try: while 1: line = self.__queue.get_nowait() - self.config(state = tkinter.NORMAL) + self.config(state=tkinter.NORMAL) self.insert(tkinter.END, str(line)) self.see(tkinter.END) - self.config(state = tkinter.DISABLED) + self.config(state=tkinter.DISABLED) self.update_idletasks() except queue.Empty: pass # interval is 100 ms self.after(100, self.__update) - \ No newline at end of file diff --git a/sharc/gui/view.py b/sharc/gui/view.py index 5de707a21..99bb6a733 100644 --- a/sharc/gui/view.py +++ b/sharc/gui/view.py @@ -20,6 +20,7 @@ import tkinter.filedialog import tkinter.scrolledtext + class View(tkinter.Tk, Observer): """ Implements the graphical user interface. This is a subclass of Observer and @@ -42,44 +43,56 @@ def initialize(self): self.__app_icon = tkinter.PhotoImage(file="img/app_icon.gif") self.tk.call('wm', 'iconphoto', self._w, self.__app_icon) - self.__frame = tkinter.Frame(self, bg = '#CCCCCC') + self.__frame = tkinter.Frame(self, bg='#CCCCCC') self.__frame.pack(fill='both', expand='yes') - self.__scrolledtext = ThreadSafeScrolledText(self.__frame, - wrap=tkinter.WORD, width=80, height=25, bd=5) + self.__scrolledtext = ThreadSafeScrolledText( + self.__frame, + wrap=tkinter.WORD, width=80, height=25, bd=5, + ) self.__scrolledtext.grid(column=0, row=1, columnspan=7, sticky='EW') - self.__start_image = tkinter.PhotoImage(file = "img/start_icon.gif") - self.__start_button = tkinter.Button(self.__frame, text="START", + self.__start_image = tkinter.PhotoImage(file="img/start_icon.gif") + self.__start_button = tkinter.Button( + self.__frame, text="START", image=self.__start_image, compound=tkinter.LEFT, - state=tkinter.NORMAL, command=self.__on_start_button_click) + state=tkinter.NORMAL, command=self.__on_start_button_click, + ) self.__start_button.bind("", self.__on_start_button_click) self.__start_button.grid(column=1, row=0, sticky='EW') self.__stop_image = tkinter.PhotoImage(file="img/stop_icon.gif") - self.__stop_button = tkinter.Button(self.__frame, text="STOP ", + self.__stop_button = tkinter.Button( + self.__frame, text="STOP ", image=self.__stop_image, compound=tkinter.LEFT, - state=tkinter.DISABLED, command=self.__on_stop_button_click) + state=tkinter.DISABLED, command=self.__on_stop_button_click, + ) self.__stop_button.bind("", self.__on_stop_button_click) self.__stop_button.grid(column=2, row=0, sticky='EW') - self.__results_image = tkinter.PhotoImage(file = "img/results_icon.gif") - self.__results_button = tkinter.Button(self.__frame, text="RESULTS", + self.__results_image = tkinter.PhotoImage(file="img/results_icon.gif") + self.__results_button = tkinter.Button( + self.__frame, text="RESULTS", image=self.__results_image, compound=tkinter.LEFT, - state=tkinter.DISABLED, command=self.__on_results_button_click) + state=tkinter.DISABLED, command=self.__on_results_button_click, + ) self.__results_button.bind("", self.__on_results_button_click) self.__results_button.grid(column=3, row=0, sticky='EW') self.__clear_image = tkinter.PhotoImage(file="img/clear_icon.gif") - self.__clear_button = tkinter.Button(self.__frame, text="CLEAR", + self.__clear_button = tkinter.Button( + self.__frame, text="CLEAR", image=self.__clear_image, compound=tkinter.LEFT, - state=tkinter.NORMAL, command=self.__on_clear_button_click) + state=tkinter.NORMAL, command=self.__on_clear_button_click, + ) self.__clear_button.grid(column=4, row=0, sticky='EW') self.__copy_image = tkinter.PhotoImage(file="img/copy_icon.gif") - self.__copy_button = tkinter.Button(self.__frame, text="COPY", + self.__copy_button = tkinter.Button( + self.__frame, text="COPY", image=self.__copy_image, compound=tkinter.LEFT, - state=tkinter.NORMAL, command=self.__on_copy_button_click) + state=tkinter.NORMAL, command=self.__on_copy_button_click, + ) self.__copy_button.grid(column=5, row=0, sticky='EW') self.grid_columnconfigure(0, weight=1) @@ -96,22 +109,27 @@ def __on_start_button_click(self, *args): """ default_file = os.path.join(os.getcwd(), "input", "parameters.ini") default_dir = os.path.join(os.getcwd(), "input") - param_file = tkinter.filedialog.askopenfilename(title = "Select parameters file", - initialdir = default_dir, - initialfile = default_file, - filetypes = (("Simulation parameters", "*.ini"), - ("All files", "*.*") )) + param_file = tkinter.filedialog.askopenfilename( + title="Select parameters file", + initialdir=default_dir, + initialfile=default_file, + filetypes=( + ("Simulation parameters", "*.ini"), + ("All files", "*.*"), + ), + ) if param_file: - self.__controller.action(action = Action.START_SIMULATION, - param_file = param_file) - + self.__controller.action( + action=Action.START_SIMULATION, + param_file=param_file, + ) def __on_stop_button_click(self, *args): """ This method is called when stop button is clicked """ self.__insert_text(__name__, "STOPPED BY USER, FINALIZING SIMULATION") - self.__controller.action(action = Action.STOP_SIMULATION) + self.__controller.action(action=Action.STOP_SIMULATION) self.__set_state(State.STOPPING) def __on_results_button_click(self, *args): @@ -129,40 +147,42 @@ def __on_clear_button_click(self): """ This method is called when clear button is clicked """ - self.__scrolledtext.config(state = tkinter.NORMAL) + self.__scrolledtext.config(state=tkinter.NORMAL) self.__scrolledtext.delete(1.0, tkinter.END) - self.__scrolledtext.config(state = tkinter.DISABLED) + self.__scrolledtext.config(state=tkinter.DISABLED) def __on_copy_button_click(self): """ This method is called when copy button is clicked """ self.clipboard_clear() - self.__scrolledtext.config(state = tkinter.NORMAL) + self.__scrolledtext.config(state=tkinter.NORMAL) self.clipboard_append(self.__scrolledtext.get(1.0, tkinter.END)) - self.__scrolledtext.config(state = tkinter.DISABLED) + self.__scrolledtext.config(state=tkinter.DISABLED) self.__popup("Copied to clipboard.") def __plot_results(self, results: Results): file_extension = ".png" transparent_figure = False - + for plot in results.plot_list: - plt.figure(figsize=(8,7), facecolor='w', edgecolor='k') - plt.plot(plot.x, plot.y, color='#990000', linewidth=2) + plt.figure(figsize=(8, 7), facecolor='w', edgecolor='k') + plt.plot(plot.x, plot.y, color='#990000', linewidth=2) plt.title(plot.title) plt.xlabel(plot.x_label) plt.ylabel(plot.y_label) - if not plot.x_lim is None: + if plot.x_lim is not None: plt.xlim(plot.x_lim) - if not plot.y_lim is None: - plt.ylim(plot.y_lim) - #plt.grid() + if plot.y_lim is not None: + plt.ylim(plot.y_lim) + # plt.grid() plt.tight_layout() - plt.savefig(os.path.join("output", plot.file_name + file_extension), - transparent=transparent_figure) + plt.savefig( + os.path.join("output", plot.file_name + file_extension), + transparent=transparent_figure, + ) - #plt.show() + # plt.show() self.__popup("Plots successfully created! Check output directory.") def __insert_text(self, source: str, text: str): @@ -236,7 +256,7 @@ def notify_observer(self, *args, **kwargs): """ if "state" in kwargs: self.__set_state(kwargs["state"]) - #self.insert_text( __name__, "\n" ) + # self.insert_text( __name__, "\n" ) if "message" in kwargs: self.__insert_text(kwargs["source"], kwargs["message"]) if "results" in kwargs: @@ -262,14 +282,17 @@ def __popup(self, message: str): empty_top_label = tkinter.Label(top_level, height=1, bg='#FFFFFF') empty_top_label.pack() - label = tkinter.Label(top_level, text=message, height=0, width=50, - bg='#FFFFFF') + label = tkinter.Label( + top_level, text=message, height=0, width=50, + bg='#FFFFFF', + ) label.pack() - button = tkinter.Button(top_level, text=" OK ", - command=top_level.destroy) + button = tkinter.Button( + top_level, text=" OK ", + command=top_level.destroy, + ) button.pack() empty_botton_label = tkinter.Label(top_level, height=1, bg='#FFFFFF') empty_botton_label.pack() - diff --git a/sharc/gui/view_cli.py b/sharc/gui/view_cli.py index 5627aadbe..992efe5b9 100644 --- a/sharc/gui/view_cli.py +++ b/sharc/gui/view_cli.py @@ -21,12 +21,12 @@ def __init__(self, parent=None): super().__init__() self.parent = parent - def initialize(self, param_file): - self.controller.action(action = Action.START_SIMULATION_SINGLE_THREAD, - param_file = param_file) - - + self.controller.action( + action=Action.START_SIMULATION_SINGLE_THREAD, + param_file=param_file, + ) + def set_controller(self, controller: Controller): """ Keeps the reference to the controller @@ -37,7 +37,6 @@ def set_controller(self, controller: Controller): """ self.controller = controller - def notify_observer(self, *args, **kwargs): """ Implements the method from Observer class. See documentation on the @@ -51,7 +50,6 @@ def notify_observer(self, *args, **kwargs): if "message" in kwargs: self.insert_text(kwargs["source"], kwargs["message"]) - def insert_text(self, source: str, text: str): """ This method can be called to display a message on the console. The same @@ -66,4 +64,3 @@ def insert_text(self, source: str, text: str): """ logger = logging.getLogger(source) logger.info(text) - \ No newline at end of file diff --git a/sharc/how_to_run_campaigns.md b/sharc/how_to_run_campaigns.md new file mode 100644 index 000000000..ceab17fac --- /dev/null +++ b/sharc/how_to_run_campaigns.md @@ -0,0 +1,42 @@ + + +## Setup the campaigns folder + +1. Create a folder for your campaign: + - Example: `imt_hibs_ras_2600_MHz` + +2. Inside the campaign folder, create the following subfolders: + - `input` -> This folder is for defining the simulation parameters. For example, look in `campaigns/imt_hibs_ras_2600_MHz/input`. + - `output` -> SHARC will use this folder to save the results and plots. + - `scripts` -> This folder is used to implement scripts for post-processing. + +## Configure your simulation + +1. Create a parameter file: + - Example: `campaigns/imt_hibs_ras_2600_MHz/input/parameters_hibs_ras_2600_MHz_0km.ini`. + +2. Set the configuration for your study in the parameter file. + +3. Set the output folder in the parameter file: + ```ini + ########################################################################### + # output destination folder - this is relative to the SHARC/sharc directory + output_dir = campaigns/imt_hibs_ras_2600_MHz/output/ + ########################################################################### + # output folder prefix + output_dir_prefix = output_imt_hibs_ras_2600_MHz_0km + ``` +4. You can create multiple simulation parameters: + - Check the folder `sharc/campaigns/imt_hibs_ras_2600_MHz/input` for examples. + +## Run simulations and view the results + +1. **Run simulations:** + - In the `scripts` folder, you can create a file to start the simulation. Check the example: `campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_multi_thread.py`. + + - If you want to run a single-threaded simulation, check the file: `campaigns/imt_hibs_ras_2600_MHz/scripts/start_simulations_single_thread.py`. + +2. **Generate plots:** + - You can create a file to read the data and generate the plots. SHARC has a function called `plot_cdf` to make plotting easy. Check the example: `campaigns/imt_hibs_ras_2600_MHz/scripts/plot_results.py`. +""" + diff --git a/sharc/img/__init__.py b/sharc/img/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/img/__init__.py +++ b/sharc/img/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/input/detect_all_ini2yaml.py b/sharc/input/detect_all_ini2yaml.py new file mode 100644 index 000000000..2c1d762f7 --- /dev/null +++ b/sharc/input/detect_all_ini2yaml.py @@ -0,0 +1,96 @@ +""" +_summary_ + Script to convert all .ini files found in the current directory and its subdirectories to .yaml, + saving them in the same directory. + Usage: `python3 script.py` + Caveats: currently, it does not parse .ini lists. +""" + +import re +import os + +num_spaces_ident = 4 + + +def convert_ini_to_yaml(input_file, output_file): + + print("Reading from file: ", input_file) + print("Writing to file: ", output_file) + + input_file_content = open(input_file, "r").readlines() + + with open(output_file, 'w') as output_file: + current_ident: int = 0 # Identation to be used in yaml file + current_section: str = "" # Section of ini file + current_attr_name: str = "" # Attribute name of ini file + current_attr_comments: list[str] = [] + for line in input_file_content: + if (line.isspace() or not line): + continue + line.strip() + if (line.startswith("#")): + current_attr_comments.append(line) + continue + elif (line.startswith("[")): + # Line is section + # go back 1 identation + current_ident = max(current_ident - num_spaces_ident, 0) + section_name = re.findall(r'\[(.+)\]', line)[0] + section_name = section_name.lower() + current_section = section_name + for comment in current_attr_comments: + output_file.write(' ' * current_ident + comment) + current_attr_comments = [] + output_file.write( + ' ' * current_ident + + f"{current_section}:\n", + ) + current_ident += num_spaces_ident + else: + # line is attribute + try: + current_attr_name = re.findall(r"(.+) *=(.+)", line)[0][0] + current_attr_value = re.findall(r"(.+) *=(.+)", line)[0][1] + if (current_attr_value == 'TRUE' or current_attr_value == 'True'): + current_attr_value = 'true' + if (current_attr_value == 'FALSE' or current_attr_value == 'False'): + current_attr_value = 'false' + for comment in current_attr_comments: + output_file.write(' ' * current_ident + comment) + current_attr_comments = [] + output_file.write( + ' ' * current_ident + f"{current_attr_name} :{current_attr_value}\n", + ) + except IndexError: + print(input_file, 'did not match the expected format. Skipping...') + + print(f"Conversion complete: {output_file.name}") + + +if __name__ == "__main__": + + root_dir = os.getcwd() # Use the current working directory as the root + # Add your environment folder names here + exclude_dirs = {'env', 'venv', '.venv'} + + ini_files = [] + for root, dirs, files in os.walk(root_dir): + + # Modify dirs in-place to exclude specific directories + dirs[:] = [d for d in dirs if d not in exclude_dirs] + + for file in files: + if file.endswith('.ini'): + ini_files.append(os.path.join(root, file)) + + if not ini_files: + print("No .ini files found in the current directory or its subdirectories.") + exit(1) + + print("Detected .ini files:") + for ini_file in ini_files: + print(ini_file) + + for ini_file in ini_files: + output_file_name = os.path.splitext(ini_file)[0] + '.yaml' + convert_ini_to_yaml(ini_file, output_file_name) diff --git a/sharc/input/examples/parameters_imt_macro_eess_active.ini b/sharc/input/examples/parameters_imt_macro_eess_active.ini new file mode 100644 index 000000000..f1741eb6b --- /dev/null +++ b/sharc/input/examples/parameters_imt_macro_eess_active.ini @@ -0,0 +1,831 @@ +[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 = 101 +########################################################################### +# if FALSE, then a new output directory is created +overwrite_output = TRUE + +[IMT] +########################################################################### +# Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" +# "INDOOR" +topology = HOTSPOT +########################################################################### +# Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies +wrap_around = FALSE +########################################################################### +# Number of clusters in macro cell topology +num_clusters = 7 +########################################################################### +# Inter-site distance in macrocell network topology [m] +intersite_distance = 500 +########################################################################### +# Minimum 2D separation distance from BS to UE [m] +minimum_separation_distance_bs_ue = 0 +########################################################################### +# Defines if IMT service is the interferer or interfered-with service +# TRUE : IMT suffers interference +# FALSE : IMT generates interference +interfered_with = FALSE +########################################################################### +# IMT center frequency [MHz] +frequency = 10500 +########################################################################### +# IMT bandwidth [MHz] +bandwidth = 200 +########################################################################### +# IMT resource block bandwidth [MHz] +rb_bandwidth = 0.180 +########################################################################### +# IMT spectrum emission mask. Options are: +# "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 +# "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in +# TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) +spectral_mask = IMT-2020 +########################################################################### +# level of spurious emissions [dBm/MHz] +spurious_emissions = -13 +########################################################################### +# Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 +# means that 10% of the total bandwidth will be used as guard band: 5% in +# the lower +guard_band_ratio = 0.1 +########################################################################### +# The load probability (or activity factor) models the statistical +# variation of the network load by defining the number of fully loaded +# base stations that are simultaneously transmitting +bs_load_probability = .2 +########################################################################### +# Conducted power per antenna element [dBm/bandwidth] +bs_conducted_power = 10 +########################################################################### +# Base station height [m] +bs_height = 6 +########################################################################### +# Base station noise figure [dB] +bs_noise_figure = 10 +########################################################################### +# User equipment noise temperature [K] +bs_noise_temperature = 290 +########################################################################### +# Base station array ohmic loss [dB] +bs_ohmic_loss = 3 +########################################################################### +# Uplink attenuation factor used in link-to-system mapping +ul_attenuation_factor = 0.4 +########################################################################### +# Uplink minimum SINR of the code set [dB] +ul_sinr_min = -10 +########################################################################### +# Uplink maximum SINR of the code set [dB] +ul_sinr_max = 22 +########################################################################### +# Number of UEs that are allocated to each cell within handover margin. +# Remember that in macrocell network each base station has 3 cells (sectors) +ue_k = 3 +########################################################################### +# Multiplication factor that is used to ensure that the sufficient number +# of UE's will distributed throughout ths system area such that the number +# of K users is allocated to each cell. Normally, this values varies +# between 2 and 10 according to the user drop method +ue_k_m = 1 +########################################################################### +# Percentage of indoor UE's [%] +ue_indoor_percent = 5 +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter states how the UEs will be distributed +# Possible values: UNIFORM : UEs will be uniformly distributed within the +# whole simulation area. Not applicable to +# hotspots. +# ANGLE_AND_DISTANCE : UEs will be distributed following +# given distributions for angle and +# distance. In this case, these must be +# defined later. +ue_distribution_type = ANGLE_AND_DISTANCE +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter models the distance between UE's and BS. +# Possible values: RAYLEIGH, UNIFORM +ue_distribution_distance = RAYLEIGH +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). +# Possible values: NORMAL, UNIFORM +ue_distribution_azimuth = NORMAL +########################################################################### +# Power control algorithm +# ue_tx_power_control = "ON",power control On +# ue_tx_power_control = "OFF",power control Off +ue_tx_power_control = ON +########################################################################### +# Power per RB used as target value [dBm] +ue_p_o_pusch = -95 +########################################################################### +# Alfa is the balancing factor for UEs with bad channel +# and UEs with good channel +ue_alpha = 1 +########################################################################### +# Maximum UE transmit power [dBm] +ue_p_cmax = 22 +########################################################################### +# UE power dynamic range [dB] +# The minimum transmit power of a UE is (ue_p_cmax - ue_dynamic_range) +ue_power_dynamic_range = 63 +########################################################################### +# UE height [m] +ue_height = 1.5 +########################################################################### +# User equipment noise figure [dB] +ue_noise_figure = 10 +########################################################################### +# User equipment feed loss [dB] +ue_ohmic_loss = 3 +########################################################################### +# User equipment body loss [dB] +ue_body_loss = 4 +########################################################################### +# Downlink attenuation factor used in link-to-system mapping +dl_attenuation_factor = 0.6 +########################################################################### +# Downlink minimum SINR of the code set [dB] +dl_sinr_min = -10 +########################################################################### +# Downlink maximum SINR of the code set [dB] +dl_sinr_max = 30 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "CI" (close-in FS reference distance) +# "UMa" (Urban Macro - 3GPP) +# "UMi" (Urban Micro - 3GPP) +# "TVRO-URBAN" +# "TVRO-SUBURBAN" +# "ABG" (Alpha-Beta-Gamma) +channel_model = UMi +########################################################################### +# Adjustment factor for LoS probability in UMi path loss model. +# Original value: 18 (3GPP) +los_adjustment_factor = 18 +########################################################################### +# If shadowing should be applied or not +shadowing = TRUE +########################################################################### +# System receive noise temperature [K] +noise_temperature = 290 +BOLTZMANN_CONSTANT = 1.38064852e-23 + +[IMT_ANTENNA] +########################################################################### +# Defines the antenna model to be used in compatibility studies between +# IMT and other services in adjacent band +# Possible values: SINGLE_ELEMENT, BEAMFORMING +adjacent_antenna_model = SINGLE_ELEMENT +########################################################################### +# If normalization of M2101 should be applied for BS +bs_normalization = FALSE +########################################################################### +# If normalization of M2101 should be applied for UE +ue_normalization = FALSE +########################################################################### +# File to be used in the BS beamforming normalization +# Normalization files can be generated with the +# antenna/beamforming_normalization/normalize_script.py script +bs_normalization_file = antenna/beamforming_normalization/bs_norm.npz +########################################################################### +# File to be used in the UE beamforming normalization +# Normalization files can be generated with the +# antenna/beamforming_normalization/normalize_script.py script +ue_normalization_file = antenna/beamforming_normalization/ue_norm.npz +########################################################################### +# Radiation pattern of each antenna element +# Possible values: "M2101", "F1336", "FIXED" +bs_element_pattern = M2101 +ue_element_pattern = M2101 +########################################################################### +# Minimum array gain for the beamforming antenna [dBi] +bs_minimum_array_gain = -200 +ue_minimum_array_gain = -200 +########################################################################### +# mechanical downtilt [degrees] +# NOTE: consider defining it to 90 degrees in case of indoor simulations +bs_downtilt = 10 +########################################################################### +# BS/UE maximum transmit/receive element gain [dBi] +# default: bs_element_max_g = 5, for M.2101 +# = 15, for M.2292 +# default: ue_element_max_g = 5, for M.2101 +# = -3, for M.2292 +bs_element_max_g = 5.5 +ue_element_max_g = 5 +########################################################################### +# BS/UE horizontal 3dB beamwidth of single element [degrees] +bs_element_phi_3db = 65 +ue_element_phi_3db = 90 +########################################################################### +# BS/UE vertical 3dB beamwidth of single element [degrees] +# For F1336: if equal to 0, then beamwidth is calculated automaticaly +bs_element_theta_3db = 65 +ue_element_theta_3db = 90 +########################################################################### +# BS/UE number of rows in antenna array +bs_n_rows = 8 +ue_n_rows = 4 +########################################################################### +# BS/UE number of columns in antenna array +bs_n_columns = 8 +ue_n_columns = 4 +########################################################################### +# BS/UE array horizontal element spacing (d/lambda) +bs_element_horiz_spacing = 0.5 +ue_element_horiz_spacing = 0.5 +########################################################################### +# BS/UE array vertical element spacing (d/lambda) +bs_element_vert_spacing = 0.5 +ue_element_vert_spacing = 0.5 +########################################################################### +# BS/UE front to back ratio of single element [dB] +bs_element_am = 30 +ue_element_am = 25 +########################################################################### +# BS/UE single element vertical sidelobe attenuation [dB] +bs_element_sla_v = 30 +ue_element_sla_v = 25 +########################################################################### +# Multiplication factor k that is used to adjust the single-element pattern. +# According to Report ITU-R M.[IMT.AAS], this may give a closer match of the +# side lobes when beamforming is assumed in adjacent channel. +# Original value: 12 (Rec. ITU-R M.2101) +bs_multiplication_factor = 12 +ue_multiplication_factor = 12 + +[HOTSPOT] +########################################################################### +# Number of hotspots per macro cell (sector) +num_hotspots_per_cell = 1 +########################################################################### +# Maximum 2D distance between hotspot and UE [m] +# This is the hotspot radius +max_dist_hotspot_ue = 100 +########################################################################### +# Minimum 2D distance between macro cell base station and hotspot [m] +min_dist_bs_hotspot = 0 + +[INDOOR] +########################################################################### +# Basic path loss model for indoor topology. Possible values: +# "FSPL" (free-space path loss), +# "INH_OFFICE" (3GPP Indoor Hotspot - Office) +basic_path_loss = INH_OFFICE +########################################################################### +# Number of rows of buildings in the simulation scenario +n_rows = 3 +########################################################################### +# Number of colums of buildings in the simulation scenario +n_colums = 2 +########################################################################### +# Number of buildings containing IMT stations. Options: +# 'ALL': all buildings contain IMT stations. +# Number of buildings. +num_imt_buildings = ALL +########################################################################### +# Street width (building separation) [m] +street_width = 30 +########################################################################### +# Intersite distance [m] +intersite_distance = 40 +########################################################################### +# Number of cells per floor +num_cells = 3 +########################################################################### +# Number of floors per building +num_floors = 1 +########################################################################### +# Percentage of indoor UE's [0, 1] +ue_indoor_percent = .95 +########################################################################### +# Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT" +building_class = TRADITIONAL + +[FSS_SS] +########################################################################### +# satellite center frequency [MHz] +frequency = 43000 +########################################################################### +# satellite bandwidth [MHz] +bandwidth = 200 +########################################################################### +# satellite altitude [m] and latitude [deg] +altitude = 35780000 +lat_deg = 0 +########################################################################### +# Elevation angle [deg] +elevation = 270 +########################################################################### +# Azimuth angle [deg] +azimuth = 0 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -5 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 950 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 0 +########################################################################### +# Satellite peak receive antenna gain [dBi] +antenna_gain = 46.6 +########################################################################### +# Antenna pattern of the FSS space station +# Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" +antenna_pattern = FSS_SS +# IMT parameters relevant to the satellite system +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +imt_altitude = 0 +imt_lat_deg = 0 +imt_long_diff_deg = 0 +season = SUMMER +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 + clutter loss) +# "P619" +channel_model = P619 +########################################################################### +# The required near-in-side-lobe level (dB) relative to peak gain +# according to ITU-R S.672-4 +antenna_l_s = -20 +########################################################################### +# 3 dB beamwidth angle (3 dB below maximum gain) [degrees] +antenna_3_dB = 0.65 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + +[FSS_ES] +########################################################################### +# type of FSS-ES location: +# FIXED - position must be given +# CELL - random within central cell +# NETWORK - random within whole network +# UNIFORM_DIST - uniform distance from cluster centre, +# between min_dist_to_bs and max_dist_to_bs +location = UNIFORM_DIST +########################################################################### +# x-y coordinates [m] (only if FIXED location is chosen) +x = 10000 +y = 0 +########################################################################### +# minimum distance from BSs [m] +min_dist_to_bs = 10 +########################################################################### +# maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen) +max_dist_to_bs = 600 +########################################################################### +# antenna height [m] +height = 6 +########################################################################### +# Elevation angle [deg], minimum and maximum, values +elevation_min = 48 +elevation_max = 80 +########################################################################### +# Azimuth angle [deg] +# either a specific angle or string 'RANDOM' +azimuth = RANDOM +########################################################################### +# center frequency [MHz] +frequency = 43000 +########################################################################### +# bandwidth [MHz] +bandwidth = 6 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 950 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 0 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -68.3 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 32 +########################################################################### +# Antenna pattern of the FSS Earth station +# Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", +# "Modified ITU-R S.465" +antenna_pattern = Modified ITU-R S.465 +########################################################################### +# Diameter of antenna [m] +diameter = 1.8 +########################################################################### +# Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model +antenna_envelope_gain = 0 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +# "P452" +# "TVRO-URBAN" +# "TVRO-SUBURBAN" +# "HDFSS" +channel_model = P452 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 +########################################################################### +# P452 parameters +########################################################################### +# Total air pressure in hPa +atmospheric_pressure = 935 +########################################################################### +# Temperature in Kelvin +air_temperature = 300 +########################################################################### +#Sea-level surface refractivity (use the map) +N0 = 352.58 +########################################################################### +#Average radio-refractive (use the map) +delta_N = 43.127 +########################################################################### +#percentage p. Float (0 to 100) or RANDOM +percentage_p = 0.2 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dct = 70 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dcr = 70 +########################################################################### +##Effective height of interfering antenna (m) +Hte = 20 +########################################################################### +#Effective height of interfered-with antenna (m) +Hre = 3 +########################################################################### +##Latitude of transmitter +tx_lat = -23.55028 +########################################################################### +#Latitude of receiver +rx_lat = -23.17889 +########################################################################### +#Antenna polarization +polarization = horizontal +########################################################################### +#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) +clutter_loss = TRUE +########################################################################### +# HDFSS propagation parameters +########################################################################### +# HDFSS position relative to building it is on. Possible values are +# ROOFTOP and BUILDINGSIDE +es_position = ROOFTOP +########################################################################### +# Enable shadowing loss +shadow_enabled = TRUE +########################################################################### +# Enable building entry loss +building_loss_enabled = TRUE +########################################################################### +# Enable interference from IMT stations at the same building as the HDFSS +same_building_enabled = FALSE +########################################################################### +# Enable diffraction loss +diffraction_enabled = TRUE +########################################################################### +# Building entry loss type applied between BSs and HDFSS ES. Options are: +# P2109_RANDOM: random probability at P.2109 model, considering elevation +# P2109_FIXED: fixed probability at P.2109 model, considering elevation. +# Probability must be specified in bs_building_entry_loss_prob. +# FIXED_VALUE: fixed value per BS. Value must be specified in +# bs_building_entry_loss_value. +bs_building_entry_loss_type = P2109_FIXED +########################################################################### +# Probability of building entry loss not exceeded if +# bs_building_entry_loss_type = P2109_FIXED +bs_building_entry_loss_prob = 0.75 +########################################################################### +# Value in dB of building entry loss if +# bs_building_entry_loss_type = FIXED_VALUE +bs_building_entry_loss_value = 35 + +[FS] +########################################################################### +# x-y coordinates [m] +x = 1000 +y = 0 +########################################################################### +# antenna height [m] +height = 15 +########################################################################### +# Elevation angle [deg] +elevation = -10 +########################################################################### +# Azimuth angle [deg] +azimuth = 180 +########################################################################### +# center frequency [MHz] +frequency = 27250 +########################################################################### +# bandwidth [MHz] +bandwidth = 112 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 290 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 20 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -68.3 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 36.9 +########################################################################### +# Antenna pattern of the fixed wireless service +# Possible values: "ITU-R F.699", "OMNI" +antenna_pattern = ITU-R F.699 +########################################################################### +# Diameter of antenna [m] +diameter = 0.3 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +channel_model = FSPL +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + + +[HAPS] +########################################################################### +# HAPS center frequency [MHz] +frequency = 27250 +########################################################################### +# HAPS bandwidth [MHz] +bandwidth = 200 +########################################################################### +# HAPS altitude [m] and latitude [deg] +altitude = 20000 +lat_deg = 0 +########################################################################### +# Elevation angle [deg] +elevation = 270 +########################################################################### +# Azimuth angle [deg] +azimuth = 0 +########################################################################### +# EIRP spectral density [dBW/MHz] +eirp_density = 4.4 +########################################################################### +# HAPS peak antenna gain [dBi] +antenna_gain = 28.1 +########################################################################### +# Adjacent channel selectivity [dB] +acs = 30 +########################################################################### +# Antenna pattern of the HAPS (airbone) station +# Possible values: "ITU-R F.1891", "OMNI" +antenna_pattern = ITU-R F.1891 +# IMT parameters relevant to the HAPS system +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +imt_altitude = 0 +imt_lat_deg = 0 +imt_long_diff_deg = 0 +season = SUMMER + +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 + clutter loss) +# "P619" +channel_model = P619 + +########################################################################### +# Near side-lobe level (dB) relative to the peak gain required by the system +# design, and has a maximum value of โˆ’25 dB +antenna_l_n = -25 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + +[RNS] +########################################################################### +# x-y coordinates [m] +x = 660 +y = -370 +########################################################################### +# altitude [m] +altitude = 150 +########################################################################### +# center frequency [MHz] +frequency = 32000 +########################################################################### +# bandwidth [MHz] +bandwidth = 60 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 1154 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -70.79 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 30 +########################################################################### +# Adjacent channel selectivity [dB] +acs = 30 +########################################################################### +# Antenna pattern of the fixed wireless service +# Possible values: "ITU-R M.1466", "OMNI" +antenna_pattern = ITU-R M.1466 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 dB + clutter loss) +# "P619" +channel_model = P619 +########################################################################### +# Specific parameters for P619 +season = SUMMER +imt_altitude = 0 +imt_lat_deg = 0 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + + +[RAS] +########################################################################### +# x-y coordinates [m] +x = 81000 +y = 0 +########################################################################### +# antenna height [m] +height = 15 +########################################################################### +# Elevation angle [deg] +elevation = 45 +########################################################################### +# Azimuth angle [deg] +azimuth = -90 +########################################################################### +# center frequency [MHz] +frequency = 43000 +########################################################################### +# bandwidth [MHz] +bandwidth = 1000 +########################################################################### +# Antenna noise temperature [K] +antenna_noise_temperature = 25 +########################################################################### +# Receiver noise temperature [K] +receiver_noise_temperature = 65 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 20 +########################################################################### +# Antenna efficiency +antenna_efficiency = 1 +########################################################################### +# Antenna pattern of the FSS Earth station +# Possible values: "ITU-R SA.509", "OMNI" +antenna_pattern = ITU-R SA.509 +########################################################################### +# Antenna gain for "OMNI" pattern +antenna_gain = 0 +########################################################################### +# Diameter of antenna [m] +diameter = 15 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 +SPEED_OF_LIGHT = 299792458 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +# "P452" +channel_model = P452 +########################################################################### +# P452 parameters +########################################################################### +# Total air pressure in hPa +atmospheric_pressure = 935 +########################################################################### +# Temperature in Kelvin +air_temperature = 300 +########################################################################### +#Sea-level surface refractivity (use the map) +N0 = 352.58 +########################################################################### +#Average radio-refractive (use the map) +delta_N = 43.127 +########################################################################### +#percentage p. Float (0 to 100) or RANDOM +percentage_p = 0.2 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dct = 70 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dcr = 70 +########################################################################### +##Effective height of interfering antenna (m) +Hte = 20 +########################################################################### +#Effective height of interfered-with antenna (m) +Hre = 3 +########################################################################### +##Latitude of transmitter +tx_lat = -23.55028 +########################################################################### +#Latitude of receiver +rx_lat = -23.17889 +########################################################################### +#Antenna polarization +polarization = horizontal +########################################################################### +#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) +clutter_loss = TRUE + +[EESS_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 = 50 +########################################################################### +# sensor altitude [m] +altitude = 514000 +########################################################################### +# 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.6 +# Antenna diameter for ITU-R RS.1813 [m] +antenna_diameter = 2.2 +########################################################################### +# receive antenna gain - applicable for 9a, 9b and OMNI [dBi] +antenna_gain = 47 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "P619" +channel_model = P619 +# Relevant IMT parameters which apply for ITU-R P.619 +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# season of the year: "SUMMER", "WINTER" +imt_altitude = 6 +imt_lat_deg = -22.9 +season = SUMMER diff --git a/sharc/input/ini2yaml_scraper.py b/sharc/input/ini2yaml_scraper.py new file mode 100644 index 000000000..577ba630e --- /dev/null +++ b/sharc/input/ini2yaml_scraper.py @@ -0,0 +1,78 @@ +""" +_summary_ + Script to be run when you need to convert between old .ini parameters and new .yaml parameters. + Usage: `python3 -i input-file.ini -o output-file.yaml` + Caveats: currently, it does not parse .ini lists. +""" + +if __name__ == "__main__": + + import re + from argparse import ArgumentParser + + num_spaces_ident = 4 + + parser = ArgumentParser() + parser.add_argument( + "-i", "--input", dest="input_file", + help="Input file. Should be .ini. Absolute Path", required=True, + ) + parser.add_argument( + "-o", "--output", dest="output_file", + help="Output file. Should be .yaml. Absolute Path", required=True, + ) + + args = parser.parse_args() + + print("Reading from file: ", args.input_file) + print("Writing to file: ", args.output_file) + + input_file_content = open(args.input_file, "r").readlines() + + # if os.path.exists(args.output_file): + # response = input(f"File {args.output_file} already exists, are you sure that you want to override it? Y/n") + # if(response == "n" or response == "N"): + # print("**** Operation Cancelled ****") + # exit(1) + with open(args.output_file, 'w') as output_file: + current_ident: int = 0 # Identation to be used in yaml file + current_section: str = "" # Section of ini file + current_attr_name: str = "" # Attribute name of ini file + current_attr_comments: list[str] = [] + for line in input_file_content: + if (line.isspace() or not line): + continue + line.strip() + if (line.startswith("#")): + current_attr_comments.append(line) + continue + elif (line.startswith("[")): + # Line is section + # go back 1 identation + current_ident = max(current_ident - num_spaces_ident, 0) + section_name = re.findall(r'\[(.+)\]', line)[0] + section_name = section_name.lower() + current_section = section_name + for comment in current_attr_comments: + output_file.write(' ' * current_ident + comment) + current_attr_comments = [] + output_file.write( + ' ' * current_ident + + f"{current_section}:\n", + ) + current_ident += num_spaces_ident + else: + # line is attribute + current_attr_name = re.findall(r"(.+) *=(.+)", line)[0][0] + current_attr_value = re.findall(r"(.+) *=(.+)", line)[0][1] + if (current_attr_value == 'TRUE' or current_attr_value == 'True'): + current_attr_value = 'true' + if (current_attr_value == 'FALSE' or current_attr_value == 'False'): + current_attr_value = 'false' + for comment in current_attr_comments: + output_file.write(' ' * current_ident + comment) + current_attr_comments = [] + output_file.write( + ' ' * current_ident + + f"{current_attr_name} :{current_attr_value}\n", + ) diff --git a/sharc/input/input_folder.md b/sharc/input/input_folder.md new file mode 100644 index 000000000..9afd7178e --- /dev/null +++ b/sharc/input/input_folder.md @@ -0,0 +1,3 @@ +# Input folder +This folder holds reference parameter files used for root level campaigns or parameters used as reference +for campaign level simulations. \ No newline at end of file diff --git a/sharc/input/parameters.ini b/sharc/input/parameters.ini index 531d3affa..f2423b268 100644 --- a/sharc/input/parameters.ini +++ b/sharc/input/parameters.ini @@ -1,14 +1,14 @@ [GENERAL] ########################################################################### # Number of simulation snapshots -num_snapshots = 10000 +num_snapshots = 100 ########################################################################### # IMT link that will be simulated (DOWNLINK or UPLINK) -imt_link = UPLINK +imt_link = DOWNLINK ########################################################################### # The chosen system for sharing study -# EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS -system = EESS_PASSIVE +# EESS_SS, FSS_SS, FSS_ES, FS, RAS +system = FSS_SS ########################################################################### # Compatibility scenario (co-channel and/or adjacent channel interference) enable_cochannel = FALSE @@ -24,7 +24,7 @@ overwrite_output = TRUE ########################################################################### # Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" # "INDOOR" -topology = HOTSPOT +topology = MACROCELL ########################################################################### # Enable wrap around. Available only for "MACROCELL" and "HOTSPOT" topologies wrap_around = FALSE @@ -44,7 +44,7 @@ minimum_separation_distance_bs_ue = 0 interfered_with = FALSE ########################################################################### # IMT center frequency [MHz] -frequency = 24350 +frequency = 43100 ########################################################################### # IMT bandwidth [MHz] bandwidth = 200 @@ -178,6 +178,21 @@ dl_sinr_max = 30 # "TVRO-SUBURBAN" # "ABG" (Alpha-Beta-Gamma) channel_model = UMi +# Parameters for the P.619 propagation model +# Used only when TopologyNTN is set. +# 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 heigh +space_station_alt_m = 20000.0 +earth_station_alt_m = 15.0 +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 ########################################################################### # Adjustment factor for LoS probability in UMi path loss model. # Original value: 18 (3GPP) @@ -291,7 +306,7 @@ min_dist_bs_hotspot = 0 # Basic path loss model for indoor topology. Possible values: # "FSPL" (free-space path loss), # "INH_OFFICE" (3GPP Indoor Hotspot - Office) -basic_path_loss = "INH_OFFICE" +basic_path_loss = INH_OFFICE ########################################################################### # Number of rows of buildings in the simulation scenario n_rows = 3 @@ -322,6 +337,39 @@ ue_indoor_percent = .95 # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT" building_class = TRADITIONAL +[NTN] +########################################################################### +# NTN Airborne Platform height (m) +bs_height = 20000 +########################################################################### +# NTN cell radius in network topology [m] +cell_radius = 90000 +########################################################################### +# NTN space station azimuth [degree] +bs_azimuth = 45 +########################################################################### +# NTN space station elevation [degree] +bs_elevation = 45 +########################################################################### +# number of sectors [degree] +num_sectors = 7 +########################################################################### +# NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3) +intersite_distance = 155884 +########################################################################### +# Conducted power per element [dBm/bandwidth] +bs_conducted_power = 37 +########################################################################### +# Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2 +bs_backoff_power = 3 +########################################################################### +# NTN Antenna configuration +bs_n_rows_layer1 = 2 +bs_n_columns_layer1 = 2 +bs_n_rows_layer2 = 4 +bs_n_columns_layer2 = 2 + + [FSS_SS] ########################################################################### # satellite center frequency [MHz] @@ -332,7 +380,7 @@ bandwidth = 200 ########################################################################### # satellite altitude [m] and latitude [deg] altitude = 35780000 -lat_deg = 0 +lat_deg = 10 ########################################################################### # Elevation angle [deg] elevation = 270 @@ -352,17 +400,28 @@ adjacent_ch_selectivity = 0 # Satellite peak receive antenna gain [dBi] antenna_gain = 46.6 ########################################################################### +# SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum +# gain and the gain at the peak of the first side lobe. +self.slr = 20 +# Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function) +self.n_side_lobes = 4 +# Radial and transverse sizes of the effective radiating area of the satellite transmit antenna (m). +self.l_r = 0.5 +self.l_t = 0.5 +self.roll_off = 3 +########################################################################### # Antenna pattern of the FSS space station # Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" antenna_pattern = FSS_SS -# IMT parameters relevant to the satellite system -# altitude of IMT system (in meters) -# latitude of IMT system (in degrees) -# difference between longitudes of IMT and satellite system +# Parameters for the P.619 propagation model +# earth_station_alt_m - altitude of IMT system (in meters) +# earth_station_lat_deg - latitude of IMT system (in degrees) +# earth_station_long_diff_deg - difference between longitudes of IMT and satellite system # (positive if space-station is to the East of earth-station) -imt_altitude = 0 -imt_lat_deg = 0 -imt_long_diff_deg = 0 +# season - season of the year. +earth_station_alt_m = 0 +earth_station_lat_deg = 0 +earth_station_long_diff_deg = 0 season = SUMMER ########################################################################### # Channel parameters @@ -610,14 +669,15 @@ acs = 30 # Antenna pattern of the HAPS (airbone) station # Possible values: "ITU-R F.1891", "OMNI" antenna_pattern = ITU-R F.1891 -# IMT parameters relevant to the HAPS system -# altitude of IMT system (in meters) -# latitude of IMT system (in degrees) -# difference between longitudes of IMT and satellite system +# Parameters for the P.619 propagation model +# earth_station_alt_m - altitude of IMT system (in meters) +# earth_station_lat_deg - latitude of IMT system (in degrees) +# earth_station_long_diff_deg - difference between longitudes of IMT and satellite system # (positive if space-station is to the East of earth-station) -imt_altitude = 0 -imt_lat_deg = 0 -imt_long_diff_deg = 0 +# season - season of the year. +earth_station_alt_m = 0 +earth_station_lat_deg = 0 +earth_station_long_diff_deg = 0 season = SUMMER ########################################################################### @@ -673,10 +733,16 @@ antenna_pattern = ITU-R M.1466 # "P619" channel_model = P619 ########################################################################### -# Specific parameters for P619 +# Parameters for the P.619 propagation model +# earth_station_alt_m - altitude of IMT system (in meters) +# earth_station_lat_deg - latitude of IMT system (in degrees) +# earth_station_long_diff_deg - difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +# season - season of the year. +earth_station_alt_m = 0 +earth_station_lat_deg = 0 +earth_station_long_diff_deg = 0 season = SUMMER -imt_altitude = 0 -imt_lat_deg = 0 ########################################################################### # Constants BOLTZMANN_CONSTANT = 1.38064852e-23 @@ -777,14 +843,41 @@ polarization = horizontal ########################################################################### #determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) clutter_loss = TRUE +# Parameters for the P.619 propagation model +# Used between IMT space station and another terrestrial system. +# 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 heigh +space_station_alt_m = 20000.0 +# Enter the RAS antenna heigh +earth_station_alt_m = 15.0 +# 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 -[EESS_PASSIVE] +[EESS_SS] ########################################################################### # sensor center frequency [MHz] frequency = 23900 ########################################################################### # sensor bandwidth [MHz] bandwidth = 200 +###########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 = 46.6 @@ -811,14 +904,66 @@ antenna_gain = 52 # channel model, possible values are "FSPL" (free-space path loss), # "P619" channel_model = FSPL -# Relevant IMT parameters which apply for ITU-R P.619 -# altitude of IMT system (in meters) -# latitude of IMT system (in degrees) -# season of the year: "SUMMER", "WINTER" -imt_altitude = 20 -imt_lat_deg = -22.9 -season = SUMMER +# Parameters for the P.619 propagation model +# earth_station_alt_m - altitude of IMT system (in meters) +# earth_station_lat_deg - latitude of IMT system (in degrees) +# earth_station_long_diff_deg - difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +# season - season of the year. +earth_station_alt_m = 0 +earth_station_lat_deg = 0 +earth_station_long_diff_deg = 0 +season = "SUMMER" + +[METSAT_SS] +########################################################################### +# sensor center frequency [MHz] +frequency = 8195 +########################################################################### +# sensor bandwidth [MHz] +bandwidth = 20 +###########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 ########################################################################### +# Elevation angle [deg] +elevation = 3 +########################################################################### +# sensor altitude [m] +altitude = 35786000 +########################################################################### +# Antenna pattern of the sensor +antenna_pattern = ITU-R S.672 +########################################################################### +# receive antenna gain [dBi] +antenna_gain = 52 +# The required near-in-side-lobe level (dB) relative to peak gain +# according to ITU-R S.672-4 +antenna_l_s = -20.0 +# 3 dB beamwidth angle (3 dB below maximum gain) [degrees] +antenna_3_dB = 0.65 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "P619" +channel_model = FSPL +# Parameters for the P.619 propagation model +# earth_station_alt_m - altitude of IMT system (in meters) +# earth_station_lat_deg - latitude of IMT system (in degrees) +# earth_station_long_diff_deg - difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +# season - season of the year. +earth_station_alt_m = 0 +earth_station_lat_deg = 0 +earth_station_long_diff_deg = 0 +season = "SUMMER" # Constants BOLTZMANN_CONSTANT = 1.38064852e-23 EARTH_RADIUS = 6371000 diff --git a/sharc/input/parameters.yaml b/sharc/input/parameters.yaml new file mode 100644 index 000000000..92e74b9e4 --- /dev/null +++ b/sharc/input/parameters.yaml @@ -0,0 +1,1293 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots: 100 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link: DOWNLINK + ########################################################################### + # The chosen system for sharing study + # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS, SINGLE_EARTH_STATION + system: SINGLE_EARTH_STATION + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel: TRUE + enable_adjacent_channel: TRUE + ########################################################################### + # Seed for random number generator + seed: 101 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output: TRUE +imt: + ########################################################################### + # Minimum 2D separation distance from BS to UE [m] + minimum_separation_distance_bs_ue: 1.3 + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE: IMT suffers interference + # FALSE : IMT generates interference + interfered_with: FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency: 7000 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth: 20 + ########################################################################### + # IMT resource block bandwidth [MHz] + rb_bandwidth: 0.181 + ########################################################################### + # IMT spectrum emission mask. Options are: + # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 + # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in + # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) + spectral_mask: 3GPP E-UTRA + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions: -13.1 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio: 0.14 + ########################################################################### + # Network topology parameters. + topology: + ########################################################################### + # 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: 543 + ########################################################################### + # Enable wrap around. + wrap_around: true + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS" + single_bs: + ########################################################################### + # Inter-site distance or Cell Radius in single Base Station network topology [m] + # You can either provide 'cell_radius' or 'intersite_distance' for this topology + # The relationship used is cell_radius = intersite_distance * 2 / 3 + cell_radius: 543 + # intersite_distance: 1 + ########################################################################### + # Number of clusters in single base station topology + # You can simulate 1 or 2 BS's with this topology + num_clusters: 2 + ########################################################################### + # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT" + hotspot: + ########################################################################### + # Inter-site distance in hotspot network topology [m] + intersite_distance: 321 + ########################################################################### + # Enable wrap around. + wrap_around: true + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: 1 + ########################################################################### + # Maximum 2D distance between hotspot and UE [m] + # This is the hotspot radius + max_dist_hotspot_ue: 99.9 + ########################################################################### + # Minimum 2D distance between macro cell base station and hotspot [m] + min_dist_bs_hotspot: 1.2 + ########################################################################### + # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR" + indoor: + ########################################################################### + # Basic path loss model for indoor topology. Possible values: + # "FSPL" (free-space path loss), + # "INH_OFFICE" (3GPP Indoor Hotspot - Office) + basic_path_loss: FSPL + ########################################################################### + # Number of rows of buildings in the simulation scenario + n_rows: 3 + ########################################################################### + # Number of colums of buildings in the simulation scenario + n_colums: 2 + ########################################################################### + # Number of buildings containing IMT stations. Options: + # 'ALL': all buildings contain IMT stations. + # Number of buildings. + num_imt_buildings: 2 + ########################################################################### + # Street width (building separation) [m] + street_width: 30.1 + ########################################################################### + # Intersite distance [m] + intersite_distance: 40.1 + ########################################################################### + # Number of cells per floor + num_cells: 3 + ########################################################################### + # Number of floors per building + num_floors: 1 + ########################################################################### + # Percentage of indoor UE's [0, 1] + ue_indoor_percent: .95 + ########################################################################### + # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT" + building_class: THERMALLY_EFFICIENT + ########################################################################### + # NTN Topology Parameters + ntn: + ########################################################################### + # NTN cell radius or intersite distance in network topology [m] + # @important: You can set only one of cell_radius or intersite_distance + # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3) + # NOTE: note that intersite distance has a different geometric meaning in ntn + cell_radius: 123 + # intersite_distance: 155884 + ########################################################################### + # NTN space station azimuth [degree] + bs_azimuth: 45 + ########################################################################### + # NTN space station elevation [degree] + bs_elevation: 45 + ########################################################################### + # number of sectors [degree] + num_sectors: 19 + ########################################################################### + # Conducted power per element [dBm/bandwidth] + bs_conducted_power: 37 + ########################################################################### + # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2 + bs_backoff_power: 3 + ########################################################################### + # NTN Antenna configuration + bs_n_rows_layer1 : 2 + bs_n_columns_layer1: 2 + bs_n_rows_layer2 : 4 + bs_n_columns_layer2: 2 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model: BEAMFORMING + # Base station parameters + bs: + ########################################################################### + # The load probability (or activity factor) models the statistical + # variation of the network load by defining the number of fully loaded + # base stations that are simultaneously transmitting + load_probability: .2 + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power: 11.1 + ########################################################################### + # Base station height [m] + height: 6.1 + ########################################################################### + # Base station noise figure [dB] + noise_figure: 10.1 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss: 3.1 + # Base Station Antenna parameters: + antenna: + ########################################################################### + # If normalization of M2101 should be applied for BS + normalization: FALSE + ########################################################################### + # 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: F1336 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt: 6 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g: 5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 65 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 65 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 8 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 30 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + + ########################################################################### + # User Equipment parameters: + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k: 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m: 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent: 5 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type: ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance: UNIFORM + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth: NORMAL + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control: ON + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch: -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha: 1 + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax: 22 + ########################################################################### + # UE power dynamic range [dB] + # The minimum transmit power of a UE is (p_cmax - dynamic_range) + power_dynamic_range: 63 + ########################################################################### + # UE height [m] + height: 1.5 + ########################################################################### + # User equipment noise figure [dB] + noise_figure: 10 + ########################################################################### + # User equipment feed loss [dB] + ohmic_loss: 3 + ########################################################################### + # User equipment body loss [dB] + body_loss: 4 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for UE + normalization: FALSE + ########################################################################### + # File to be used in the UE beamforming normalization + # Normalization files can be generated with the + # antenna/beamforming_normalization/normalize_script.py script + normalization_file: antenna/beamforming_normalization/ue_norm.npz + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern: F1336 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain: -200 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g: 5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db: 90 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db: 90 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows: 4 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns: 4 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing: 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing: 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am: 25 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v: 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor: 12 + ########################################################################### + # Uplink parameters. Only needed when using uplink on imt + uplink: + ########################################################################### + # Uplink attenuation factor used in link-to-system mapping + attenuation_factor: 0.4 + ########################################################################### + # Uplink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Uplink maximum SINR of the code set [dB] + sinr_max: 22 + ########################################################################### + # Downlink parameters. Only needed when using donwlink on imt + downlink: + ########################################################################### + # Downlink attenuation factor used in link-to-system mapping + attenuation_factor: 0.6 + ########################################################################### + # Downlink minimum SINR of the code set [dB] + sinr_min: -10 + ########################################################################### + # Downlink maximum SINR of the code set [dB] + sinr_max: 30 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "CI" (close-in FS reference distance) + # "UMa" (Urban Macro - 3GPP) + # "UMi" (Urban Micro - 3GPP) + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "ABG" (Alpha-Beta-Gamma) + # "P619" + # "EHF" (Milimetric-wave) + channel_model: EHF + 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 +fss_ss: + ########################################################################### + # satellite center frequency [MHz] + frequency: 43000 + ########################################################################### + # satellite bandwidth [MHz] + bandwidth: 200 + ########################################################################### + # satellite altitude [m] and latitude [deg] + altitude: 35780000 + lat_deg: 10 + ########################################################################### + # Elevation angle [deg] + elevation: 270 + ########################################################################### + # Azimuth angle [deg] + azimuth: 0 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -5 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 950 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity: 0 + ########################################################################### + # Antenna pattern of the FSS space station + # Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" + antenna_pattern: FSS_SS + ########################################################################### + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s: -20 + ########################################################################### + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB: 0.65 + ########################################################################### + # Satellite peak receive antenna gain [dBi] + antenna_gain: 46.6 + ########################################################################### + # Paramters for the Antenna ITU-R S.1528 + antenna_s1528: + # Antenna pattern according to ITU-R-S.1528. Possible values are + # "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", "ITU-R-S.1528-Taylor" + antenna_pattern: "ITU-R-S.1528-LEO" + ########################################################################### + # 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 + # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function) + n_side_lobes : 4 + # Radial and transverse sizes of the effective radiating area of the satellite transmit antenna (m). + l_r : 0.5 + l_t : 0.5 + # Roll-off factor + roll_off : 3 + ########################################################################### + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 0 + earth_station_lat_deg: 0 + earth_station_long_diff_deg: 0 + season: SUMMER + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model: P619 +fss_es: + ########################################################################### + # type of FSS-ES location: + # FIXED - position must be given + # CELL - random within central cell + # NETWORK - random within whole network + # UNIFORM_DIST - uniform distance from cluster centre, + # between min_dist_to_bs and max_dist_to_bs + location: UNIFORM_DIST + ########################################################################### + # x-y coordinates [m] (only if FIXED location is chosen) + x: 10000 + y: 0 + ########################################################################### + # minimum distance from BSs [m] + min_dist_to_bs: 10 + ########################################################################### + # maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen) + max_dist_to_bs: 600 + ########################################################################### + # antenna height [m] + height: 6 + ########################################################################### + # Elevation angle [deg], minimum and maximum, values + elevation_min: 48 + elevation_max: 80 + ########################################################################### + # Azimuth angle [deg] + # either a specific angle or string 'RANDOM' + azimuth: RANDOM + ########################################################################### + # center frequency [MHz] + frequency: 43000 + ########################################################################### + # bandwidth [MHz] + bandwidth: 6 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 950 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity: 0 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -68.3 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain: 32 + ########################################################################### + # Antenna pattern of the FSS Earth station + # Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", + # "Modified ITU-R S.465" + antenna_pattern: Modified ITU-R S.465 + ########################################################################### + # Diameter of antenna [m] + diameter: 1.8 + ########################################################################### + # Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model + antenna_envelope_gain: 0 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + # "P452" + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "HDFSS" + # "UMa" + # "UMi" + channel_model: P452 + ########################################################################### + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 + ########################################################################### + # P452 parameters + ########################################################################### + # Total air pressure in hPa + atmospheric_pressure: 935 + ########################################################################### + # Temperature in Kelvin + air_temperature: 300 + ########################################################################### + #Sea-level surface refractivity (use the map) + N0: 352.58 + ########################################################################### + #Average radio-refractive (use the map) + delta_N: 43.127 + ########################################################################### + #percentage p. Float (0 to 100) or RANDOM + percentage_p: 0.2 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dct: 70 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dcr: 70 + ########################################################################### + ##Effective height of interfering antenna (m) + Hte: 20 + ########################################################################### + #Effective height of interfered-with antenna (m) + Hre: 3 + ########################################################################### + ##Latitude of transmitter + tx_lat: -23.55028 + ########################################################################### + #Latitude of receiver + rx_lat: -23.17889 + ########################################################################### + #Antenna polarization + polarization: horizontal + ########################################################################### + #determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: TRUE + ########################################################################### + # HDFSS propagation parameters + ########################################################################### + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: ROOFTOP + ########################################################################### + # Enable shadowing loss + shadow_enabled: TRUE + ########################################################################### + # Enable building entry loss + building_loss_enabled: TRUE + ########################################################################### + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: FALSE + ########################################################################### + # Enable diffraction loss + diffraction_enabled: TRUE + ########################################################################### + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: P2109_FIXED + ########################################################################### + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: 0.75 + ########################################################################### + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: 35 +fs: + ########################################################################### + # x-y coordinates [m] + x: 1000 + y: 0 + ########################################################################### + # antenna height [m] + height: 15 + ########################################################################### + # Elevation angle [deg] + elevation: -10 + ########################################################################### + # Azimuth angle [deg] + azimuth: 180 + ########################################################################### + # center frequency [MHz] + frequency: 27250 + ########################################################################### + # bandwidth [MHz] + bandwidth: 112 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 290 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity: 20 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -68.3 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain: 36.9 + ########################################################################### + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R F.699", "OMNI" + antenna_pattern: ITU-R F.699 + ########################################################################### + # Diameter of antenna [m] + diameter: 0.3 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + channel_model: FSPL + ########################################################################### + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 +haps: + ########################################################################### + # HAPS center frequency [MHz] + frequency: 27250 + ########################################################################### + # HAPS bandwidth [MHz] + bandwidth: 200 + ########################################################################### + # HAPS altitude [m] and latitude [deg] + altitude: 20000 + lat_deg: 0 + ########################################################################### + # Elevation angle [deg] + elevation: 270 + ########################################################################### + # Azimuth angle [deg] + azimuth: 0 + ########################################################################### + # EIRP spectral density [dBW/MHz] + eirp_density: 4.4 + ########################################################################### + # HAPS peak antenna gain [dBi] + antenna_gain: 28.1 + ########################################################################### + # Adjacent channel selectivity [dB] + acs: 30 + ########################################################################### + # Antenna pattern of the HAPS (airbone) station + # Possible values: "ITU-R F.1891", "OMNI" + antenna_pattern: ITU-R F.1891 + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 0 + earth_station_lat_deg: 0 + earth_station_long_diff_deg: 0 + season: SUMMER + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model: P619 + ########################################################################### + # Near side-lobe level (dB) relative to the peak gain required by the system + # design, and has a maximum value of โˆ’25 dB + antenna_l_n: -25 + ########################################################################### + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 +rns: + ########################################################################### + # x-y coordinates [m] + x: 660 + y: -370 + ########################################################################### + # altitude [m] + altitude: 150 + ########################################################################### + # center frequency [MHz] + frequency: 32000 + ########################################################################### + # bandwidth [MHz] + bandwidth: 60 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 1154 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -70.79 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain: 30 + ########################################################################### + # Adjacent channel selectivity [dB] + acs: 30 + ########################################################################### + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R M.1466", "OMNI" + antenna_pattern: ITU-R M.1466 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 dB + clutter loss) + # "P619" + channel_model: P619 + ########################################################################### + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 0 + earth_station_lat_deg: 0 + earth_station_long_diff_deg: 0 + season: SUMMER + ########################################################################### + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 +ras: + ########################################################################### + # x-y coordinates [m] + x: 81000 + y: 0 + ########################################################################### + # antenna height [m] + height: 15 + ########################################################################### + # Elevation angle [deg] + elevation: 45 + ########################################################################### + # Azimuth angle [deg] + azimuth: -90 + ########################################################################### + # center frequency [MHz] + frequency: 43000 + ########################################################################### + # bandwidth [MHz] + bandwidth: 1000 + ########################################################################### + # Antenna noise temperature [K] + antenna_noise_temperature: 25 + ########################################################################### + # Receiver noise temperature [K] + receiver_noise_temperature: 65 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity: 20 + ########################################################################### + # Antenna efficiency + antenna_efficiency: 1 + ########################################################################### + # Antenna pattern of the FSS Earth station + # Possible values: "ITU-R SA.509", "OMNI" + antenna_pattern: ITU-R SA.509 + ########################################################################### + # Antenna gain for "OMNI" pattern + antenna_gain: 0 + ########################################################################### + # Diameter of antenna [m] + diameter: 15 + ########################################################################### + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 + SPEED_OF_LIGHT: 299792458 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + # "P452" + channel_model: P452 + ########################################################################### + # P452 parameters + ########################################################################### + # Total air pressure in hPa + atmospheric_pressure: 935 + ########################################################################### + # Temperature in Kelvin + air_temperature: 300 + ########################################################################### + #Sea-level surface refractivity (use the map) + N0: 352.58 + ########################################################################### + #Average radio-refractive (use the map) + delta_N: 43.127 + ########################################################################### + #percentage p. Float (0 to 100) or RANDOM + percentage_p: 0.2 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dct: 70 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dcr: 70 + ########################################################################### + ##Effective height of interfering antenna (m) + Hte: 20 + ########################################################################### + #Effective height of interfered-with antenna (m) + Hre: 3 + ########################################################################### + ##Latitude of transmitter + tx_lat: -23.55028 + ########################################################################### + #Latitude of receiver + rx_lat: -23.17889 + ########################################################################### + #Antenna polarization + polarization: horizontal + ########################################################################### + #determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: TRUE + # Parameters for the P.619 propagation model + # Used between IMT space station and another terrestrial system. + # 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 heigh + space_station_alt_m: 20000.0 + # Enter the RAS antenna heigh + earth_station_alt_m: 15.0 + # 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 +eess_ss: + ########################################################################### + # sensor center frequency [MHz] + frequency: 23900 + ########################################################################### + # sensor bandwidth [MHz] + bandwidth: 200 + ###########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: 46.6 + ########################################################################### + # sensor altitude [m] + altitude: 828000 + ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "OMNI" + antenna_pattern: ITU-R RS.1813 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency: 0.6 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: 2.2 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: 52 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: FSPL + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 0 + earth_station_lat_deg: 0 + earth_station_long_diff_deg: 0 + season: "SUMMER" +metsat_ss: + ########################################################################### + # sensor center frequency [MHz] + frequency: 8195 + ########################################################################### + # sensor bandwidth [MHz] + bandwidth: 20 + ###########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 + ########################################################################### + # Elevation angle [deg] + elevation: 3 + ########################################################################### + # sensor altitude [m] + altitude: 35786000 + ########################################################################### + # Antenna pattern of the sensor + antenna_pattern: ITU-R S.672 + ########################################################################### + # receive antenna gain [dBi] + antenna_gain: 52 + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s: -20.0 + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB: 0.65 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model: FSPL + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: 0 + earth_station_lat_deg: 0 + earth_station_long_diff_deg: 0 + season: "SUMMER" + # Constants + BOLTZMANN_CONSTANT: 1.38064852e-23 + EARTH_RADIUS: 6371000 + + + +single_earth_station: + ########################################################################### + # Sensor center frequency [MHz] + frequency: 7000 + ########################################################################### + # Sensor bandwidth [MHz] + bandwidth: 20 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 300 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -65 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 6 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + ########################################################################### + # Type of azimuth. May be "UNIFORM_DIST", "FIXED" + type: FIXED + ########################################################################### + # Value of azimuth when type == "FIXED" [deg] + fixed: 0 + ########################################################################### + # Limits of random uniform distribution when type == "UNIFORM_DIST" + uniform_dist: + ########################################################################### + # uniform distribution lower bound [deg] + min: -180 + ########################################################################### + # uniform distribution upper bound [deg] + max: 180 + ########################################################################### + # Elevation angle [degrees] + elevation: + # Type of elevation. May be "UNIFORM_DIST", "FIXED" + type: FIXED + # Value of elevation when type == "FIXED" [deg] + fixed: 60 + # Limits of random uniform distribution when type == "UNIFORM_DIST" + uniform_dist: + # uniform distribution lower bound [deg] + min: 30 + # uniform distribution upper bound [deg] + max: 65 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + # choose way to set location. May be "CELL", "NETWORK", "UNIFORM_DIST" or "FIXED" + # location.type == CELL means that the ES will be distributed randomly, + # but always in the central cell + # location.type == NETWORK means that the ES will be distributed randomly, + # in a random cell + # location.type == UNIFORM_DIST means that the ES will be distributed randomly, + # in a ring shaped section with delimited outer and inner radius + # location.type == FIXED means that the ES will be always at SAME fixed location + type: CELL + ########################################################################### + # Value of position (x,y) when type == "FIXED" [(m, m)] + fixed: + x: 10 + y: 100 + cell: + ########################################################################### + # Minimum distance to IMT BS when location.type == CELL [m] + min_dist_to_bs: 100 + network: + ########################################################################### + # Minimum distance to IMT BS when location.type == NETWORK [m] + min_dist_to_bs: 150 + uniform_dist: + ########################################################################### + # Ring inner radius [m] + min_dist_to_center: 101 + ########################################################################### + # Ring outer radius [m] + max_dist_to_center: 102 + antenna: + ########################################################################### + # Choose the antenna pattern. Can be one of: + # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855" + pattern: ITU-R S.465 + ########################################################################### + # Earth station peak receive antenna gain [dBi] + gain: 28 + itu_r_f_699: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_465: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_580: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_1855: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_465_modified: + ########################################################################### + # Antenna envelope gain [dBi] + envelope_gain: -4 + ########################################################################### + # Selected channel model + # Possible values are "P619", "P452", "TerrestrialSimple" + channel_model: "P452" + + ########################################################################### + # Parameters for P619 channel model + param_p619: + ########################################################################### + # altitude of the space station [m] + space_station_alt_m: 540000 + ########################################################################### + # altitude of ES system [m] + earth_station_alt_m: 1200 + ########################################################################### + # latitude of ES system [m] + earth_station_lat_deg: 13 + ########################################################################### + # difference between longitudes of IMT-NTN station and FSS-ES system [deg] + # (positive if space-station is to the East of earth-station) + earth_station_long_diff_deg: 10 + + ########################################################################### + # season - season of the year. + # Only actually useful for P619, but we can't put it inside param_p619 without changing more code + season: SUMMER + + ########################################################################### + # Parameters for P452 channel model + param_p452: + ########################################################################### + # Total air pressure [hPa] + atmospheric_pressure: 1 + ########################################################################### + # Temperature [K] + air_temperature: 2 + ########################################################################### + # Sea-level surface refractivity (use the map) + N0: 3 + ########################################################################### + # Average radio-refractive (use the map) + delta_N: 4 + ########################################################################### + # percentage p. Float (0 to 100) or RANDOM + percentage_p: 5 + ########################################################################### + # Distance over land from the transmit and receive antennas to the coast (km) + Dct: 6 + ########################################################################### + # Distance over land from the transmit and receive antennas to the coast (km) + Dcr: 7 + ########################################################################### + # Effective height of interfering antenna (m) + Hte: 8 + ########################################################################### + # Effective height of interfered-with antenna (m) + Hre: 9 + ########################################################################### + # Latitude of transmitter + tx_lat: 10 + ########################################################################### + # Latitude of receiver + rx_lat: 11 + ########################################################################### + # Antenna polarization. Possible values are "horizontal", "vertical" + polarization: horizontal + ########################################################################### + # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: TRUE + + # HDFSS propagation parameters + param_hdfss: + ########################################################################### + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: BUILDINGSIDE + ########################################################################### + # Enable shadowing loss + shadow_enabled: FALSE + ########################################################################### + # Enable building entry loss + building_loss_enabled: FALSE + ########################################################################### + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: TRUE + ########################################################################### + # Enable diffraction loss + diffraction_enabled: FALSE + ########################################################################### + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: FIXED_VALUE + ########################################################################### + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: 0.19 + ########################################################################### + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: 35 + + ########################################################################### + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: BUILDINGSIDE + ########################################################################### + # Enable shadowing loss + shadow_enabled: TRUE + ########################################################################### + # Enable building entry loss + building_loss_enabled: TRUE + ########################################################################### + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: FALSE + ########################################################################### + # Enable diffraction loss + diffraction_enabled: FALSE + ########################################################################### + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: P2109_FIXED + ########################################################################### + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: 0.75 + ########################################################################### + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: 35 + diff --git a/sharc/main.py b/sharc/main.py index 5e610222c..299a6105c 100644 --- a/sharc/main.py +++ b/sharc/main.py @@ -5,28 +5,28 @@ @author: edgar """ +from sharc.support.logging import Logging +from sharc.controller import Controller +from sharc.gui.view import View +from sharc.model import Model import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "..")) - -from sharc.model import Model -from sharc.gui.view import View -from sharc.controller import Controller -from sharc.support.logging import Logging + def main(): Logging.setup_logging() - + model = Model() view = View() controller = Controller() - + view.set_controller(controller) controller.set_model(model) model.add_observer(view) - + view.mainloop() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/sharc/main_cli.py b/sharc/main_cli.py index cfe9241b1..0f019f92a 100644 --- a/sharc/main_cli.py +++ b/sharc/main_cli.py @@ -4,20 +4,23 @@ @author: edgar """ - -import sys, getopt +import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "..")) - -from sharc.model import Model -from sharc.gui.view_cli import ViewCli -from sharc.controller import Controller from sharc.support.logging import Logging +from sharc.controller import Controller +from sharc.gui.view_cli import ViewCli +from sharc.model import Model + +import getopt + + + def main(argv): print("Welcome to SHARC!\n") - + param_file = '' try: @@ -27,7 +30,7 @@ def main(argv): sys.exit(2) if not opts: - param_file = os.path.join(os.getcwd(), "input", "parameters.ini") + param_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "input", "parameters.yaml") else: for opt, arg in opts: if opt == "-h": @@ -35,20 +38,19 @@ def main(argv): sys.exit() elif opt == "-p": param_file = param_file = os.path.join(os.getcwd(), arg) - + Logging.setup_logging() - + model = Model() view_cli = ViewCli() controller = Controller() - + view_cli.set_controller(controller) controller.set_model(model) model.add_observer(view_cli) - - view_cli.initialize(param_file) + view_cli.initialize(param_file) if __name__ == "__main__": - main(sys.argv[1:]) \ No newline at end of file + main(sys.argv[1:]) diff --git a/sharc/mask/spectral_mask.py b/sharc/mask/spectral_mask.py index 3e74a055b..f1355ab2c 100644 --- a/sharc/mask/spectral_mask.py +++ b/sharc/mask/spectral_mask.py @@ -8,18 +8,24 @@ from abc import ABC, abstractmethod import numpy as np + class SpectralMask(ABC): + def __init__(self) -> None: + self.mask_dbm = None + self.freq_lim = None + self.p_tx = None + @abstractmethod - def set_mask(self, p_tx = 0): + def set_mask(self, p_tx=0): pass - def power_calc(self,center_f: float, band: float): + def power_calc(self, center_f: float, band: float): """ - Calculates out-of-band power in the given band. It does that by + Calculates out-of-band power in the given band. It does that by dividing the band into the rectangular sections defined by the spectral mask and adding up the area of all the rectangles. - + Parameters: center_f (float): center frequency of band in which out-of-band power is to be calculated @@ -27,17 +33,21 @@ def power_calc(self,center_f: float, band: float): be calculated """ # Limit delta f: edges of band - df_min = center_f - band/2 - df_max = center_f + band/2 + df_min = center_f - band / 2 + df_max = center_f + band / 2 # Power in mW - power_oob = 0 # Out-of-band power + power_oob = 0 # Out-of-band power # Included delta f values: values of spectral mask delta f break limist # which are contained within the band - inc_df = np.where(np.logical_and(self.freq_lim > df_min, - self.freq_lim < df_max))[0] - + inc_df = np.where( + np.logical_and( + self.freq_lim > df_min, + self.freq_lim < df_max, + ), + )[0] + # If no break limits are within band: the band does not need to be # divided into rectangles if len(inc_df) == 0: @@ -45,33 +55,34 @@ def power_calc(self,center_f: float, band: float): # Define what is the power emission level at that frequency msk = self.mask_dbm[np.where(self.freq_lim >= df_max)] # If df_max is below smallest break limit - if len(msk) == 0: msk = np.array([self.mask_dbm[-1]]) + if len(msk) == 0: + msk = np.array([self.mask_dbm[-1]]) # Turn it into scalar pwr_lvl = msk[0] - if pwr_lvl != self.p_tx: power_oob += band*np.power(10,(pwr_lvl/10)) + if pwr_lvl != self.p_tx: + power_oob += band * np.power(10, (pwr_lvl / 10)) # If one or more break limitas are within band else: - + # Lower and upper power emission levels pwr_lvl_1 = self.mask_dbm[inc_df[0]] pwr_lvl_2 = self.mask_dbm[inc_df[-1] + 1] # Upper and lower rectangles if pwr_lvl_1 != self.p_tx: - power_oob += (self.freq_lim[inc_df[0]] - df_min)*\ - np.power(10,(pwr_lvl_1/10)) + power_oob += (self.freq_lim[inc_df[0]] - df_min) *\ + np.power(10, (pwr_lvl_1 / 10)) if pwr_lvl_2 != self.p_tx: - power_oob += (df_max - self.freq_lim[inc_df[-1]])*\ - np.power(10,(pwr_lvl_2/10)) + power_oob += (df_max - self.freq_lim[inc_df[-1]]) *\ + np.power(10, (pwr_lvl_2 / 10)) # Middle rectangles for df in inc_df[0:-1]: pwr_lvl = self.mask_dbm[df + 1] if pwr_lvl != self.p_tx: - power_oob += (self.freq_lim[df + 1] - self.freq_lim[df])*\ - np.power(10,(pwr_lvl/10)) - - return 10*np.log10(power_oob) + power_oob += (self.freq_lim[df + 1] - self.freq_lim[df]) *\ + np.power(10, (pwr_lvl / 10)) + return 10 * np.log10(power_oob) diff --git a/sharc/mask/spectral_mask_3gpp.py b/sharc/mask/spectral_mask_3gpp.py index c03d55527..b98ccf34c 100644 --- a/sharc/mask/spectral_mask_3gpp.py +++ b/sharc/mask/spectral_mask_3gpp.py @@ -11,22 +11,25 @@ import numpy as np import sys + class SpectralMask3Gpp(SpectralMask): - def __init__(self, - sta_type: StationType, - freq_mhz: float, - band_mhz: float, - spurious_emissions: float, - scenario = "OUTDOOR"): + def __init__( + self, + sta_type: StationType, + freq_mhz: float, + band_mhz: float, + spurious_emissions: float, + scenario="OUTDOOR", + ): """ - Implements spectral emission mask from 3GPP 36.104 Table 6.6.3.1-6 for + Implements spectral emission mask from 3GPP 36.104 Table 6.6.3.1-6 for Wide Area BS operating with 5, 10, 15 or 20 MHz channel bandwidth. - - Also implements spectral emission mask from 3GPP 36.101 Table 6.6.2.1.1-1 + + Also implements spectral emission mask from 3GPP 36.101 Table 6.6.2.1.1-1 for UE operating with 5, 10, 15 or 20 MHz channel bandwidth. - - In order to characterize Cat-A or Cat-B base stations, it is necessary + + In order to characterize Cat-A or Cat-B base stations, it is necessary to adjust the input parameter that defines the spurious emission level: Cat-A: -13 dBm/MHz Cat-B: -30 dBm/MHz @@ -35,11 +38,11 @@ def __init__(self, message = "ERROR\nInvalid station type: " + str(sta_type) sys.stderr.write(message) sys.exit(1) - - if band_mhz not in [ 5, 10, 15, 20 ]: - message = "ERROR\nInvalid bandwidth for 3GPP mask: " + band_mhz + + if band_mhz not in [5, 10, 15, 20]: + message = f"ERROR\nInvalid bandwidth for 3GPP mask: {band_mhz}" sys.stderr.write(message) - sys.exit(1) + sys.exit(1) # Attributes self.spurious_emissions = spurious_emissions @@ -47,28 +50,31 @@ def __init__(self, self.scenario = scenario self.band_mhz = band_mhz self.freq_mhz = freq_mhz - + delta_f_lim = self.get_frequency_limits(self.sta_type, self.band_mhz) - #delta_f_lim_flipped = np.flip(self.delta_f_lim,0) + # delta_f_lim_flipped = np.flip(self.delta_f_lim,0) delta_f_lim_flipped = delta_f_lim[::-1] - - self.freq_lim = np.concatenate(((self.freq_mhz - self.band_mhz/2) - delta_f_lim_flipped, - (self.freq_mhz + self.band_mhz/2) + delta_f_lim)) - - - def get_frequency_limits(self, - sta_type : StationType, - bandwidth : float) -> np.array: + + self.freq_lim = np.concatenate(( + (self.freq_mhz - self.band_mhz / 2) - delta_f_lim_flipped, + (self.freq_mhz + self.band_mhz / 2) + delta_f_lim, + )) + + def get_frequency_limits( + self, + sta_type: StationType, + bandwidth: float, + ) -> np.array: """ Calculates the frequency limits of the spectrum emission masks. This implementation is valid only for bandwidths equal to 5, 10, 15 or 20 MHz. """ - + if sta_type is StationType.IMT_BS: # Mask delta f breaking limits [MHz] delta_f_lim = np.arange(0, 5.1, .1) delta_f_lim = np.append(delta_f_lim, 10) - else: + else: delta_f_lim = np.array([0, 1, 5]) if bandwidth == 5: delta_f_lim = np.append(delta_f_lim, np.array([6, 10])) @@ -79,28 +85,34 @@ def get_frequency_limits(self, else: delta_f_lim = np.append(delta_f_lim, np.array([20, 25])) return delta_f_lim - - - def set_mask(self, power = 0): - emission_limits = self.get_emission_limits(self.sta_type, - self.band_mhz, - self.spurious_emissions) - self.p_tx = power - 10 * np.log10(self.band_mhz) - #emission_limits = np.flip(emission_limits, 0) - emission_limits_flipped = emission_limits[::-1] - self.mask_dbm = np.concatenate((emission_limits_flipped, - np.array([self.p_tx]), - emission_limits)) + def set_mask(self, p_tx=0): + emission_limits = self.get_emission_limits( + self.sta_type, + self.band_mhz, + self.spurious_emissions, + ) + self.p_tx = p_tx - 10 * np.log10(self.band_mhz) + # emission_limits = np.flip(emission_limits, 0) + emission_limits_flipped = emission_limits[::-1] + self.mask_dbm = np.concatenate(( + emission_limits_flipped, + np.array([self.p_tx]), + emission_limits, + )) - def get_emission_limits(self, - sta_type : StationType, - bandwidth : float, - spurious_emissions : float) -> np.array: + def get_emission_limits( + self, + sta_type: StationType, + bandwidth: float, + spurious_emissions: float, + ) -> np.array: if sta_type is StationType.IMT_BS: # emission limits in dBm/MHz emission_limits = 3 - 7 / 5 * (np.arange(.05, 5, .1) - .05) - emission_limits = np.append(emission_limits, np.array([-4, spurious_emissions])) + emission_limits = np.append( + emission_limits, np.array([-4, spurious_emissions]), + ) else: if bandwidth == 5: limit_r1 = np.array([-15]) @@ -110,6 +122,8 @@ def get_emission_limits(self, limit_r1 = np.array([-20]) else: limit_r1 = np.array([-21]) - emission_limits = np.append(limit_r1 + 10*np.log10(1/0.03), - np.array([-10, -13, -25, spurious_emissions])) - return emission_limits \ No newline at end of file + emission_limits = np.append( + limit_r1 + 10 * np.log10(1 / 0.03), + np.array([-10, -13, -25, spurious_emissions]), + ) + return emission_limits diff --git a/sharc/mask/spectral_mask_imt.py b/sharc/mask/spectral_mask_imt.py index 71c807dca..86bd6872e 100644 --- a/sharc/mask/spectral_mask_imt.py +++ b/sharc/mask/spectral_mask_imt.py @@ -11,17 +11,20 @@ import numpy as np import matplotlib.pyplot as plt + class SpectralMaskImt(SpectralMask): """ - Implements spectral masks for IMT-2020 according to Document 5-1/36-E. - The masks are in the document's tables 1 to 8. - + Implements spectral masks for IMT-2020 according to Document 5-1/36-E. + The masks are in the document's tables 1 to 8. + Implements alternative spectral masks for IMT-2020 for cases Document 5-1/36-E does not implement + according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0. + Uses alternative when: outdoor BS's with freq < 24.25GHz + Attributes: spurious_emissions (float): level of power emissions at spurious - domain [dBm/MHz]. - delta_f_lin (np.array): mask delta f breaking limits in MHz. Delta f - values for which the spectral mask changes value. Hard coded as - [0, 20, 400]. In this context, delta f is the frequency distance to + domain [dBm/MHz]. + delta_f_lin (np.array): mask delta f breaking limits in MHz. Delta f + values for which the spectral mask changes value. In this context, delta f is the frequency distance to the transmission's edge frequencies freq_lim (no.array): frequency values for which the spectral mask changes emission value @@ -32,115 +35,267 @@ class SpectralMaskImt(SpectralMask): scenario (str): INDOOR or OUTDOOR scenario p_tx (float): station's transmit power in dBm/MHz mask_dbm (np.array): spectral mask emission values in dBm + alternative_mask_used (bool): represents whether the alternative mask should be used or not + ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE (int): A hardcoded value that specifies how many samples of a diagonal + line may be taken. Is needed because oob_power is calculated expecting rectangles, so we approximate + the diagonal with 'SAMPLESIZE' rectangles """ - def __init__(self, - sta_type: StationType, - freq_mhz: float, - band_mhz: float, - spurious_emissions : float, - scenario = "OUTDOOR"): + ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE: int = 40 + + def __init__( + self, + sta_type: StationType, + freq_mhz: float, + band_mhz: float, + spurious_emissions: float, + scenario="OUTDOOR", + ): """ Class constructor. - + Parameters: sta_type (StationType): type of station to which consider the spectral mask. Possible values are StationType.IMT_BS and StationType. IMT_UE freq_mhz (float): center frequency of station in MHz band_mhs (float): transmitting bandwidth of station in MHz - spurious_emissions (float): level of spurious emissions [dBm/MHz]. + spurious_emissions (float): level of spurious emissions [dBm/MHz]. scenario (str): INDOOR or OUTDOOR scenario """ # Spurious domain limits [dBm/MHz] self.spurious_emissions = spurious_emissions + + # conditions to use alternative mask + self.alternative_mask_used = freq_mhz < 24250 \ + and scenario != "INDOOR" \ + and sta_type == StationType.IMT_BS \ + and spurious_emissions in [-13, -30] + # Mask delta f breaking limits [MHz] - self.delta_f_lim = np.array([0, 20, 400]) - + if self.alternative_mask_used: + self.delta_f_lim = self.get_alternative_mask_delta_f_lim( + freq_mhz, band_mhz, + ) + else: + # use value from 5-1/36-E + self.delta_f_lim = np.array([0, 20, 400]) + # Attributes self.sta_type = sta_type self.scenario = scenario self.band_mhz = band_mhz self.freq_mhz = freq_mhz - - self.freq_lim = np.concatenate(((freq_mhz - band_mhz/2)-self.delta_f_lim[::-1], - (freq_mhz + band_mhz/2)+self.delta_f_lim)) - - - def set_mask(self, power = 0): + + self.freq_lim = np.concatenate(( + (freq_mhz - band_mhz / 2) - self.delta_f_lim[::-1], + (freq_mhz + band_mhz / 2) + self.delta_f_lim, + )) + + def set_mask(self, p_tx=0): """ - Sets the spectral mask (mask_dbm attribute) based on station type, + Sets the spectral mask (mask_dbm attribute) based on station type, operating frequency and transmit power. - + Parameters: - power (float): station transmit power. Default = 0 + p_tx (float): station transmit power. Default = 0 """ - self.p_tx = power - 10*np.log10(self.band_mhz) - - # Set new transmit power value + if self.alternative_mask_used: + self.mask_dbm = self.get_alternative_mask_mask_dbm(p_tx) + return + + self.p_tx = p_tx - 10 * np.log10(self.band_mhz) + + # Set new transmit power value if self.sta_type is StationType.IMT_UE: # Table 8 mask_dbm = np.array([-5, -13, self.spurious_emissions]) - - elif self.sta_type is StationType.IMT_BS and self.scenario is "INDOOR": + + elif self.sta_type is StationType.IMT_BS and self.scenario == "INDOOR": # Table 1 mask_dbm = np.array([-5, -13, self.spurious_emissions]) - + else: - + if (self.freq_mhz > 24250 and self.freq_mhz < 33400): - if power >= 34.5: + if p_tx >= 34.5: # Table 2 mask_dbm = np.array([-5, -13, self.spurious_emissions]) else: # Table 3 - mask_dbm = np.array([-5, np.max((power-47.5,-20)), - self.spurious_emissions]) + mask_dbm = np.array([ + -5, np.max((p_tx - 47.5, -20)), + self.spurious_emissions, + ]) elif (self.freq_mhz > 37000 and self.freq_mhz < 52600): - if power >= 32.5: + if p_tx >= 32.5: # Table 4 mask_dbm = np.array([-5, -13, self.spurious_emissions]) else: # Table 5 - mask_dbm = np.array([-5, np.max((power-45.5,-20)), - self.spurious_emissions]) + mask_dbm = np.array([ + -5, np.max((p_tx - 45.5, -20)), + self.spurious_emissions, + ]) elif (self.freq_mhz > 66000 and self.freq_mhz < 86000): - if power >= 30.5: + if p_tx >= 30.5: # Table 6 mask_dbm = np.array([-5, -13, self.spurious_emissions]) else: # Table 7 - mask_dbm = np.array([-5, np.max((power-43.5,-20)), - self.spurious_emissions]) + mask_dbm = np.array([ + -5, np.max((p_tx - 43.5, -20)), + self.spurious_emissions, + ]) else: - # Dummy spectral mask, for testing purposes only - mask_dbm = np.array([-10, -20, -50]) - - self.mask_dbm = np.concatenate((mask_dbm[::-1],np.array([self.p_tx]), - mask_dbm)) - + # this will only be reached when spurious emission has been manually set to something invalid and + # alternative mask should be used + raise ValueError( + "SpectralMaskIMT cannot be used with current parameters. \ + You may have set spurious emission to a value not in [-13,-30]", + ) + + self.mask_dbm = np.concatenate(( + mask_dbm[::-1], np.array([self.p_tx]), + mask_dbm, + )) + + def get_alternative_mask_delta_f_lim(self, freq_mhz: float, band_mhz: float) -> np.array: + """ + Implements spectral masks for IMT-2020 outdoor BS's when freq < 26GHz, + according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0. + Reference tables are: + - Table 1 in ITU-R SM.1541-6 + - Table 2 in ITU-R SM.1539-1 + - Table 6.6.4.2.1-2 in ETSI TS 138 104 V16.6.0 + - Table 6.6.4.2.2.1-2 in ETSI TS 138 104 V16.6.0 + - Table 6.6.5.2.1-1 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table) + - Table 6.6.5.2.1-2 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table) + """ + # ITU-R SM.1539-1 Table 2 + if (freq_mhz > 0.009 and freq_mhz <= 0.15): + B_L = 0.00025 + B_U = 0.01 + elif (freq_mhz > 0.15 and freq_mhz <= 30): + B_L = 0.004 + B_U = 0.1 + elif (freq_mhz > 30 and freq_mhz <= 1000): + B_L = 0.025 + B_U = 10 + elif (freq_mhz > 1000 and freq_mhz <= 3000): + B_L = 0.1 + B_U = 50 + elif (freq_mhz > 3000 and freq_mhz <= 10000): + B_L = 0.1 + B_U = 100 + elif (freq_mhz > 10000 and freq_mhz <= 15000): + B_L = 0.3 + B_U = 250 + elif (freq_mhz > 15000 and freq_mhz <= 26000): + B_L = 0.5 + B_U = 500 + else: + raise ValueError(f"Invalid frequency value {freq_mhz} for mask ITU-R SM.1539-1") + + # ITU-R SM.1541-6 Table 1 (same as using only ITU-R SM.1539-1 Table 2, but with less hardcoded values) + B_L_separation = 2.5 * B_L + B_U_separation = 1.5 * band_mhz + B_U + B_N_separation = 2.5 * band_mhz + + if band_mhz < B_L: + delta_f_spurious = B_L_separation + elif band_mhz > B_U: + delta_f_spurious = B_U_separation + else: + delta_f_spurious = B_N_separation + + diagonal = np.array([ + i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE for i in range( + self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE, + ) + ]) + + # band/2 is subtracted from delta_f_spurious beacuse that specific interval is from frequency center + rest_of_oob_and_spurious = np.array( + [5, 10.0, delta_f_spurious - band_mhz / 2], + ) + + return np.concatenate((diagonal, rest_of_oob_and_spurious)) + + def get_alternative_mask_mask_dbm(self, power: float = 0) -> np.array: + """ + Implements spectral masks for IMT-2020 outdoor BS's when freq < 26GHz, + according to Documents ITU-R SM.1541-6, ITU-R SM.1539-1 and ETSI TS 138 104 V16.6.0. + Reference tables are: + - Table 1 in ITU-R SM.1541-6 + - Table 2 in ITU-R SM.1539-1 + - Table 6.6.4.2.1-2 in ETSI TS 138 104 V16.6.0 + - Table 6.6.4.2.2.1-2 in ETSI TS 138 104 V16.6.0 + - Table 6.6.5.2.1-1 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table) + - Table 6.6.5.2.1-2 in ETSI TS 138 104 V16.6.0 (to choose spurious emission, not an impementation table) + """ + self.p_tx = power - 10 * np.log10(self.band_mhz) + + if self.spurious_emissions == -13: + # use document ETSI TS 138 104 V16.6.0 Table 6.6.4.2.1-2 + diagonal_samples = np.array([ + -7 - 7 / 5 * + (i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE) + for i in range(self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE) + ]) + rest_of_oob_and_spurious = np.array([ + -14, -13, self.spurious_emissions, + ]) + mask_dbm = np.concatenate( + (diagonal_samples, rest_of_oob_and_spurious), + ) + elif self.spurious_emissions == -30: + # use document ETSI TS 138 104 V16.6.0 Table 6.6.4.2.2.1-2 + diagonal_samples = np.array([ + -7 - 7 / 5 * + (i * 5 / self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE) + for i in range(self.ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE) + ]) + rest_of_oob_and_spurious = np.array( + [-14, -15, self.spurious_emissions], + ) + mask_dbm = np.concatenate( + (diagonal_samples, rest_of_oob_and_spurious), + ) + else: + raise ValueError( + "Alternative mask should only be used for spurious emissions -13 and -30", + ) + + return np.concatenate(( + mask_dbm[::-1], np.array([self.p_tx]), + mask_dbm, + )) + + if __name__ == '__main__': # Initialize variables sta_type = StationType.IMT_BS - p_tx = 25.1 - freq = 43000 - band = 200 - + p_tx = 34.061799739838875 + freq = 9000 + band = 100 + spurious_emissions_dbm_mhz = -30 + # Create mask - msk = SpectralMaskImt(sta_type,freq,band) + msk = SpectralMaskImt(sta_type, freq, band, spurious_emissions_dbm_mhz) msk.set_mask(p_tx) - + # Frequencies - freqs = np.linspace(-600,600,num=1000)+freq - + freqs = np.linspace(-600, 600, num=1000) + freq + # Mask values - mask_val = np.ones_like(freqs)*msk.mask_dbm[0] - for k in range(len(msk.freq_lim)-1,-1,-1): + mask_val = np.ones_like(freqs) * msk.mask_dbm[0] + for k in range(len(msk.freq_lim) - 1, -1, -1): mask_val[np.where(freqs < msk.freq_lim[k])] = msk.mask_dbm[k] - + # Plot - plt.plot(freqs,mask_val) - plt.xlim([freqs[0],freqs[-1]]) - plt.xlabel("$\Delta$f [MHz]") + plt.plot(freqs, mask_val) + plt.xlim([freqs[0], freqs[-1]]) + plt.xlabel(r"$\Delta$f [MHz]") plt.ylabel("Spectral Mask [dBc]") plt.grid() plt.show() diff --git a/sharc/model.py b/sharc/model.py index 6789260b1..3c4936e6d 100644 --- a/sharc/model.py +++ b/sharc/model.py @@ -13,7 +13,7 @@ from sharc.parameters.parameters import Parameters import random -import sys + class Model(Observable): """ @@ -32,8 +32,10 @@ def add_observer(self, observer: Observer): def set_param_file(self, param_file): self.param_file = param_file - self.notify_observers(source = __name__, - message = "Loading file:\n" + self.param_file) + self.notify_observers( + source=__name__, + message="Loading file:\n" + self.param_file, + ) def initialize(self): """ @@ -45,21 +47,27 @@ def initialize(self): self.parameters.read_params() if self.parameters.general.imt_link == "DOWNLINK": - self.simulation = SimulationDownlink(self.parameters, self.param_file) + self.simulation = SimulationDownlink( + self.parameters, self.param_file, + ) else: - self.simulation = SimulationUplink(self.parameters, self.param_file) + self.simulation = SimulationUplink( + self.parameters, self.param_file, + ) self.simulation.add_observer_list(self.observers) description = self.get_description() - self.notify_observers(source=__name__, - message=description + "\nSimulation is running...", - state=State.RUNNING ) + self.notify_observers( + source=__name__, + message=description + "\nSimulation is running...", + state=State.RUNNING, + ) self.current_snapshot = 0 self.simulation.initialize() - random.seed( self.parameters.general.seed ) + random.seed(self.parameters.general.seed) self.secondary_seeds = [None] * self.parameters.general.num_snapshots @@ -72,18 +80,18 @@ def get_description(self) -> str: param_system = self.simulation.param_system description = "\nIMT:\n" \ - + "\tinterfered with: {:s}\n".format(str(self.parameters.imt.interfered_with)) \ - + "\tdirection: {:s}\n".format(self.parameters.general.imt_link) \ - + "\tfrequency: {:.3f} GHz\n".format(self.parameters.imt.frequency*1e-3) \ - + "\tbandwidth: {:.0f} MHz\n".format(self.parameters.imt.bandwidth) \ - + "\tspurious emissions: {:.0f} dBm/MHz\n".format(self.parameters.imt.spurious_emissions) \ - + "\ttopology: {:s}\n".format(self.parameters.imt.topology) \ - + "\tpath loss model: {:s}\n".format(self.parameters.imt.channel_model) \ - + "{:s}:\n".format(self.parameters.general.system) \ - + "\tfrequency: {:.3f} GHz\n".format(param_system.frequency*1e-3) \ - + "\tbandwidth: {:.0f} MHz\n".format(param_system.bandwidth) \ - + "\tpath loss model: {:s}\n".format(param_system.channel_model) \ - + "\tantenna pattern: {:s}\n".format(param_system.antenna_pattern) + + "\tinterfered with: {:s}\n".format(str(self.parameters.imt.interfered_with)) \ + + "\tdirection: {:s}\n".format(self.parameters.general.imt_link) \ + + "\tfrequency: {:.3f} GHz\n".format(self.parameters.imt.frequency * 1e-3) \ + + "\tbandwidth: {:.0f} MHz\n".format(self.parameters.imt.bandwidth) \ + + "\tspurious emissions: {:.0f} dBm/MHz\n".format(self.parameters.imt.spurious_emissions) \ + + "\ttopology: {:s}\n".format(self.parameters.imt.topology.type) \ + + "\tpath loss model: {:s}\n".format(self.parameters.imt.channel_model) \ + + "{:s}:\n".format(self.parameters.general.system) \ + + "\tfrequency: {:.3f} GHz\n".format(param_system.frequency * 1e-3) \ + + "\tbandwidth: {:.0f} MHz\n".format(param_system.bandwidth) \ + + "\tpath loss model: {:s}\n".format(param_system.channel_model) \ + + "\tantenna pattern: {:s}\n".format(param_system.antenna_pattern) return description @@ -96,12 +104,16 @@ def snapshot(self): if not self.current_snapshot % 10: write_to_file = True - self.notify_observers(source=__name__, - message="Snapshot #" + str(self.current_snapshot)) + self.notify_observers( + source=__name__, + message="Snapshot #" + str(self.current_snapshot), + ) - self.simulation.snapshot(write_to_file=write_to_file, - snapshot_number=self.current_snapshot, - seed = self.secondary_seeds[self.current_snapshot - 1]) + self.simulation.snapshot( + write_to_file=write_to_file, + snapshot_number=self.current_snapshot, + seed=self.secondary_seeds[self.current_snapshot - 1], + ) def is_finished(self) -> bool: """ @@ -122,8 +134,10 @@ def finalize(self): Finalizes the simulation and performs all post-simulation tasks """ self.simulation.finalize(snapshot_number=self.current_snapshot) - self.notify_observers(source=__name__, - message="FINISHED!", state=State.FINISHED) + self.notify_observers( + source=__name__, + message="FINISHED!", state=State.FINISHED, + ) def set_elapsed_time(self, elapsed_time: str): """ @@ -134,6 +148,8 @@ def set_elapsed_time(self, elapsed_time: str): ---------- elapsed_time: Elapsed time. """ - self.notify_observers(source=__name__, - message="Elapsed time: " + elapsed_time, - state=State.FINISHED) + self.notify_observers( + source=__name__, + message="Elapsed time: " + elapsed_time, + state=State.FINISHED, + ) diff --git a/sharc/output/__init__.py b/sharc/output/__init__.py deleted file mode 100644 index faaaf799c..000000000 --- a/sharc/output/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - - diff --git a/sharc/parameters/__init__.py b/sharc/parameters/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/parameters/__init__.py +++ b/sharc/parameters/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/parameters/antenna/parameters_antenna_s1528.py b/sharc/parameters/antenna/parameters_antenna_s1528.py new file mode 100644 index 000000000..3c14e15b1 --- /dev/null +++ b/sharc/parameters/antenna/parameters_antenna_s1528.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersAntennaS1528(ParametersBase): + """Dataclass containing the Fixed Satellite Services - Space Station + parameters for the simulator + """ + section_name: str = "S1528" + # satellite center frequency [MHz] + frequency: float = 43000.0 + # channel bandwidth - used for Taylor antenna + bandwidth: float = 500.0 + # Peak antenna gain [dBi] + antenna_gain: float = 46.6 + # Antenna pattern from ITU-R S.1528 + # Possible values: "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", "ITU-R-S.1528-Taylor" + antenna_pattern: str = "ITU-R-S.1528-LEO" + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s: float = -20.0 + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB: float = 0.65 + + # The following parameters are used for S.1528-Tayloer antenna pattern + # SLR is the side-lobe ratio of the pattern (dB), the difference in gain between the maximum + # gain and the gain at the peak of the first side lobe. + slr: float = 20.0 + # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function) + n_side_lobes: int = 4 + # beam roll-off (difference between the maximum gain and the gain at the edge of the illuminated beam) + # Possible values are 3, 5 and 7 + roll_off: int = 7 + # Radial and transverse sizes of the effective radiating area of the satellite transmit antenna (m). + l_r: float = 1.0 + l_t: float = 1.0 + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + + # Now do the sanity check for some parameters + if self.antenna_pattern.upper() not in [ParametersAntennaS1528]: + raise ValueError(f"ParametersAntennaS1528: \ + 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\"") + + if int(self.roll_off) not in [3, 5, 7]: + raise ValueError( + f"AntennaS1528Taylor: Invalid value for roll_off factor {self.roll_off}") + + def load_from_parameters(self, param: ParametersBase): + """Load from another parameter object + + Parameters + ---------- + param : ParametersBase + Parameters object containing ParametersAntennaS1528 + """ + self.antenna_gain = param.antenna_gain + self.frequency = param.frequency + self.antenna_gain = param.antenna_gain + self.antenna_pattern = param.antenna_pattern + self.antenna_l_s = param.antenna_l_s + self.antenna_3_dB = param.antenna_3_dB_bw + self.slr = param.slr + self.n_side_lobes = param.n_side_lobes + self.roll_off = param.roll_off + self.l_r = param.l_r + self.l_t = param.l_t + return self + + def set_external_parameters(self, frequency: float, bandwidth: float, antenna_gain: float, antenna_l_s: float): + """ + This method is used to "propagate" parameters from external context + to the values required by antenna S1528. + """ + self.frequency = frequency + self.bandwidth = bandwidth + self.antenna_gain = antenna_gain + self.antenna_l_s = antenna_l_s diff --git a/sharc/parameters/constants.py b/sharc/parameters/constants.py new file mode 100644 index 000000000..22f5a1075 --- /dev/null +++ b/sharc/parameters/constants.py @@ -0,0 +1,5 @@ +"""Constants definitions used thourghput the simulator +""" +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 +SPEED_OF_LIGHT = 299792458 diff --git a/sharc/parameters/imt/parameters_antenna_imt.py b/sharc/parameters/imt/parameters_antenna_imt.py new file mode 100644 index 000000000..bacf1316c --- /dev/null +++ b/sharc/parameters/imt/parameters_antenna_imt.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Apr 15 16:29:36 2017 + +@author: Calil +""" + +from sharc.support.named_tuples import AntennaPar +from numpy import load +import typing + +from dataclasses import dataclass +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersAntennaImt(ParametersBase): + """ + Defines the antenna model and related parameters to be used in compatibility + studies between IMT and other services in adjacent bands. + """ + section_name: str = "imt_antenna" + + # Normalization application flags for base station (BS) and user equipment (UE). + normalization: bool = False + + # Normalization files for BS and UE beamforming. + normalization_file: str = "antenna/beamforming_normalization/norm.npz" + + # Radiation pattern of each antenna element. + element_pattern: str = "M2101" + + # Minimum array gain for the beamforming antenna [dBi]. + minimum_array_gain: float = -200.0 + + # Mechanical downtilt [degrees]. + # PS: downtilt doesn't make sense on UE's + downtilt: float = 6.0 + + # BS/UE maximum transmit/receive element gain [dBi]. + element_max_g: float = 5.0 + + # BS/UE horizontal 3dB beamwidth of single element [degrees]. + element_phi_3db: float = 65.0 + + # BS/UE vertical 3dB beamwidth of single element [degrees]. + element_theta_3db: float = 65.0 + + # BS/UE number of rows and columns in antenna array. + n_rows: int = 8 + n_columns: int = 8 + + # BS/UE array element spacing (d/lambda). + element_horiz_spacing: float = 0.5 + element_vert_spacing: float = 0.5 + + # BS/UE front to back ratio and single element vertical sidelobe attenuation [dB]. + element_am: int = 30 + element_sla_v: int = 30 + + # Multiplication factor k used to adjust the single-element pattern. + multiplication_factor: int = 12 + + adjacent_antenna_model: typing.Literal["BEAMFORMING", "SINGLE_ELEMENT"] = None + + def load_subparameters(self, ctx: str, params: dict, quiet=True): + """ + Loads the parameters when is placed as subparameter + """ + super().load_subparameters(ctx, params, quiet) + + def set_external_parameters(self, *, adjacent_antenna_model: typing.Literal["BEAMFORMING", "SINGLE_ELEMENT"]): + self.adjacent_antenna_model = adjacent_antenna_model + + def validate(self, ctx: str): + # Additional sanity checks specific to antenna parameters can be implemented here + + # Sanity check for adjacent_antenna_model + if self.adjacent_antenna_model not in ["SINGLE_ELEMENT", "BEAMFORMING"]: + raise ValueError("adjacent_antenna_model must be 'SINGLE_ELEMENT'") + + # Sanity checks for normalization flags + if not isinstance(self.normalization, bool): + raise ValueError("normalization must be a boolean value") + + # Sanity checks for element patterns + if self.element_pattern.upper() not in ["M2101", "F1336", "FIXED"]: + raise ValueError( + f"Invalid element_pattern value {self.element_pattern}", + ) + + def get_antenna_parameters(self) -> AntennaPar: + if self.normalization: + # Load data, save it in dict and close it + data = load(self.normalization_file) + data_dict = {key: data[key] for key in data} + self.normalization_data = data_dict + data.close() + else: + self.normalization_data = None + tpl = AntennaPar( + self.adjacent_antenna_model, + self.normalization, + self.normalization_data, + self.element_pattern, + self.element_max_g, + self.element_phi_3db, + self.element_theta_3db, + self.element_am, + self.element_sla_v, + self.n_rows, + self.n_columns, + self.element_horiz_spacing, + self.element_vert_spacing, + self.multiplication_factor, + self.minimum_array_gain, + self.downtilt, + ) + + return tpl diff --git a/sharc/parameters/imt/parameters_hotspot.py b/sharc/parameters/imt/parameters_hotspot.py new file mode 100644 index 000000000..481f18fe0 --- /dev/null +++ b/sharc/parameters/imt/parameters_hotspot.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersHotspot(ParametersBase): + intersite_distance: int = None + # Enable wrap around + wrap_around: bool = False + # Number of clusters topology + num_clusters: int = 1 + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell: float = 1.0 + # Maximum 2D distance between hotspot and UE [m] + # This is the hotspot radius + max_dist_hotspot_ue: float = 100.0 + # Minimum 2D distance between macro cell base station and hotspot [m] + min_dist_bs_hotspot: float = 0.0 + + def validate(self, ctx): + if not isinstance(self.intersite_distance, int) and not isinstance(self.intersite_distance, float): + raise ValueError(f"{ctx}.intersite_distance should be a number") + + if self.num_clusters not in [1, 7]: + raise ValueError(f"{ctx}.num_clusters should be either 1 or 7") + + if not isinstance(self.num_hotspots_per_cell, int) or self.num_hotspots_per_cell < 0: + raise ValueError("num_hotspots_per_cell must be non-negative") + + if (not isinstance(self.max_dist_hotspot_ue, float) and not isinstance(self.max_dist_hotspot_ue, int))\ + or self.max_dist_hotspot_ue < 0: + raise ValueError("max_dist_hotspot_ue must be non-negative") + + if (not isinstance(self.min_dist_bs_hotspot, float) and not isinstance(self.min_dist_bs_hotspot, int))\ + or self.min_dist_bs_hotspot < 0: + raise ValueError("min_dist_bs_hotspot must be non-negative") diff --git a/sharc/parameters/imt/parameters_imt.py b/sharc/parameters/imt/parameters_imt.py new file mode 100644 index 000000000..5e3f95538 --- /dev/null +++ b/sharc/parameters/imt/parameters_imt.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +"""Parameters definitions for IMT systems +""" +import typing +from dataclasses import dataclass, field + +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_p619 import ParametersP619 + + +@dataclass +class ParametersImt(ParametersBase): + """Dataclass containing the IMT system parameters + """ + section_name: str = "imt" + # whether to enable recursive parameters setting on .yaml file + nested_parameters_enabled: bool = True + + minimum_separation_distance_bs_ue: float = 0.0 + interfered_with: bool = False + frequency: float = 7000 + bandwidth: float = 80 + rb_bandwidth: float = 0.180 + spectral_mask: str = "IMT-2020" + spurious_emissions: float = -13.0 + guard_band_ratio: float = 0.1 + + @dataclass + class ParametersBS(ParametersBase): + load_probability = 0.2 + conducted_power = 10.0 + height: float = 6.0 + noise_figure: float = 10.0 + ohmic_loss: float = 3.0 + antenna: ParametersAntennaImt = field(default_factory=ParametersAntennaImt) + + bs: ParametersBS = field(default_factory=ParametersBS) + + topology: ParametersImtTopology = field(default_factory=ParametersImtTopology) + + @dataclass + class ParametersUL(ParametersBase): + attenuation_factor: float = 0.4 + sinr_min: float = -10.0 + sinr_max: float = 22.0 + uplink: ParametersUL = field(default_factory=ParametersUL) + + # Antenna model for adjacent band studies. + adjacent_antenna_model: typing.Literal["SINGLE_ELEMENT", "BEAMFORMING"] = "SINGLE_ELEMENT" + + @dataclass + class ParametersUE(ParametersBase): + k: int = 3 + k_m: int = 1 + indoor_percent: int = 5.0 + distribution_type: str = "ANGLE_AND_DISTANCE" + distribution_distance: str = "RAYLEIGH" + distribution_azimuth: str = "NORMAL" + azimuth_range: tuple = (-60, 60) + tx_power_control: bool = True + p_o_pusch: float = -95.0 + alpha: float = 1.0 + p_cmax: float = 22.0 + power_dynamic_range: float = 63.0 + height: float = 1.5 + noise_figure: float = 10.0 + ohmic_loss: float = 3.0 + body_loss: float = 4.0 + antenna: ParametersAntennaImt = field(default_factory=lambda: ParametersAntennaImt(downtilt=0.0)) + + ue: ParametersUE = field(default_factory=ParametersUE) + + @dataclass + class ParamatersDL(ParametersBase): + attenuation_factor: float = 0.6 + sinr_min: float = -10.0 + sinr_max: float = 30.0 + + downlink: ParamatersDL = field(default_factory=ParamatersDL) + + noise_temperature: float = 290.0 + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "CI" (close-in FS reference distance) + # "UMa" (Urban Macro - 3GPP) + # "UMi" (Urban Micro - 3GPP) + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "ABG" (Alpha-Beta-Gamma) + # TODO: check if we wanna separate the channel model definition in its own nested attributes + channel_model: str = "P1411" + # Parameters for the P.619 propagation model + # For IMT NTN the model is used for calculating the coupling loss between + # the BS space station and the UEs on Earth's surface. + # For now, the NTN footprint is centered over the BS nadir point, therefore + # the paramters imt_lag_deg and imt_long_diff_deg SHALL be zero. + # space_station_alt_m - altitude of IMT space station (meters) + # earth_station_alt_m - altitude of IMT earth stations (UEs) (in meters) + # earth_station_lat_deg - latitude of IMT earth stations (UEs) (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT space and earth stations + # (positive if space-station is to the East of earth-station) + # season - season of the year. + param_p619 = ParametersP619() + season: str = "SUMMER" + + # TODO: create parameters for where this is needed + los_adjustment_factor: float = 18.0 + shadowing: bool = True + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + + if self.spectral_mask not in ["IMT-2020", "3GPP E-UTRA"]: + raise ValueError( + f"""ParametersImt: Inavlid Spectral Mask Name {self.spectral_mask}""", + ) + + if self.channel_model not in ["FSPL", "CI", "UMa", "UMi", "TVRO-URBAN", "TVRO-SUBURBAN", "ABG", "P619", "P1411", "EHF"]: + raise ValueError(f"ParamtersImt: \ + Invalid value for parameter channel_model - {self.channel_model}. \ + Possible values are \"FSPL\",\"CI\", \"UMa\", \"UMi\", \"TVRO-URBAN\", \"TVRO-SUBURBAN\", \ + \"ABG\", \"P619\", \"P1411\", \"EHF\".") + + if self.topology.type == "NTN" and self.channel_model not in ["FSPL", "P619"]: + raise ValueError( + f"ParametersImt: Invalid channel model {self.channel_model} for topology NTN", + ) + + if self.season not in ["SUMMER", "WINTER"]: + raise ValueError(f"ParamtersImt: \ + Invalid value for parameter season - {self.season}. \ + Possible values are \"SUMMER\", \"WINTER\".") + + if self.topology.type == "NTN": + self.is_space_to_earth = True + # self.param_p619.load_from_paramters(self) + + self.frequency = float(self.frequency) + + self.topology.ntn.set_external_parameters( + bs_height=self.bs.height + ) + + self.bs.antenna.set_external_parameters( + adjacent_antenna_model=self.adjacent_antenna_model + ) + + self.ue.antenna.set_external_parameters( + adjacent_antenna_model=self.adjacent_antenna_model + ) + + self.validate("imt") diff --git a/sharc/parameters/imt/parameters_imt_topology.py b/sharc/parameters/imt/parameters_imt_topology.py new file mode 100644 index 000000000..27b90002d --- /dev/null +++ b/sharc/parameters/imt/parameters_imt_topology.py @@ -0,0 +1,37 @@ +import typing +from dataclasses import field, dataclass + +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.imt.parameters_hotspot import ParametersHotspot +from sharc.parameters.imt.parameters_indoor import ParametersIndoor +from sharc.parameters.imt.parameters_macrocell import ParametersMacrocell +from sharc.parameters.imt.parameters_ntn import ParametersNTN +from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS + + +@dataclass +class ParametersImtTopology(ParametersBase): + type: typing.Literal[ + "MACROCELL", "HOTSPOT", "INDOOR", "SINGLE_BS", "NTN" + ] = "MACROCELL" + + macrocell: ParametersMacrocell = field(default_factory=ParametersMacrocell) + hotspot: ParametersHotspot = field(default_factory=ParametersHotspot) + indoor: ParametersIndoor = field(default_factory=ParametersIndoor) + single_bs: ParametersSingleBS = field(default_factory=ParametersSingleBS) + ntn: ParametersNTN = field(default_factory=ParametersNTN) + + def validate(self, ctx): + match self.type: + case "MACROCELL": + self.macrocell.validate(f"{ctx}.macrocell") + case "HOTSPOT": + self.hotspot.validate(f"{ctx}.hotspot") + case "INDOOR": + self.indoor.validate(f"{ctx}.indoor") + case "SINGLE_BS": + self.single_bs.validate(f"{ctx}.single_bs") + case "NTN": + self.ntn.validate(f"{ctx}.ntn") + case _: + raise NotImplementedError(f"{ctx}.type == '{self.type}' may not be implemented") diff --git a/sharc/parameters/imt/parameters_indoor.py b/sharc/parameters/imt/parameters_indoor.py new file mode 100644 index 000000000..2763ee458 --- /dev/null +++ b/sharc/parameters/imt/parameters_indoor.py @@ -0,0 +1,34 @@ +from dataclasses import dataclass +import typing + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersIndoor(ParametersBase): + # Basic path loss model for indoor topology. Possible values: + # "FSPL" (free-space path loss), + # "INH_OFFICE" (3GPP Indoor Hotspot - Office) + basic_path_loss: typing.Literal["INH_OFFICE", "FSPL"] = "INH_OFFICE" + # Number of rows of buildings in the simulation scenario + n_rows: int = 3 + # Number of colums of buildings in the simulation scenario + n_colums: int = 2 + # Number of buildings containing IMT stations. Options: + # 'ALL': all buildings contain IMT stations. + # An integer representing the number of buildings. + num_imt_buildings: typing.Union[typing.Literal["ALL"], int] = "ALL" + # Street width (building separation) [m] + street_width: int = 30 + # Intersite distance [m] + intersite_distance: int = 40 + # Number of cells per floor + num_cells: int = 3 + # Number of floors per building + num_floors: int = 1 + # Percentage of indoor UE's [0, 1] + ue_indoor_percent: int = .95 + # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT" + building_class: typing.Literal[ + "TRADITIONAL", "THERMALLY_EFFICIENT" + ] = "TRADITIONAL" diff --git a/sharc/parameters/imt/parameters_macrocell.py b/sharc/parameters/imt/parameters_macrocell.py new file mode 100644 index 000000000..3345019c5 --- /dev/null +++ b/sharc/parameters/imt/parameters_macrocell.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersMacrocell(ParametersBase): + intersite_distance: int = None + wrap_around: bool = False + num_clusters: int = 1 + + def validate(self, ctx): + if not isinstance(self.intersite_distance, int) and not isinstance(self.intersite_distance, float): + raise ValueError(f"{ctx}.intersite_distance should be a number") + + if self.num_clusters not in [1, 7]: + raise ValueError(f"{ctx}.num_clusters should be either 1 or 7") diff --git a/sharc/parameters/imt/parameters_ntn.py b/sharc/parameters/imt/parameters_ntn.py new file mode 100644 index 000000000..91296077a --- /dev/null +++ b/sharc/parameters/imt/parameters_ntn.py @@ -0,0 +1,105 @@ +# NOTE: some parameters are without use in implementation as of this commit +# Some parameters should probably go to the BS geometry definition +# instead of being ntn only. Need to ensure the parameters make sense +# to other topologies as well, or validate that those parameters +# can't be set if another topology is chosen + +from dataclasses import dataclass +import numpy as np + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersNTN(ParametersBase): + """ + Simulation parameters for NTN network topology. + """ + section_name: str = "ntn" + + # NTN Airborne Platform height (m) + bs_height: float = None + + # NTN cell radius in network topology [m] + cell_radius: float = 90000 + + # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3) + # @important: for NTN, intersite distance means something different than normally, + # since the BS's are placed at center of hexagons + intersite_distance: float = None + + # BS azimuth + # TODO: Put this elsewhere (in a bs.geometry for example) if needed by another model + bs_azimuth: float = 45 + # BS elevation + bs_elevation: float = 90 + + # Number of sectors + num_sectors: int = 7 + + # TODO: implement the below parameters in the simulator. They are currently not used + # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2 + bs_backoff_power: int = 3 + + # NTN Antenna configuration + bs_n_rows_layer1: int = 2 + bs_n_columns_layer1: int = 2 + bs_n_rows_layer2: int = 4 + bs_n_columns_layer2: int = 2 + + def load_subparameters(self, ctx: str, params: dict, quiet=True): + super().load_subparameters(ctx, params, quiet) + + if self.cell_radius is not None and self.intersite_distance is not None: + raise ValueError(f"You cannot set both {ctx}.intersite_distance and {ctx}.cell_radius.") + + if self.cell_radius is not None: + self.intersite_distance = self.cell_radius * np.sqrt(3) + + if self.intersite_distance is not None: + self.cell_radius = self.intersite_distance / np.sqrt(3) + + def set_external_parameters(self, *, bs_height: float): + """ + This method is used to "propagate" parameters from external context + to the values required by ntn topology. It's not ideal, but it's done + this way until we decide on a better way to model context. + """ + self.bs_height = bs_height + + def validate(self, ctx: str): + # Now do the sanity check for some parameters + if self.num_sectors not in [1, 7, 19]: + raise ValueError( + f"ParametersNTN: Invalid number of sectors {self.num_sectors}", + ) + + if self.bs_height <= 0: + raise ValueError( + f"ParametersNTN: bs_height must be greater than 0, but is {self.bs_height}", + ) + + if self.cell_radius <= 0: + raise ValueError( + f"ParametersNTN: cell_radius must be greater than 0, but is {self.cell_radius}", + ) + + if self.intersite_distance <= 0: + raise ValueError( + f"ParametersNTN: intersite_distance must be greater than 0, but is {self.intersite_distance}", + ) + + if not isinstance(self.bs_backoff_power, int) or self.bs_backoff_power < 0: + raise ValueError( + f"ParametersNTN: bs_backoff_power must be a non-negative integer, but is {self.bs_backoff_power}", + ) + + if not np.all((0 <= self.bs_azimuth) & (self.bs_azimuth <= 360)): + raise ValueError( + "ParametersNTN: bs_azimuth values must be between 0 and 360 degrees", + ) + + if not np.all((0 <= self.bs_elevation) & (self.bs_elevation <= 90)): + raise ValueError( + "ParametersNTN: bs_elevation values must be between 0 and 90 degrees", + ) diff --git a/sharc/parameters/imt/parameters_single_bs.py b/sharc/parameters/imt/parameters_single_bs.py new file mode 100644 index 000000000..65d4eb373 --- /dev/null +++ b/sharc/parameters/imt/parameters_single_bs.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersSingleBS(ParametersBase): + intersite_distance: int = None + cell_radius: int = None + num_clusters: int = 1 + + def load_subparameters(self, ctx: str, params: dict, quiet=True): + super().load_subparameters(ctx, params, quiet) + + if self.intersite_distance is not None and self.cell_radius is not None: + raise ValueError(f"{ctx}.intersite_distance and {ctx}.cell_radius should not be set at the same time") + + if self.intersite_distance is not None: + self.cell_radius = self.intersite_distance * 2 / 3 + if self.cell_radius is not None: + self.intersite_distance = self.cell_radius * 3 / 2 + + def validate(self, ctx): + if None in [self.intersite_distance, self.cell_radius]: + raise ValueError(f"{ctx}.intersite_distance and cell_radius should be set.\ + One of them through the parameters, the other inferred") + + if self.num_clusters not in [1, 2]: + raise ValueError(f"{ctx}.num_clusters should either be 1 or 2") diff --git a/sharc/parameters/parameters.py b/sharc/parameters/parameters.py index 07275b273..420744703 100644 --- a/sharc/parameters/parameters.py +++ b/sharc/parameters/parameters.py @@ -5,20 +5,21 @@ @author: edgar """ -import configparser +import os +import sys -from sharc.parameters.parameters_general import ParametersGeneral -from sharc.parameters.parameters_imt import ParametersImt -from sharc.parameters.parameters_hotspot import ParametersHotspot -from sharc.parameters.parameters_indoor import ParametersIndoor -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt -from sharc.parameters.parameters_eess_passive import ParametersEessPassive +from sharc.parameters.imt.parameters_imt import ParametersImt +from sharc.parameters.parameters_eess_ss import ParametersEessSS from sharc.parameters.parameters_fs import ParametersFs -from sharc.parameters.parameters_fss_ss import ParametersFssSs from sharc.parameters.parameters_fss_es import ParametersFssEs +from sharc.parameters.parameters_fss_ss import ParametersFssSs +from sharc.parameters.parameters_general import ParametersGeneral from sharc.parameters.parameters_haps import ParametersHaps -from sharc.parameters.parameters_rns import ParametersRns +from sharc.parameters.parameters_metsat_ss import ParametersMetSatSS from sharc.parameters.parameters_ras import ParametersRas +from sharc.parameters.parameters_rns import ParametersRns +from sharc.parameters.parameters_single_earth_station import \ + ParametersSingleEarthStation class Parameters(object): @@ -31,331 +32,91 @@ def __init__(self): self.general = ParametersGeneral() self.imt = ParametersImt() - self.antenna_imt = ParametersAntennaImt() - self.hotspot = ParametersHotspot() - self.indoor = ParametersIndoor() - self.eess_passive = ParametersEessPassive() + self.eess_ss = ParametersEessSS() self.fs = ParametersFs() self.fss_ss = ParametersFssSs() self.fss_es = ParametersFssEs() self.haps = ParametersHaps() self.rns = ParametersRns() self.ras = ParametersRas() - + self.single_earth_station = ParametersSingleEarthStation() + self.metsat_ss = ParametersMetSatSS() def set_file_name(self, file_name: str): - self.file_name = file_name + """sets the configuration file name + Parameters + ---------- + file_name : str + configuration file path + """ + self.file_name = file_name def read_params(self): - config = configparser.ConfigParser() - config.read(self.file_name) + """Read the parameters from the config file + """ + if not os.path.isfile(self.file_name): + err_msg = f"PARAMETER ERROR [{self.__class__.__name__}]: \ + Could not find the configuration file {self.file_name}" + sys.stderr.write(err_msg) + sys.exit(1) ####################################################################### # GENERAL ####################################################################### - self.general.num_snapshots = config.getint("GENERAL", "num_snapshots") - self.general.imt_link = config.get("GENERAL", "imt_link") - self.general.system = config.get("GENERAL", "system") - self.general.enable_cochannel = config.getboolean("GENERAL", "enable_cochannel") - self.general.enable_adjacent_channel = config.getboolean("GENERAL", "enable_adjacent_channel") - self.general.seed = config.getint("GENERAL", "seed") - self.general.overwrite_output = config.getboolean("GENERAL", "overwrite_output") - + self.general.load_parameters_from_file(self.file_name) ####################################################################### # IMT ####################################################################### - self.imt.topology = config.get("IMT", "topology") - self.imt.wrap_around = config.getboolean("IMT", "wrap_around") - self.imt.num_clusters = config.getint("IMT", "num_clusters") - self.imt.intersite_distance = config.getfloat("IMT", "intersite_distance") - self.imt.minimum_separation_distance_bs_ue = config.getfloat("IMT", "minimum_separation_distance_bs_ue") - self.imt.interfered_with = config.getboolean("IMT", "interfered_with") - self.imt.frequency = config.getfloat("IMT", "frequency") - self.imt.bandwidth = config.getfloat("IMT", "bandwidth") - self.imt.rb_bandwidth = config.getfloat("IMT", "rb_bandwidth") - self.imt.spectral_mask = config.get("IMT", "spectral_mask") - self.imt.spurious_emissions = config.getfloat("IMT", "spurious_emissions") - self.imt.guard_band_ratio = config.getfloat("IMT", "guard_band_ratio") - self.imt.bs_load_probability = config.getfloat("IMT", "bs_load_probability") - self.imt.bs_conducted_power = config.getfloat("IMT", "bs_conducted_power") - self.imt.bs_height = config.getfloat("IMT", "bs_height") - self.imt.bs_noise_figure = config.getfloat("IMT", "bs_noise_figure") - self.imt.bs_noise_temperature = config.getfloat("IMT", "bs_noise_temperature") - self.imt.bs_ohmic_loss = config.getfloat("IMT", "bs_ohmic_loss") - self.imt.ul_attenuation_factor = config.getfloat("IMT", "ul_attenuation_factor") - self.imt.ul_sinr_min = config.getfloat("IMT", "ul_sinr_min") - self.imt.ul_sinr_max = config.getfloat("IMT", "ul_sinr_max") - self.imt.ue_k = config.getint("IMT", "ue_k") - self.imt.ue_k_m = config.getint("IMT", "ue_k_m") - self.imt.ue_indoor_percent = config.getfloat("IMT", "ue_indoor_percent") - self.imt.ue_distribution_type = config.get("IMT", "ue_distribution_type") - self.imt.ue_distribution_distance = config.get("IMT", "ue_distribution_distance") - self.imt.ue_distribution_azimuth = config.get("IMT", "ue_distribution_azimuth") - self.imt.ue_tx_power_control = config.get("IMT", "ue_tx_power_control") - self.imt.ue_p_o_pusch = config.getfloat("IMT", "ue_p_o_pusch") - self.imt.ue_alpha = config.getfloat("IMT", "ue_alpha") - self.imt.ue_p_cmax = config.getfloat("IMT", "ue_p_cmax") - self.imt.ue_power_dynamic_range = config.getfloat("IMT", "ue_power_dynamic_range") - self.imt.ue_height = config.getfloat("IMT", "ue_height") - self.imt.ue_noise_figure = config.getfloat("IMT", "ue_noise_figure") - self.imt.ue_ohmic_loss = config.getfloat("IMT", "ue_ohmic_loss") - self.imt.ue_body_loss = config.getfloat("IMT", "ue_body_loss") - self.imt.dl_attenuation_factor = config.getfloat("IMT", "dl_attenuation_factor") - self.imt.dl_sinr_min = config.getfloat("IMT", "dl_sinr_min") - self.imt.dl_sinr_max = config.getfloat("IMT", "dl_sinr_max") - self.imt.channel_model = config.get("IMT", "channel_model") - self.imt.los_adjustment_factor = config.getfloat("IMT", "los_adjustment_factor") - self.imt.shadowing = config.getboolean("IMT", "shadowing") - self.imt.noise_temperature = config.getfloat("IMT", "noise_temperature") - self.imt.BOLTZMANN_CONSTANT = config.getfloat("IMT", "BOLTZMANN_CONSTANT") - - ####################################################################### - # IMT ANTENNA - ####################################################################### - self.antenna_imt.adjacent_antenna_model = config.get("IMT_ANTENNA", "adjacent_antenna_model") - self.antenna_imt.bs_normalization = config.getboolean("IMT_ANTENNA", "bs_normalization") - self.antenna_imt.ue_normalization = config.getboolean("IMT_ANTENNA", "ue_normalization") - self.antenna_imt.bs_normalization_file = config.get("IMT_ANTENNA", "bs_normalization_file") - self.antenna_imt.ue_normalization_file = config.get("IMT_ANTENNA", "ue_normalization_file") - self.antenna_imt.bs_element_pattern = config.get("IMT_ANTENNA", "bs_element_pattern") - self.antenna_imt.ue_element_pattern = config.get("IMT_ANTENNA", "ue_element_pattern") - - self.antenna_imt.bs_element_max_g = config.getfloat("IMT_ANTENNA", "bs_element_max_g") - self.antenna_imt.bs_element_phi_3db = config.getfloat("IMT_ANTENNA", "bs_element_phi_3db") - self.antenna_imt.bs_element_theta_3db = config.getfloat("IMT_ANTENNA", "bs_element_theta_3db") - self.antenna_imt.bs_element_am = config.getfloat("IMT_ANTENNA", "bs_element_am") - self.antenna_imt.bs_element_sla_v = config.getfloat("IMT_ANTENNA", "bs_element_sla_v") - self.antenna_imt.bs_n_rows = config.getfloat("IMT_ANTENNA", "bs_n_rows") - self.antenna_imt.bs_n_columns = config.getfloat("IMT_ANTENNA", "bs_n_columns") - self.antenna_imt.bs_element_horiz_spacing = config.getfloat("IMT_ANTENNA", "bs_element_horiz_spacing") - self.antenna_imt.bs_element_vert_spacing = config.getfloat("IMT_ANTENNA", "bs_element_vert_spacing") - self.antenna_imt.bs_multiplication_factor = config.getfloat("IMT_ANTENNA", "bs_multiplication_factor") - self.antenna_imt.bs_minimum_array_gain = config.getfloat("IMT_ANTENNA", "bs_minimum_array_gain") - - self.antenna_imt.ue_element_max_g = config.getfloat("IMT_ANTENNA", "ue_element_max_g") - self.antenna_imt.ue_element_phi_3db = config.getfloat("IMT_ANTENNA", "ue_element_phi_3db") - self.antenna_imt.ue_element_theta_3db = config.getfloat("IMT_ANTENNA", "ue_element_theta_3db") - self.antenna_imt.ue_element_am = config.getfloat("IMT_ANTENNA", "ue_element_am") - self.antenna_imt.ue_element_sla_v = config.getfloat("IMT_ANTENNA", "ue_element_sla_v") - self.antenna_imt.ue_n_rows = config.getfloat("IMT_ANTENNA", "ue_n_rows") - self.antenna_imt.ue_n_columns = config.getfloat("IMT_ANTENNA", "ue_n_columns") - self.antenna_imt.ue_element_horiz_spacing = config.getfloat("IMT_ANTENNA", "ue_element_horiz_spacing") - self.antenna_imt.ue_element_vert_spacing = config.getfloat("IMT_ANTENNA", "ue_element_vert_spacing") - self.antenna_imt.ue_multiplication_factor = config.getfloat("IMT_ANTENNA", "ue_multiplication_factor") - self.antenna_imt.ue_minimum_array_gain = config.getfloat("IMT_ANTENNA", "ue_minimum_array_gain") - - self.antenna_imt.bs_downtilt = config.getfloat("IMT_ANTENNA", "bs_downtilt") - - ####################################################################### - # HOTSPOT - ####################################################################### - self.hotspot.num_hotspots_per_cell = config.getint("HOTSPOT", "num_hotspots_per_cell") - self.hotspot.max_dist_hotspot_ue = config.getfloat("HOTSPOT", "max_dist_hotspot_ue") - self.hotspot.min_dist_bs_hotspot = config.getfloat("HOTSPOT", "min_dist_bs_hotspot") - - ####################################################################### - # INDOOR - ####################################################################### - self.indoor.basic_path_loss = config.get("INDOOR", "basic_path_loss") - self.indoor.n_rows = config.getint("INDOOR", "n_rows") - self.indoor.n_colums = config.getint("INDOOR", "n_colums") - self.indoor.num_imt_buildings = config.get("INDOOR", "num_imt_buildings") - self.indoor.street_width = config.getint("INDOOR", "street_width") - self.indoor.intersite_distance = config.getfloat("INDOOR", "intersite_distance") - self.indoor.num_cells = config.getint("INDOOR", "num_cells") - self.indoor.num_floors = config.getint("INDOOR", "num_floors") - self.indoor.ue_indoor_percent = config.getfloat("INDOOR", "ue_indoor_percent") - self.indoor.building_class = config.get("INDOOR", "building_class") + self.imt.load_parameters_from_file(self.file_name) ####################################################################### # FSS space station ####################################################################### - self.fss_ss.frequency = config.getfloat("FSS_SS", "frequency") - self.fss_ss.bandwidth = config.getfloat("FSS_SS", "bandwidth") - self.fss_ss.tx_power_density = config.getfloat("FSS_SS", "tx_power_density") - self.fss_ss.altitude = config.getfloat("FSS_SS", "altitude") - self.fss_ss.lat_deg = config.getfloat("FSS_SS", "lat_deg") - self.fss_ss.elevation = config.getfloat("FSS_SS", "elevation") - self.fss_ss.azimuth = config.getfloat("FSS_SS", "azimuth") - self.fss_ss.noise_temperature = config.getfloat("FSS_SS", "noise_temperature") - self.fss_ss.adjacent_ch_selectivity = config.getfloat("FSS_SS", "adjacent_ch_selectivity") - self.fss_ss.antenna_gain = config.getfloat("FSS_SS", "antenna_gain") - self.fss_ss.antenna_pattern = config.get("FSS_SS", "antenna_pattern") - self.fss_ss.imt_altitude = config.getfloat("FSS_SS", "imt_altitude") - self.fss_ss.imt_lat_deg = config.getfloat("FSS_SS", "imt_lat_deg") - self.fss_ss.imt_long_diff_deg = config.getfloat("FSS_SS", "imt_long_diff_deg") - self.fss_ss.season = config.get("FSS_SS", "season") - self.fss_ss.channel_model = config.get("FSS_SS", "channel_model") - self.fss_ss.antenna_l_s = config.getfloat("FSS_SS", "antenna_l_s") - self.fss_ss.antenna_3_dB = config.getfloat("FSS_SS", "antenna_3_dB") - self.fss_ss.BOLTZMANN_CONSTANT = config.getfloat("FSS_SS", "BOLTZMANN_CONSTANT") - self.fss_ss.EARTH_RADIUS = config.getfloat("FSS_SS", "EARTH_RADIUS") + self.fss_ss.load_parameters_from_file(self.file_name) ####################################################################### # FSS earth station ####################################################################### - self.fss_es.location = config.get("FSS_ES", "location") - self.fss_es.x = config.getfloat("FSS_ES", "x") - self.fss_es.y = config.getfloat("FSS_ES", "y") - self.fss_es.min_dist_to_bs = config.getfloat("FSS_ES", "min_dist_to_bs") - self.fss_es.max_dist_to_bs = config.getfloat("FSS_ES", "max_dist_to_bs") - self.fss_es.height = config.getfloat("FSS_ES", "height") - self.fss_es.elevation_min = config.getfloat("FSS_ES", "elevation_min") - self.fss_es.elevation_max = config.getfloat("FSS_ES", "elevation_max") - self.fss_es.azimuth = config.get("FSS_ES", "azimuth") - self.fss_es.frequency = config.getfloat("FSS_ES", "frequency") - self.fss_es.bandwidth = config.getfloat("FSS_ES", "bandwidth") - self.fss_es.adjacent_ch_selectivity = config.getfloat("FSS_ES", "adjacent_ch_selectivity") - self.fss_es.tx_power_density = config.getfloat("FSS_ES", "tx_power_density") - self.fss_es.noise_temperature = config.getfloat("FSS_ES", "noise_temperature") - self.fss_es.antenna_gain = config.getfloat("FSS_ES", "antenna_gain") - self.fss_es.antenna_pattern = config.get("FSS_ES", "antenna_pattern") - self.fss_es.antenna_envelope_gain = config.getfloat("FSS_ES", "antenna_envelope_gain") - self.fss_es.diameter = config.getfloat("FSS_ES", "diameter") - self.fss_es.channel_model = config.get("FSS_ES", "channel_model") - self.fss_es.BOLTZMANN_CONSTANT = config.getfloat("FSS_ES", "BOLTZMANN_CONSTANT") - self.fss_es.EARTH_RADIUS = config.getfloat("FSS_ES", "EARTH_RADIUS") - - # P452 parameters - self.fss_es.atmospheric_pressure = config.getfloat("FSS_ES", "atmospheric_pressure") - self.fss_es.air_temperature = config.getfloat("FSS_ES", "air_temperature") - self.fss_es.N0 = config.getfloat("FSS_ES", "N0") - self.fss_es.delta_N = config.getfloat("FSS_ES", "delta_N") - self.fss_es.percentage_p = config.get("FSS_ES", "percentage_p") - self.fss_es.Dct = config.getfloat("FSS_ES", "Dct") - self.fss_es.Dcr = config.getfloat("FSS_ES", "Dcr") - self.fss_es.Hte = config.getfloat("FSS_ES", "Hte") - self.fss_es.Hre = config.getfloat("FSS_ES", "Hre") - self.fss_es.tx_lat = config.getfloat("FSS_ES", "tx_lat") - self.fss_es.rx_lat = config.getfloat("FSS_ES", "rx_lat") - self.fss_es.polarization = config.get("FSS_ES", "polarization") - self.fss_es.clutter_loss = config.getboolean("FSS_ES", "clutter_loss") - - # HDFSS propagation parameters - self.fss_es.es_position = config.get("FSS_ES", "es_position") - self.fss_es.shadow_enabled = config.getboolean("FSS_ES", "shadow_enabled") - self.fss_es.building_loss_enabled = config.getboolean("FSS_ES", "building_loss_enabled") - self.fss_es.same_building_enabled = config.getboolean("FSS_ES", "same_building_enabled") - self.fss_es.diffraction_enabled = config.getboolean("FSS_ES", "diffraction_enabled") - self.fss_es.bs_building_entry_loss_type = config.get("FSS_ES", "bs_building_entry_loss_type") - self.fss_es.bs_building_entry_loss_prob = config.getfloat("FSS_ES", "bs_building_entry_loss_prob") - self.fss_es.bs_building_entry_loss_value = config.getfloat("FSS_ES", "bs_building_entry_loss_value") + self.fss_es.load_parameters_from_file(self.file_name) ####################################################################### # Fixed wireless service ####################################################################### - self.fs.x = config.getfloat("FS", "x") - self.fs.y = config.getfloat("FS", "y") - self.fs.height = config.getfloat("FS", "height") - self.fs.elevation = config.getfloat("FS", "elevation") - self.fs.azimuth = config.getfloat("FS", "azimuth") - self.fs.frequency = config.getfloat("FS", "frequency") - self.fs.bandwidth = config.getfloat("FS", "bandwidth") - self.fs.noise_temperature = config.getfloat("FS", "noise_temperature") - self.fs.adjacent_ch_selectivity = config.getfloat("FS", "adjacent_ch_selectivity") - self.fs.tx_power_density = config.getfloat("FS", "tx_power_density") - self.fs.antenna_gain = config.getfloat("FS", "antenna_gain") - self.fs.antenna_pattern = config.get("FS", "antenna_pattern") - self.fs.diameter = config.getfloat("FS", "diameter") - self.fs.channel_model = config.get("FS", "channel_model") - self.fs.BOLTZMANN_CONSTANT = config.getfloat("FS", "BOLTZMANN_CONSTANT") - self.fs.EARTH_RADIUS = config.getfloat("FS", "EARTH_RADIUS") + self.fs.load_parameters_from_file(self.file_name) ####################################################################### # HAPS (airbone) station ####################################################################### - self.haps.frequency = config.getfloat("HAPS", "frequency") - self.haps.bandwidth = config.getfloat("HAPS", "bandwidth") - self.haps.antenna_gain = config.getfloat("HAPS", "antenna_gain") - self.haps.tx_power_density = config.getfloat("HAPS", "eirp_density") - self.haps.antenna_gain - 60 - self.haps.altitude = config.getfloat("HAPS", "altitude") - self.haps.lat_deg = config.getfloat("HAPS", "lat_deg") - self.haps.elevation = config.getfloat("HAPS", "elevation") - self.haps.azimuth = config.getfloat("HAPS", "azimuth") - self.haps.antenna_pattern = config.get("HAPS", "antenna_pattern") - self.haps.imt_altitude = config.getfloat("HAPS", "imt_altitude") - self.haps.imt_lat_deg = config.getfloat("HAPS", "imt_lat_deg") - self.haps.imt_long_diff_deg = config.getfloat("HAPS", "imt_long_diff_deg") - self.haps.season = config.get("HAPS", "season") - self.haps.acs = config.getfloat("HAPS", "acs") - self.haps.channel_model = config.get("HAPS", "channel_model") - self.haps.antenna_l_n = config.getfloat("HAPS", "antenna_l_n") - self.haps.BOLTZMANN_CONSTANT = config.getfloat("HAPS", "BOLTZMANN_CONSTANT") - self.haps.EARTH_RADIUS = config.getfloat("HAPS", "EARTH_RADIUS") + self.haps.load_parameters_from_file(self.file_name) ####################################################################### # RNS ####################################################################### - self.rns.x = config.getfloat("RNS", "x") - self.rns.y = config.getfloat("RNS", "y") - self.rns.altitude = config.getfloat("RNS", "altitude") - self.rns.frequency = config.getfloat("RNS", "frequency") - self.rns.bandwidth = config.getfloat("RNS", "bandwidth") - self.rns.noise_temperature = config.getfloat("RNS", "noise_temperature") - self.rns.tx_power_density = config.getfloat("RNS", "tx_power_density") - self.rns.antenna_gain = config.getfloat("RNS", "antenna_gain") - self.rns.antenna_pattern = config.get("RNS", "antenna_pattern") - self.rns.season = config.get("RNS", "season") - self.rns.imt_altitude = config.getfloat("RNS", "imt_altitude") - self.rns.imt_lat_deg = config.getfloat("RNS", "imt_lat_deg") - self.rns.channel_model = config.get("RNS", "channel_model") - self.rns.acs = config.getfloat("RNS", "acs") - self.rns.BOLTZMANN_CONSTANT = config.getfloat("RNS", "BOLTZMANN_CONSTANT") - self.rns.EARTH_RADIUS = config.getfloat("RNS", "EARTH_RADIUS") + self.rns.load_parameters_from_file(self.file_name) ####################################################################### # RAS station ####################################################################### - self.ras.x = config.getfloat("RAS", "x") - self.ras.y = config.getfloat("RAS", "y") - self.ras.height = config.getfloat("RAS", "height") - self.ras.elevation = config.getfloat("RAS", "elevation") - self.ras.azimuth = config.getfloat("RAS", "azimuth") - self.ras.frequency = config.getfloat("RAS", "frequency") - self.ras.bandwidth = config.getfloat("RAS", "bandwidth") - self.ras.antenna_noise_temperature = config.getfloat("RAS", "antenna_noise_temperature") - self.ras.receiver_noise_temperature = config.getfloat("RAS", "receiver_noise_temperature") - self.ras.adjacent_ch_selectivity = config.getfloat("FSS_ES", "adjacent_ch_selectivity") - self.ras.antenna_efficiency = config.getfloat("RAS", "antenna_efficiency") - self.ras.antenna_gain = config.getfloat("RAS", "antenna_gain") - self.ras.antenna_pattern = config.get("RAS", "antenna_pattern") - self.ras.diameter = config.getfloat("RAS", "diameter") - self.ras.channel_model = config.get("RAS", "channel_model") - self.ras.BOLTZMANN_CONSTANT = config.getfloat("RAS", "BOLTZMANN_CONSTANT") - self.ras.EARTH_RADIUS = config.getfloat("RAS", "EARTH_RADIUS") - self.ras.SPEED_OF_LIGHT = config.getfloat("RAS", "SPEED_OF_LIGHT") - - # P452 parameters - self.ras.atmospheric_pressure = config.getfloat("RAS", "atmospheric_pressure") - self.ras.air_temperature = config.getfloat("RAS", "air_temperature") - self.ras.N0 = config.getfloat("RAS", "N0") - self.ras.delta_N = config.getfloat("RAS", "delta_N") - self.ras.percentage_p = config.get("RAS", "percentage_p") - self.ras.Dct = config.getfloat("RAS", "Dct") - self.ras.Dcr = config.getfloat("RAS", "Dcr") - self.ras.Hte = config.getfloat("RAS", "Hte") - self.ras.Hre = config.getfloat("RAS", "Hre") - self.ras.tx_lat = config.getfloat("RAS", "tx_lat") - self.ras.rx_lat = config.getfloat("RAS", "rx_lat") - self.ras.polarization = config.get("RAS", "polarization") - self.ras.clutter_loss = config.getboolean("RAS", "clutter_loss") + #self.ras.load_parameters_from_file(self.file_name) ####################################################################### # EESS passive ####################################################################### - self.eess_passive.frequency = config.getfloat("EESS_PASSIVE", "frequency") - self.eess_passive.bandwidth = config.getfloat("EESS_PASSIVE", "bandwidth") - self.eess_passive.nadir_angle = config.getfloat("EESS_PASSIVE", "nadir_angle") - self.eess_passive.altitude = config.getfloat("EESS_PASSIVE", "altitude") - self.eess_passive.antenna_pattern = config.get("EESS_PASSIVE", "antenna_pattern") - self.eess_passive.antenna_efficiency = config.getfloat("EESS_PASSIVE", "antenna_efficiency") - self.eess_passive.antenna_diameter = config.getfloat("EESS_PASSIVE", "antenna_diameter") - self.eess_passive.antenna_gain = config.getfloat("EESS_PASSIVE", "antenna_gain") - self.eess_passive.channel_model = config.get("EESS_PASSIVE", "channel_model") - self.eess_passive.imt_altitude = config.getfloat("EESS_PASSIVE", "imt_altitude") - self.eess_passive.imt_lat_deg = config.getfloat("EESS_PASSIVE", "imt_lat_deg") - self.eess_passive.season = config.get("EESS_PASSIVE", "season") - self.eess_passive.BOLTZMANN_CONSTANT = config.getfloat("EESS_PASSIVE", "BOLTZMANN_CONSTANT") - self.eess_passive.EARTH_RADIUS = config.getfloat("EESS_PASSIVE", "EARTH_RADIUS") + self.eess_ss.load_parameters_from_file(self.file_name) + + self.single_earth_station.load_parameters_from_file(self.file_name) + + +if __name__ == "__main__": + from pprint import pprint + parameters = Parameters() + param_sections = [ + a for a in dir(parameters) if not a.startswith('__') and not + callable(getattr(parameters, a)) + ] + print("\n#### Dumping default parameters:") + for p in param_sections: + print("\n") + pprint(getattr(parameters, p)) diff --git a/sharc/parameters/parameters_antenna.py b/sharc/parameters/parameters_antenna.py new file mode 100644 index 000000000..f52741212 --- /dev/null +++ b/sharc/parameters/parameters_antenna.py @@ -0,0 +1,90 @@ +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_antenna_with_diameter import ParametersAntennaWithDiameter +from sharc.parameters.parameters_antenna_with_envelope_gain import ParametersAntennaWithEnvelopeGain + +from dataclasses import dataclass, field +import typing + + +@dataclass +class ParametersAntenna(ParametersBase): + # available antenna radiation patterns + __SUPPORTED_ANTENNA_PATTERNS = [ + "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855", + ] + + # chosen antenna radiation pattern + pattern: typing.Literal[ + "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855", + ] = None + + # antenna gain [dBi] + gain: float = None + + itu_r_f_699: ParametersAntennaWithDiameter = field( + default_factory=ParametersAntennaWithDiameter, + ) + + itu_r_s_465: ParametersAntennaWithDiameter = field( + default_factory=ParametersAntennaWithDiameter, + ) + + itu_r_s_1855: ParametersAntennaWithDiameter = field( + default_factory=ParametersAntennaWithDiameter, + ) + + itu_r_s_580: ParametersAntennaWithDiameter = field( + default_factory=ParametersAntennaWithDiameter, + ) + + itu_r_s_465_modified: ParametersAntennaWithEnvelopeGain = field( + default_factory=ParametersAntennaWithEnvelopeGain, + ) + + def set_external_parameters(self, *, frequency): + attr_list = [ + a for a in dir(self) if not a.startswith('__') and isinstance(getattr(self, a), ParametersBase) + ] + + for attr_name in attr_list: + param = getattr(self, attr_name) + + if "frequency" in dir(param): + param.frequency = frequency + if "antenna_gain" in dir(param): + param.antenna_gain = self.gain + + def load_parameters_from_file(self, config_file): + # should not be loaded except as subparameter + raise NotImplementedError() + + def validate(self, ctx): + if None in [self.pattern, self.gain]: + raise ValueError( + f"Required parameters for Antenna were not met. At {ctx}", + ) + + if self.pattern not in self.__SUPPORTED_ANTENNA_PATTERNS: + raise ValueError( + f"Invalid ParametersAntenna.pattern. It should be one of: {self.__SUPPORTED_ANTENNA_PATTERNS}. At {ctx}", + ) + + match self.pattern: + case "OMNI": + pass + case "ITU-R F.699": + self.itu_r_f_699.validate(f"{ctx}.itu_r_f_699") + case "ITU-R S.465": + self.itu_r_s_465.validate(f"{ctx}.itu_r_s_465") + case "ITU-R S.1855": + self.itu_r_s_1855.validate(f"{ctx}.itu_r_s_1855") + case "MODIFIED ITU-R S.465": + self.itu_r_s_465_modified.validate( + f"{ctx}.itu_r_s_465_modified", + ) + case "ITU-R S.580": + self.itu_r_s_580.validate(f"{ctx}.itu_r_s_580") + case _: + raise NotImplementedError( + "ParametersAntenna.validate does not implement this antenna validation!", + ) diff --git a/sharc/parameters/parameters_antenna_imt.py b/sharc/parameters/parameters_antenna_imt.py deleted file mode 100644 index d128a7862..000000000 --- a/sharc/parameters/parameters_antenna_imt.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat Apr 15 16:29:36 2017 - -@author: Calil -""" - -import sys - -from sharc.support.named_tuples import AntennaPar -from sharc.support.enumerations import StationType -from numpy import load - -class ParametersAntennaImt(object): - """ - Defines parameters for antenna array. - """ - - def __init__(self): - pass - - - ########################################################################### - # Named tuples which contain antenna types - - def get_antenna_parameters(self, sta_type: StationType)-> AntennaPar: - if sta_type is StationType.IMT_BS: - if self.bs_normalization: - # Load data, save it in dict and close it - data = load(self.bs_normalization_file) - data_dict = {key:data[key] for key in data} - self.bs_normalization_data = data_dict - data.close() - else: - self.bs_normalization_data = None - tpl = AntennaPar(self.adjacent_antenna_model, - self.bs_normalization, - self.bs_normalization_data, - self.bs_element_pattern, - self.bs_element_max_g, - self.bs_element_phi_3db, - self.bs_element_theta_3db, - self.bs_element_am, - self.bs_element_sla_v, - self.bs_n_rows, - self.bs_n_columns, - self.bs_element_horiz_spacing, - self.bs_element_vert_spacing, - self.bs_multiplication_factor, - self.bs_minimum_array_gain, - self.bs_downtilt) - elif sta_type is StationType.IMT_UE: - if self.ue_normalization: - # Load data, save it in dict and close it - data = load(self.ue_normalization_file) - data_dict = {key:data[key] for key in data} - self.ue_normalization_data = data_dict - data.close() - else: - self.ue_normalization_data = None - tpl = AntennaPar(self.adjacent_antenna_model, - self.ue_normalization, - self.ue_normalization_data, - self.ue_element_pattern, - self.ue_element_max_g, - self.ue_element_phi_3db, - self.ue_element_theta_3db, - self.ue_element_am, - self.ue_element_sla_v, - self.ue_n_rows, - self.ue_n_columns, - self.ue_element_horiz_spacing, - self.ue_element_vert_spacing, - self.ue_multiplication_factor, - self.ue_minimum_array_gain, - 0) - else: - sys.stderr.write("ERROR\nInvalid station type: " + sta_type) - sys.exit(1) - - return tpl diff --git a/sharc/parameters/parameters_antenna_with_diameter.py b/sharc/parameters/parameters_antenna_with_diameter.py new file mode 100644 index 000000000..a34c21be9 --- /dev/null +++ b/sharc/parameters/parameters_antenna_with_diameter.py @@ -0,0 +1,30 @@ +from sharc.parameters.parameters_base import ParametersBase + +from dataclasses import dataclass + + +@dataclass +class ParametersAntennaWithDiameter(ParametersBase): + antenna_gain: float = None + frequency: float = None + diameter: float = None + + # ideally we would make this happen at the ParametersBase.load_subparameters, but + # since parser is a bit hacky, this is created without acces to system freq and antenna gain + # so validation needs to happen manually afterwards + def validate(self, ctx): + if None in [ + self.antenna_gain, + self.diameter, + self.frequency, + ]: + raise ValueError(f"{ctx} needs to have all its parameters set") + + if not isinstance(self.antenna_gain, int) and not isinstance(self.antenna_gain, float): + raise ValueError(f"{ctx}.antenna_gain needs to be a number") + + if (not isinstance(self.diameter, int) and not isinstance(self.diameter, float)) or self.diameter <= 0: + raise ValueError(f"{ctx}.diameter needs to be a positive number") + + if not isinstance(self.frequency, int) and not isinstance(self.frequency, float): + raise ValueError(f"{ctx}.frequency needs to be a number") diff --git a/sharc/parameters/parameters_antenna_with_envelope_gain.py b/sharc/parameters/parameters_antenna_with_envelope_gain.py new file mode 100644 index 000000000..59a451ecd --- /dev/null +++ b/sharc/parameters/parameters_antenna_with_envelope_gain.py @@ -0,0 +1,25 @@ +from sharc.parameters.parameters_base import ParametersBase + +from dataclasses import dataclass + + +@dataclass +class ParametersAntennaWithEnvelopeGain(ParametersBase): + antenna_gain: float = None + envelope_gain: float = None + + # ideally we would make this happen at the ParametersBase.load_subparameters, but + # since parser is a bit hacky, this is created without acces to system freq and antenna gain + # so validation needs to happen manually afterwards + def validate(self, ctx): + if None in [ + self.antenna_gain, + self.envelope_gain, + ]: + raise ValueError(f"{ctx} needs to have all its parameters set") + + if not isinstance(self.gain, int) and not isinstance(self.gain, float): + raise ValueError(f"{ctx}.gain needs to be a number") + + if not isinstance(self.envelope_gain, int) and not isinstance(self.envelope_gain, float): + raise ValueError(f"{ctx}.envelope_gain needs to be a number") diff --git a/sharc/parameters/parameters_base.py b/sharc/parameters/parameters_base.py new file mode 100644 index 000000000..1baf7c3a9 --- /dev/null +++ b/sharc/parameters/parameters_base.py @@ -0,0 +1,157 @@ +import yaml +from dataclasses import dataclass + + +# Register a tuple constructor with PyYAML +def tuple_constructor(loader, node): + """Load the sequence of values from the YAML node and returns a tuple constructed from the sequence.""" + values = loader.construct_sequence(node) + return tuple(values) + + +yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor) + + +@dataclass +class ParametersBase: + """Base class for parameter dataclassess + """ + section_name: str = "default" + is_space_to_earth: bool = False # whether the system is a space station or not + + # whether to enable recursive parameters setting on .yaml file + # TODO: make every system have this be True and remove this attribute + nested_parameters_enabled: bool = False + + # TODO: make this be directly called as the default load method, after reading .yml file + def load_subparameters(self, ctx: str, params: dict, quiet=True): + """ + ctx: provides information on what subattribute is being parsed. + This is mainly for debugging/logging/error handling + params: dict that contains the attributes needed by the + quiet: if True the parser will not warn about unset paramters + """ + # Load all the parameters from the configuration file + attr_list = [ + a for a in dir(self) if not a.startswith('_') and not callable(getattr(self, a)) and a not in + ["section_name", "nested_parameters_enabled",]] + + for attr_name in attr_list: + default_attr_value = getattr(self, attr_name) + + if attr_name not in params: + if not quiet: + print( + f"[INFO]: WARNING. Using default parameters for {ctx}.{attr_name}: {default_attr_value}", + ) + elif isinstance(default_attr_value, ParametersBase): + if not isinstance(params[attr_name], dict): + raise ValueError( + f"ERROR: Cannot parse section {ctx}.{attr_name}, is {params[attr_name]} instead of a dictionary", + ) + + # try to recursively set config + # is a bit hacky and limits some stuff, since it doesn't know the context it is in + # for example, it cannot get system frequency to set some value + default_attr_value.load_subparameters( + f"{ctx}.{attr_name}", params[attr_name], + ) + else: + setattr(self, attr_name, params[attr_name]) + + def validate(self, ctx: str): + """ + This method exists because there was a need to separate params parsing from validating, + since nested parameters may need some attributes to be set by a "parent" + before proper validation. + ctx: context string. It should be a string that gives more information about where + validation is being called on, so that errors may be better handled/alerted + """ + attr_list = [ + a for a in dir(self) if not a.startswith('_') and isinstance(getattr(self, a), ParametersBase) + ] + + for attr in attr_list: + getattr(self, attr).validate(f"{ctx}.{attr}") + + def load_parameters_from_file(self, config_file: str, quiet=True): + """Load the parameters from file. + The sanity check is child class reponsibility + + Parameters + ---------- + file_name : str + the path to the configuration file + quiet: if True the parser will not warn about unset paramters + + Raises + ------ + ValueError + if a parameter is not valid + """ + + with open(config_file, 'r') as file: + config = yaml.safe_load(file) + + if self.section_name.lower() not in config.keys(): + if not quiet: + print(f"ParameterBase: section {self.section_name} not in parameter file.\ + Only default parameters where loaded.") + return + + # Load all the parameters from the configuration file + attr_list = [ + a for a in dir(self) if not a.startswith('_') and not + callable(getattr(self, a)) and a != "section_name" + ] + + for attr_name in attr_list: + try: + attr_val = getattr(self, attr_name) + # since there are no tuple types in yaml: + if isinstance(attr_val, tuple): + # TODO: test this conditional. There are no test cases for tuple, and no tuple in any of current parameters + # Check if the string defines a list of floats + try: + param_val = config[self.section_name][attr_name] + tmp_val = list(map(float, param_val.split(","))) + setattr(self, attr_name, tuple(tmp_val)) + except ValueError: + # its a regular string. Let the specific class implementation + # do the sanity check + print( + f"ParametersBase: could not convert string to tuple \"{self.section_name}.{attr_name}\"", + ) + exit() + + # TODO: make every parameters use this way of setting its own attributes, and remove + # attr_val.nested_parameters_enabled we check for attr_val.nested_parameters_enabled because we don't + # want to print notice for this kind of parameter YET + elif isinstance(attr_val, ParametersBase): + if not self.nested_parameters_enabled: + continue + + if not isinstance(config[self.section_name][attr_name], dict): + raise ValueError( + f"ERROR: Cannot parse section {self.section_name}.{attr_name}, is \ + {config[self.section_name][attr_name]} instead of a dictionary", + ) + + # try to recursively set config + # is a bit hacky and limits some stuff, since it doesn't know the context it is in + # for example, it cannot get system frequency to set some value + attr_val.load_subparameters( + f"{self.section_name}.{attr_name}", config[self.section_name][attr_name], + ) + else: + setattr( + self, attr_name, + config[self.section_name][attr_name], + ) + + except KeyError: + if not quiet: + print( + f"ParametersBase: NOTICE! Configuration parameter \"{self.section_name}.{attr_name}\" \ + is not set in configuration file. Using default value {attr_val}", + ) diff --git a/sharc/parameters/parameters_eess_passive.py b/sharc/parameters/parameters_eess_passive.py deleted file mode 100644 index 1b6ee46fe..000000000 --- a/sharc/parameters/parameters_eess_passive.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 21 11:58:56 2019 - -@author: Edgar Souza -""" - -class ParametersEessPassive(object): - """ - Simulation parameters for EESS passive services - """ - - def __init__(self): - pass \ No newline at end of file diff --git a/sharc/parameters/parameters_eess_ss.py b/sharc/parameters/parameters_eess_ss.py new file mode 100644 index 000000000..306dbb8f9 --- /dev/null +++ b/sharc/parameters/parameters_eess_ss.py @@ -0,0 +1,122 @@ +from dataclasses import dataclass + +from sharc.parameters.parameters_p619 import ParametersP619 +from sharc.parameters.parameters_space_station import ParametersSpaceStation + + +@dataclass +class ParametersEessSS(ParametersSpaceStation): + """ + Defines parameters for Earth Exploration Satellite Service (EESS) sensors/space stations (SS) + and their interaction with other services based on ITU recommendations. + """ + section_name: str = "eess_ss" + + is_space_to_earth: bool = True + + # Sensor center frequency [MHz] + frequency: float = 23900.0 # Center frequency of the sensor in MHz + + # Sensor bandwidth [MHz] + bandwidth: float = 200.0 # Bandwidth of the sensor in MHz + + # Off-nadir pointing angle [deg] + nadir_angle: float = 46.6 # Angle in degrees away from the nadir point + + # Sensor altitude [m] + # Altitude of the sensor above the Earth's surface in meters + altitude: float = 828000.0 + + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813", "ITU-R RS.1861 9a", "ITU-R RS.1861 9b", + # "ITU-R RS.1861 9c", "ITU-R RS.2043", "OMNI" + # TODO: check `x` and `y`: + # @important: for EESS Passive, antenna pattern is from Recommendatio ITU-R RS.1813 + antenna_pattern: str = "ITU-R RS.1813" # Antenna radiation pattern + + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + # Efficiency factor for the antenna, range from 0 to 1 + antenna_efficiency: float = 0.6 + + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter: float = 2.2 # Diameter of the antenna in meters + + # Receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: float = 52.0 # Gain of the antenna in dBi + + # Channel model, possible values are "FSPL" (free-space path loss), "P619" + channel_model: str = "FSPL" # Channel model to be used + + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + param_p619 = ParametersP619() + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + + ########### Creates a statistical distribution of nadir angle############### + ############## following variables nadir_angle_distribution################# + # if distribution_enable = ON, nadir_angle will vary statistically######### + # if distribution_enable = OFF, nadir_angle follow nadir_angle variable ### + # distribution_type = UNIFORM + # UNIFORM = UNIFORM distribution in nadir_angle + # - nadir_angle_distribution = initial nadir angle, final nadir angle + distribution_enable: bool = False + distribution_type: str = "UNIFORM" + nadir_angle_distribution: tuple = (18.5, 49.3) + + def load_parameters_from_file(self, config_file: str): + """ + Load the parameters from a file and run a sanity check. + + Parameters + ---------- + config_file : str + The path to the configuration file. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + super().load_parameters_from_file(config_file) + # self.param_p619.load_from_paramters(self) + # # print("altitude, earth_station_alt_m", self.altitude, self.earth_station_alt_m) + # print([ + # self.earth_station_alt_m, self.earth_station_lat_deg, self.earth_station_long_diff_deg + # ]) + # Implement additional sanity checks for EESS specific parameters + if not (0 <= self.antenna_efficiency or self.antenna_efficiency <= 1): + raise ValueError("antenna_efficiency must be between 0 and 1") + + if self.antenna_pattern not in [ + "ITU-R RS.1813", "ITU-R RS.1861 9a", + "ITU-R RS.1861 9b", "ITU-R RS.1861 9c", + "ITU-R RS.2043", "OMNI", + ]: + raise ValueError(f"Invalid antenna_pattern: { + self.antenna_pattern + }") + + if self.antenna_pattern == "ITU-R RS.2043" and \ + (self.frequency <= 9000.0 or self.frequency >= 10999.0): + raise ValueError( + f"Frequency {self.frequency} MHz is not in the range for antenna pattern \"ITU-R RS.2043\"", + ) + + # Check channel model + if self.channel_model not in ["FSPL", "P619"]: + raise ValueError( + "Invalid channel_model, must be either 'FSPL' or 'P619'", + ) + + # Check season + if self.season not in ["SUMMER", "WINTER"]: + raise ValueError( + "Invalid season, must be either 'SUMMER' or 'WINTER'", + ) diff --git a/sharc/parameters/parameters_fs.py b/sharc/parameters/parameters_fs.py index c1332b932..c5ed8836a 100644 --- a/sharc/parameters/parameters_fs.py +++ b/sharc/parameters/parameters_fs.py @@ -1,14 +1,91 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Aug 9 19:35:52 2017 +from dataclasses import dataclass +from sharc.parameters.parameters_base import ParametersBase -@author: edgar -""" -class ParametersFs(object): +@dataclass +class ParametersFs(ParametersBase): """ - Simulation parameters for Fixed Services + Parameters definitions for fixed wireless service systems. """ - def __init__(self): - pass + section_name: str = "fs" + + # x-y coordinates [meters] + x: float = 1000.0 + y: float = 0.0 + + # Antenna height [meters] + height: float = 15.0 + + # Elevation angle [degrees] + elevation: float = -10.0 + + # Azimuth angle [degrees] + azimuth: float = 180.0 + + # Center frequency [MHz] + frequency: float = 27250.0 + + # Bandwidth [MHz] + bandwidth: float = 112.0 + + # System receive noise temperature [Kelvin] + noise_temperature: float = 290.0 + + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: float = 20.0 + + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: float = -68.3 + + # Antenna peak gain [dBi] + antenna_gain: float = 36.9 + + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R F.699", "OMNI" + antenna_pattern: str = "ITU-R F.699" + + # Diameter of antenna [meters] + diameter: float = 0.3 + + # Channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + channel_model: str = "FSPL" + + def load_parameters_from_file(self, config_file: str): + """ + Load the parameters from a file and run a sanity check. + + Parameters + ---------- + config_file : str + The path to the configuration file. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + super().load_parameters_from_file(config_file) + + # Implementing sanity checks for critical parameters + if not (-90 <= self.elevation <= 90): + raise ValueError( + "Elevation angle must be between -90 and 90 degrees.", + ) + + if not (0 <= self.azimuth <= 360): + raise ValueError( + "Azimuth angle must be between 0 and 360 degrees.", + ) + + if self.antenna_pattern not in ["ITU-R F.699", "OMNI"]: + raise ValueError( + f"Invalid antenna_pattern: {self.antenna_pattern}", + ) + + # Sanity check for channel model + if self.channel_model not in ["FSPL", "TerrestrialSimple"]: + raise ValueError( + "Invalid channel_model, must be either 'FSPL' or 'TerrestrialSimple'", + ) diff --git a/sharc/parameters/parameters_fss_es.py b/sharc/parameters/parameters_fss_es.py index 8ce8f2414..6085fe87c 100644 --- a/sharc/parameters/parameters_fss_es.py +++ b/sharc/parameters/parameters_fss_es.py @@ -1,14 +1,211 @@ # -*- coding: utf-8 -*- -""" -Created on Mon Jul 24 15:30:49 2017 +from dataclasses import dataclass -@author: edgar -""" +from sharc.support.sharc_utils import is_float +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_p452 import ParametersP452 +from sharc.parameters.parameters_p619 import ParametersP619 -class ParametersFssEs(object): - """ - Simulation parameters for FSS Earth Station. + +@dataclass +class ParametersFssEs(ParametersBase): + """Dataclass containing the Fixed Satellite Services - Earth Station + parameters for the simulator """ - - def __init__(self): - pass \ No newline at end of file + section_name: str = "fss_es" + + # type of FSS-ES location: + # FIXED - position must be given + # CELL - random within central cell + # NETWORK - random within whole network + # UNIFORM_DIST - uniform distance from cluster centre, + # between min_dist_to_bs and max_dist_to_bs + location: str = "UNIFORM_DIST" + # x-y coordinates [m] (only if FIXED location is chosen) + x: float = 10000.0 + y: float = 0.0 + # minimum distance from BSs [m] + min_dist_to_bs: float = 10.0 + # maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen) + max_dist_to_bs: float = 10.0 + # antenna height [m] + height: float = 6.0 + # Elevation angle [deg], minimum and maximum, values + elevation_min: float = 48.0 + elevation_max: float = 80.0 + # Azimuth angle [deg] + # either a specific angle or string 'RANDOM' + azimuth: str = 0.2 + # center frequency [MHz] + frequency: float = 43000.0 + # bandwidth [MHz] + bandwidth: float = 6.0 + # adjacent channel selectivity (dB) + adjacent_ch_selectivity: float = 0.0 + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: float = -68.3 + # System receive noise temperature [K] + noise_temperature: float = 950.0 + # antenna peak gain [dBi] + antenna_gain: float = 0.0 + # Antenna pattern of the FSS Earth station + # Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", + # "Modified ITU-R S.465" + antenna_pattern: str = "Modified ITU-R S.465" + # Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model + antenna_envelope_gain: float = 0.0 + # Diameter of the antenna [m] + diameter: float = 1.8 + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + # "P452" + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "HDFSS" + channel_model: str = "P452" + + # P452 parameters + param_p452 = ParametersP452() + # Total air pressure in hPa + atmospheric_pressure: float = 935.0 + # Temperature in Kelvin + air_temperature: float = 300.0 + # Sea-level surface refractivity (use the map) + N0: float = 352.58 + # Average radio-refractive (use the map) + delta_N: float = 43.127 + # Percentage p. Float (0 to 100) or RANDOM + percentage_p: str = "0.2" + # Distance over land from the transmit and receive antennas to the coast (km) + Dct: float = 70.0 + # Distance over land from the transmit and receive antennas to the coast (km) + Dcr: float = 70.0 + # Effective height of interfering antenna (m) + Hte: float = 20.0 + # Effective height of interfered-with antenna (m) + Hre: float = 3.0 + # Latitude of transmitter + tx_lat: float = -23.55028 + # Latitude of receiver + rx_lat: float = -23.17889 + # Antenna polarization + polarization: str = "horizontal" + # Determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: bool = True + + # Parameters for the P.619 propagation model used for sharing studies between IMT-NTN and FSS-ES + # space_station_alt_m - altiteude of the IMT-MSS station + # earth_station_alt_m - altitude of FSS-ES system (in meters) + # earth_station_lat_deg - latitude of FSS-ES system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT-NTN station and FSS-ES system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + param_p619 = ParametersP619() + space_station_alt_m: float = 35780000.0 + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + + # HDFSS propagation parameters + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: str = "ROOFTOP" + # Enable shadowing loss + shadow_enabled: bool = True + # Enable building entry loss + building_loss_enabled: bool = True + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: bool = False + # Enable diffraction loss + diffraction_enabled: bool = False + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: str = "P2109_FIXED" + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: float = 0.75 + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: float = 35 + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + + if self.location not in ["FIXED", "CELL", "NETWORK", "UNIFORM_DIST"]: + raise ValueError(f"ParametersFssEs: \ + Invalid value for paramter location - {self.location}. \ + Allowed values are \"FIXED\", \"CELL\", \"NETWORK\", \"UNIFORM_DIST\".") + + if self.antenna_pattern not in [ + "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", + "Modified ITU-R S.465", + ]: + raise ValueError(f"ParametersFssEs: \ + Invalid value for paramter antenna_pattern - {self.antenna_pattern}. \ + Allowed values are \ + \"ITU-R S.1855\", \"ITU-R S.465\", \"ITU-R S.580\", \"OMNI\", \ + \"Modified ITU-R S.465\"") + + if is_float(self.azimuth): + self.azimuth = float(self.azimuth) + elif self.azimuth.upper() != "RANDOM": + if self.azimuth.isnumeric(): + self.azimuth = float(self.azimuth) + else: + raise ValueError(f"""ParametersFssEs: + Invalid value for parameter azimuth - {self.azimuth}. + Allowed values are \"RANDOM\" or a angle in degrees.""") + + if isinstance(self.percentage_p, str) and self.percentage_p.upper() != "RANDOM": + percentage_p = float(self.percentage_p) + if percentage_p <= 0 or percentage_p > 1: + raise ValueError(f"""ParametersFssEs: + Invalid value for parameter percentage_p - {self.percentage_p}. + Allowed values are a percentage between 0 and 1 (exclusive).""") + + if self.polarization.lower() not in ["horizontal", "vertical"]: + raise ValueError(f"ParametersFssEss: \ + Invalid value for parameter polarization - {self.polarization}. \ + Allowed values are: \"horizontal\", \"vertical\"") + + if self.es_position.upper() not in ["BUILDINGSIDE", "ROOFTOP"]: + raise ValueError(f"ParametersFssEss: \ + Invalid value for parameter es_position - {self.es_position} \ + Allowed values are \"BUILDINGSIDE\", \"ROOFTOP\".") + + if self.bs_building_entry_loss_type not in ["P2109_RANDOM", "P2109_FIXED", "FIXED_VALUE"]: + raise ValueError(f"ParametersFssEs: \ + Invalid value for parameter bs_building_entry_loss_type - \ + {self.bs_building_entry_loss_type} \ + Allowd values are \"P2109_RANDOM\", \"P2109_FIXED\", \"FIXED_VALUE\".") + if self.channel_model.upper() not in [ + "FSPL", "TERRESTRIALSIMPLE", "P452", "P619", + "TVRO-URBAN", "TVRO-SUBURBAN", "HDFSS", "UMA", "UMI", + ]: + raise ValueError( + f"ParametersFssEs: Invalid value for parameter channel_model - {self.channel_model}", + ) + + if self.channel_model == "P452": + self.param_p452.load_from_paramters(self) + + elif self.channel_model == "P619": + self.param_p619.load_from_paramters(self) diff --git a/sharc/parameters/parameters_fss_ss.py b/sharc/parameters/parameters_fss_ss.py index 37b0a6085..efcd2dadc 100644 --- a/sharc/parameters/parameters_fss_ss.py +++ b/sharc/parameters/parameters_fss_ss.py @@ -1,11 +1,102 @@ # -*- coding: utf-8 -*- -""" -Created on Thu Apr 13 13:16:02 2017 +from dataclasses import dataclass, field -@author: edgar -""" +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_p619 import ParametersP619 +from sharc.parameters.antenna.parameters_antenna_s1528 import ParametersAntennaS1528 -class ParametersFssSs(object): - def __init__(self): - pass \ No newline at end of file +@dataclass +class ParametersFssSs(ParametersBase): + """Dataclass containing the Fixed Satellite Services - Space Station + parameters for the simulator + """ + section_name: str = "fss_ss" + nested_parameters_enabled: bool = True + is_space_to_earth: bool = True + # satellite center frequency [MHz] + frequency: float = 43000.0 + # satellite bandwidth [MHz] + bandwidth: float = 200.0 + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: float = -5.0 + # satellite altitude [m] + altitude: float = 35780000.0 + # satellite latitude [deg] + lat_deg: float = 0.0 + # Elevation angle [deg] + elevation: float = 270.0 + # Azimuth angle [deg] + azimuth: float = 0.0 + # System receive noise temperature [K] + noise_temperature: float = 950.0 + # Adjacent channel selectivity (dB) + adjacent_ch_selectivity: float = 0.0 + + # Antenna parameters + # Antenna pattern of the FSS space station + # Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" + antenna_pattern: str = "ITU-R S.672" + ############################ + # Satellite peak receive antenna gain [dBi] + antenna_gain: float = 46.6 + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s: float = -20.0 + # Parameters if antenna_pattern = ITU_R S.1528 + antenna_s1528: ParametersAntennaS1528 = field(default_factory=ParametersAntennaS1528) + + ############################ + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + param_p619 = ParametersP619() + space_station_alt_m: float = 35780000.0 + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model: str = "P619" + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB: float = 0.65 + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + + # Now do the sanity check for some parameters + if self.antenna_pattern not in ["ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI"]: + raise ValueError(f"ParametersFssSs: \ + invalid value for parameter antenna_pattern - {self.antenna_pattern}. \ + Possible values \ + are \"ITU-R S.672\", \"ITU-R S.1528\", \"FSS_SS\", \"OMNI\"") + + if self.season.upper() not in ["SUMMER", "WINTER"]: + raise ValueError(f"ParametersFssSs: \ + Invalid value for parameter season - {self.season}. \ + Possible values are \"SUMMER\", \"WINTER\".") + + if self.channel_model.upper() not in ["FSPL", "SATELLITESIMPLE", "P619"]: + raise ValueError(f"ParametersFssSs: \ + Invalid value for paramter channel_model = {self.channel_model}. \ + Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".") + self.param_p619.load_from_paramters(self) + self.antenna_s1528.set_external_parameters(self.frequency, self.bandwidth, self.antenna_gain, self.antenna_l_s) diff --git a/sharc/parameters/parameters_general.py b/sharc/parameters/parameters_general.py index 77873f0f9..a040358c4 100644 --- a/sharc/parameters/parameters_general.py +++ b/sharc/parameters/parameters_general.py @@ -1,11 +1,45 @@ # -*- coding: utf-8 -*- -""" -Created on Wed Feb 15 16:05:46 2017 +from dataclasses import dataclass -@author: edgar -""" +from sharc.sharc_definitions import SHARC_IMPLEMENTED_SYSTEMS +from sharc.parameters.parameters_base import ParametersBase -class ParametersGeneral(object): - def __init__(self): - pass \ No newline at end of file +@dataclass +class ParametersGeneral(ParametersBase): + """Dataclass containing the general parameters for the simulator + """ + section_name: str = "general" + num_snapshots: int = 10000 + imt_link: str = "DOWNLINK" + system: str = "RAS" + enable_cochannel: bool = False + enable_adjacent_channel: bool = True + seed: int = 101 + overwrite_output: bool = True + output_dir: str = "output" + output_dir_prefix: str = "output" + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + + # Now do the sanity check for some parameters + if self.imt_link.upper() not in ["DOWNLINK", "UPLINK"]: + raise ValueError(f"ParametersGeneral: \ + Invalid value for parameter imt_link - {self.imt_link} \ + Possible values are DOWNLINK and UPLINK") + + if self.system not in SHARC_IMPLEMENTED_SYSTEMS: + raise ValueError(f"Invalid system name {self.system}") diff --git a/sharc/parameters/parameters_haps.py b/sharc/parameters/parameters_haps.py index 251a486df..991e9aedb 100644 --- a/sharc/parameters/parameters_haps.py +++ b/sharc/parameters/parameters_haps.py @@ -1,15 +1,82 @@ # -*- coding: utf-8 -*- -""" -Created on Thu Oct 19 12:32:30 2017 +from dataclasses import dataclass -@author: edgar -""" +from sharc.parameters.parameters_base import ParametersBase -class ParametersHaps(object): +@dataclass +class ParametersHaps(ParametersBase): + """Dataclass containing the IMT system parameters """ - Simulation parameters for HAPS (airbone) platform. - """ - - def __init__(self): - pass \ No newline at end of file + section_name: str = "haps" + # HAPS center frequency [MHz] + frequency: float = 27250.0 + # HAPS bandwidth [MHz] + bandwidth: float = 200.0 + # HAPS peak antenna gain [dBi] + antenna_gain: float = 28.1 + # EIRP spectral density [dBW/MHz] + eirp_density: float = 4.4 + # tx antenna power density [dBW/MHz] + tx_power_density: float = eirp_density - antenna_gain - 60 + # HAPS altitude [m] and latitude [deg] + altitude: float = 20000.0 + lat_deg: float = 0.0 + # Elevation angle [deg] + elevation: float = 270.0 + # Azimuth angle [deg] + azimuth: float = 0.0 + # Antenna pattern of the HAPS (airbone) station + # Possible values: "ITU-R F.1891", "OMNI" + antenna_pattern: str = "ITU-R F.1891" + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + # Adjacent channel selectivity [dB] + acs: float = 30.0 + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model: str = "P619" + # Near side-lobe level (dB) relative to the peak gain required by the system + # design, and has a maximum value of โˆ’25 dB + antenna_l_n: float = -25.0 + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + if self.antenna_pattern not in ["ITU-R F.1891", "OMNI"]: + raise ValueError(f"ParametersHaps: \ + Invalid value for parameter {self.antenna_pattern}. \ + Allowed values are \"ITU-R F.1891\", \"OMNI\".") + if self.channel_model.upper() not in ["FSPL", "SatelliteSimple", "P619"]: + raise ValueError(f"ParametersHaps: \ + Invalid value for paramter channel_model = {self.channel_model}. \ + Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".") + if self.season.upper() not in ["SUMMER", "WINTER"]: + raise ValueError(f"ParametersHaps: \ + Invalid value for parameter season - {self.season}. \ + Possible values are \"SUMMER\", \"WINTER\".") + + # Post initialization + # tx antenna power density [dBW/MHz] + self.tx_power_density = self.eirp_density - self.antenna_gain - 60 diff --git a/sharc/parameters/parameters_hdfss.py b/sharc/parameters/parameters_hdfss.py new file mode 100644 index 000000000..4f878f1ba --- /dev/null +++ b/sharc/parameters/parameters_hdfss.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersHDFSS(ParametersBase): + """ + Dataclass containing the HDFSS (High-Density Fixed Satellite System Propagation Model) + propagation model parameters + """ + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: str = "ROOFTOP" + # Enable shadowing loss + shadow_enabled: bool = True + # Enable building entry loss + building_loss_enabled: bool = True + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: bool = False + # Enable diffraction loss + diffraction_enabled: bool = False + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: str = "P2109_FIXED" + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: float = 0.75 + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: float = 35 diff --git a/sharc/parameters/parameters_hotspot.py b/sharc/parameters/parameters_hotspot.py deleted file mode 100644 index 4479d375f..000000000 --- a/sharc/parameters/parameters_hotspot.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed May 17 15:47:05 2017 - -@author: edgar -""" - -class ParametersHotspot(object): - - def __init__(self): - pass \ No newline at end of file diff --git a/sharc/parameters/parameters_imt.py b/sharc/parameters/parameters_imt.py deleted file mode 100644 index 4438bb4f5..000000000 --- a/sharc/parameters/parameters_imt.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Feb 15 16:05:58 2017 - -@author: edgar -""" - -class ParametersImt(object): - - def __init__(self): - pass - diff --git a/sharc/parameters/parameters_indoor.py b/sharc/parameters/parameters_indoor.py deleted file mode 100644 index 3dd695a8e..000000000 --- a/sharc/parameters/parameters_indoor.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Dec 5 17:50:05 2017 - -@author: edgar -""" - -class ParametersIndoor(object): - """ - Simulation parameters for indoor network topology. - """ - - def __init__(self): - pass \ No newline at end of file diff --git a/sharc/parameters/parameters_metsat_ss.py b/sharc/parameters/parameters_metsat_ss.py new file mode 100644 index 000000000..850fad364 --- /dev/null +++ b/sharc/parameters/parameters_metsat_ss.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass + +from sharc.parameters.parameters_space_station import ParametersSpaceStation + +# The default values come from Report ITU-R SA.2488-0, table 19 (the only earth-to-space MetSat entry) +# TODO: let MetSat as interferrer +# TODO: ver com professor se considerar as tabelas do report estรก correto + + +@dataclass +class ParametersMetSatSS(ParametersSpaceStation): + """ + Defines parameters for MetSat space stations (SS) + and their interaction with other services based on ITU recommendations. + """ + section_name: str = "mestat_ss" + + # raw data transmission in 8175-8215 range + frequency: float = 8195.0 # Satellite center frequency [MHz] + bandwidth: float = 20.0 + + # Elevation angle [deg] + elevation: float = 3 # Minimum == 3 + + # Satellite altitude [m] + # Altitude of the satellite above the Earth's surface in meters + altitude: float = 35786000.0 # 35786000 for GSO + + # Antenna pattern of the satellite + # Possible values: "ITU-R S.672" + antenna_pattern: str = "ITU-R S.672" # Antenna radiation pattern + + # antenna peak gain [dBi] + antenna_gain: float = 52.0 + + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + # TODO: check if this changes from fss_ss + antenna_l_s: float = -20.0 + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB: float = 0.65 + + # Channel model, possible values are "FSPL" (free-space path loss), "P619" + # See params space station to check P619 needed params + channel_model: str = "FSPL" # Channel model to be used + + def load_parameters_from_file(self, config_file: str): + """ + Load the parameters from a file and run a sanity check. + + Parameters + ---------- + config_file : str + The path to the configuration file. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + super().load_parameters_from_file(config_file) + print(self.antenna_pattern) + if self.antenna_pattern not in ["ITU-R S.672"]: + raise ValueError(f"Invalid antenna_pattern: { + self.antenna_pattern + }") + + # Check channel model + if self.channel_model not in ["FSPL", "P619"]: + raise ValueError( + "Invalid channel_model, must be either 'FSPL' or 'P619'", + ) + + # Check season + if self.season not in ["SUMMER", "WINTER"]: + raise ValueError( + "Invalid season, must be either 'SUMMER' or 'WINTER'", + ) diff --git a/sharc/parameters/parameters_p1411.py b/sharc/parameters/parameters_p1411.py new file mode 100644 index 000000000..466e326dc --- /dev/null +++ b/sharc/parameters/parameters_p1411.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersP1411(ParametersBase): + """Dataclass containing the P.1411-2 propagation model parameters + """ + environment: str = 'URBAN' + frequency: float = 7 + #Corner loss in dB + l_corner: float = 20 + #reference distance for nlos case + ref_dist: float = 1 + + d_corner: float = 30 + #street width at the position of the Station 1 (m) + street_width1: float = 0 + #street width at the position of the Station 2 (m) + street_width2: float = 20 + #distance Station 1 to street crossing (m) + distance1: float = 0 + #distance Station 2 to street crossing (m) + distance2: float = 2.0 + #is the corner angle (rad) + corner_angle: float = 0 + #Basic transmission loss exponent + n: float = 2 + + def load_from_paramters(self, param: ParametersBase): + """Used to load parameters of P.1411-2 from IMT or system parameters + + Parameters + ---------- + param : ParametersBase + IMT or system parameters + """ + self.environment = param.environment + self.l_corner = param.l_corner + self.d_corner = param.d_corner + self.street_width1 = param.street_width1 + self.street_width2 = param.street_width2 + self.distance1 = param.distance1 + self.distance2 = param.distance2 + self.corner_angle = param.corner_angle + self.ref_dist = param.ref_dist + self.n = param.n \ No newline at end of file diff --git a/sharc/parameters/parameters_p452.py b/sharc/parameters/parameters_p452.py new file mode 100644 index 000000000..d26a11422 --- /dev/null +++ b/sharc/parameters/parameters_p452.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersP452(ParametersBase): + """Dataclass containing the P.452 propagation model parameters + """ + # Total air pressure in hPa + atmospheric_pressure: float = 935.0 + # Temperature in Kelvin + air_temperature: float = 300.0 + # Sea-level surface refractivity (use the map) + N0: float = 352.58 + # Average radio-refractive (use the map) + delta_N: float = 43.127 + # percentage p. Float (0 to 100) or RANDOM + percentage_p: float = 0.2 + # Distance over land from the transmit and receive antennas to the coast (km) + Dct: float = 70.0 + # Distance over land from the transmit and receive antennas to the coast (km) + Dcr: float = 70.0 + # Effective height of interfering antenna (m) + Hte: float = 20.0 + # Effective height of interfered-with antenna (m) + Hre: float = 3.0 + # Latitude of transmitter + tx_lat: float = -23.55028 + # Latitude of receiver + rx_lat: float = -23.17889 + # Antenna polarization + polarization: str = "horizontal" + # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: bool = True + + def load_from_paramters(self, param: ParametersBase): + """Used to load parameters of P.452 from IMT or system parameters + + Parameters + ---------- + param : ParametersBase + IMT or system parameters + """ + self.atmospheric_pressure = param.atmospheric_pressure + self.air_temperature = param.air_temperature + self.N0 = param.N0 + self.delta_N = param.delta_N + self.percentage_p = param.percentage_p + self.Dct = param.Dct + self.Dcr = param.Dcr + self.Hte = param.Hte + self.Hre = param.Hre + self.tx_lat = param.tx_lat + self.rx_lat = param.rx_lat + self.polarization = param.polarization + self.clutter_loss = param.clutter_loss diff --git a/sharc/parameters/parameters_p619.py b/sharc/parameters/parameters_p619.py new file mode 100644 index 000000000..4bd5ab768 --- /dev/null +++ b/sharc/parameters/parameters_p619.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Object that loads the parameters for the P.619 propagation model. +"""Parameters definitions for IMT systems +""" +from dataclasses import dataclass + +from sharc.parameters.parameters_base import ParametersBase + + +@dataclass +class ParametersP619(ParametersBase): + """Dataclass containing the P.619 propagation model parameters + """ + # Parameters for the P.619 propagation model + # For IMT NTN the model is used for calculating the coupling loss between + # the BS space station and the UEs on Earth's surface. + # For now, the NTN footprint is centered over the BS nadir point, therefore + # the paramters imt_lag_deg and imt_long_diff_deg SHALL be zero. + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + space_station_alt_m: float = 20000.0 + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + shadowing: bool = True + noise_temperature: float = 290.0 + + def load_from_paramters(self, param: ParametersBase): + """Used to load parameters of P.619 from IMT or system parameters + + Parameters + ---------- + param : ParametersBase + IMT or system parameters + """ + self.space_station_alt_m = param.space_station_alt_m + self.earth_station_alt_m = param.earth_station_alt_m + self.earth_station_lat_deg = param.earth_station_lat_deg + self.earth_station_long_diff_deg = param.earth_station_long_diff_deg + self.season = param.season + + if self.season.upper() not in ["SUMMER", "WINTER"]: + raise ValueError(f"{self.__class__.__name__}: \ + Invalid value for parameter season - {self.season}. \ + Possible values are \"SUMMER\", \"WINTER\".") diff --git a/sharc/parameters/parameters_ras.py b/sharc/parameters/parameters_ras.py index 1ebbf20e2..9a9956eda 100644 --- a/sharc/parameters/parameters_ras.py +++ b/sharc/parameters/parameters_ras.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -""" -Created on Thu Nov 16 17:11:06 2017 +from dataclasses import dataclass +import numpy as np -@author: Calil -""" +from sharc.parameters.parameters_single_earth_station import ParametersSingleEarthStation -class ParametersRas(object): + +@dataclass +class ParametersRas(ParametersSingleEarthStation): """ Simulation parameters for Radio Astronomy Service """ - - def __init__(self): - pass \ No newline at end of file + section_name: str = "ras" + polarization_loss: float = 0.0 diff --git a/sharc/parameters/parameters_rns.py b/sharc/parameters/parameters_rns.py index e38f8efdb..0ed01de96 100644 --- a/sharc/parameters/parameters_rns.py +++ b/sharc/parameters/parameters_rns.py @@ -1,14 +1,74 @@ # -*- coding: utf-8 -*- -""" -Created on Wed Dec 20 16:27:43 2017 +from dataclasses import dataclass -@author: edgar -""" +from sharc.parameters.parameters_base import ParametersBase -class ParametersRns(object): + +@dataclass +class ParametersRns(ParametersBase): """ Simulation parameters for radionavigation service """ - - def __init__(self): - pass \ No newline at end of file + section_name: str = "rns" + # x-y coordinates [m] + x: float = 660.0 + y: float = -370.0 + # altitude [m] + altitude: float = 150.0 + # center frequency [MHz] + frequency: float = 32000.0 + # bandwidth [MHz] + bandwidth: float = 60.0 + # System receive noise temperature [K] + noise_temperature: float = 1154.0 + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: float = -70.79 + # antenna peak gain [dBi] + antenna_gain: float = 30.0 + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R M.1466", "OMNI" + antenna_pattern: str = "ITU-R M.1466" + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 dB + clutter loss) + # "P619" + channel_model: str = "P619" + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + # Adjacent channel selectivity [dB] + acs: float = 30.0 + + def load_parameters_from_file(self, config_file: str): + """Load the parameters from file an run a sanity check + + Parameters + ---------- + file_name : str + the path to the configuration file + + Raises + ------ + ValueError + if a parameter is not valid + """ + super().load_parameters_from_file(config_file) + if self.antenna_pattern not in ["ITU-R M.1466", "OMNI"]: + raise ValueError(f"ParametersRns: \ + Invalid value for parameter {self.antenna_pattern}. \ + Allowed values are \"ITU-R M.1466\", \"OMNI\".") + if self.channel_model.upper() not in ["FSPL", "SatelliteSimple", "P619"]: + raise ValueError(f"ParametersRns: \ + Invalid value for paramter channel_model = {self.channel_model}. \ + Possible values are \"FSPL\", \"SatelliteSimple\", \"P619\".") + if self.season.upper() not in ["SUMMER", "WINTER"]: + raise ValueError(f"ParametersRns: \ + Invalid value for parameter season - {self.season}. \ + Possible values are \"SUMMER\", \"WINTER\".") diff --git a/sharc/parameters/parameters_single_earth_station.py b/sharc/parameters/parameters_single_earth_station.py new file mode 100644 index 000000000..8c5816637 --- /dev/null +++ b/sharc/parameters/parameters_single_earth_station.py @@ -0,0 +1,235 @@ +import typing +from dataclasses import dataclass, field + +from sharc.parameters.parameters_antenna import ParametersAntenna +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_hdfss import ParametersHDFSS +from sharc.parameters.parameters_p452 import ParametersP452 +from sharc.parameters.parameters_p619 import ParametersP619 + + +@dataclass +class ParametersSingleEarthStation(ParametersBase): + """ + Defines parameters for passive Earth Exploration Satellite Service (EESS) sensors + and their interaction with other services based on ITU recommendations. + """ + section_name: str = "single_earth_station" + nested_parameters_enabled: bool = True + + # Sensor center frequency [MHz] + frequency: float = None # Center frequency of the sensor in MHz + + # Sensor bandwidth [MHz] + bandwidth: float = None # Bandwidth of the sensor in MHz + + # System receive noise temperature [K] + noise_temperature: float = None + + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: float = None + + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: float = None + + # Antenna pattern of the sensor + antenna: ParametersAntenna = field(default_factory=ParametersAntenna) + + # Channel model, possible values are "FSPL" (free-space path loss), "P619" + channel_model: typing.Literal[ + "FSPL", "P619", + "P452", + ] = "FSPL" # Channel model to be used + + param_p619: ParametersP619 = field(default_factory=ParametersP619) + # TODO: remove season from system parameter and put it as p619 parameter + season: typing.Literal["WINTER", "SUMMER"] = "SUMMER" + + param_p452: ParametersP452 = field(default_factory=ParametersP452) + + param_hdfss: ParametersHDFSS = field(default_factory=ParametersHDFSS) + + @dataclass + class EarthStationGeometry(ParametersBase): + height: float = None + + @dataclass + class FixedOrUniformDist(ParametersBase): + __EXISTING_TYPES = ["UNIFORM_DIST", "FIXED"] + type: typing.Literal["UNIFORM_DIST", "FIXED"] = None + fixed: float = None + + @dataclass + class UniformDistParams(ParametersBase): + min: float = None + max: float = None + + def validate(self, ctx): + if not isinstance(self.min, int) and not isinstance(self.min, float): + raise ValueError( + f"{ctx}.min parameter should be a number", + ) + if not isinstance(self.max, int) and not isinstance(self.max, float): + raise ValueError( + f"{ctx}.max parameter should be a number", + ) + if self.max < self.min: + raise ValueError( + f"{ctx}.max parameter should be greater than or equal to {ctx}.min", + ) + uniform_dist: UniformDistParams = field( + default_factory=UniformDistParams, + ) + + def validate(self, ctx): + if self.type not in self.__EXISTING_TYPES: + raise ValueError( + f"Invalid value for {ctx}.type. Should be one of {self.__EXISTING_TYPES}", + ) + + match self.type: + case "UNIFORM_DIST": + self.uniform_dist.validate(f"{ctx}.uniform_dist") + case "FIXED": + if not isinstance(self.fixed, int) and not isinstance(self.fixed, float): + raise ValueError(f"{ctx}.fixed should be a number") + case _: + raise NotImplementedError( + f"Validation for {ctx}.type = {self.type} is not implemented", + ) + + azimuth: FixedOrUniformDist = field(default_factory=FixedOrUniformDist) + elevation: FixedOrUniformDist = field( + default_factory=FixedOrUniformDist, + ) + + @dataclass + class Location(ParametersBase): + __EXISTING_TYPES = ["FIXED", "CELL", "NETWORK", "UNIFORM_DIST"] + type: typing.Literal[ + "FIXED", "CELL", + "NETWORK", "UNIFORM_DIST", + ] = None + + @dataclass + class LocationFixed(ParametersBase): + x: float = None + y: float = None + + def validate(self, ctx): + if not isinstance(self.x, int) and not isinstance(self.x, float): + raise ValueError(f"{ctx}.x needs to be a number") + if not isinstance(self.y, int) and not isinstance(self.y, float): + raise ValueError(f"{ctx}.y needs to be a number") + + @dataclass + class LocationDistributed(ParametersBase): + min_dist_to_bs: float = None + + def validate(self, ctx): + if not isinstance(self.min_dist_to_bs, int) and not isinstance(self.min_dist_to_bs, float): + raise ValueError( + f"{ctx}.min_dist_to_bs needs to be a number", + ) + + @dataclass + class LocationDistributedWithinCircle(ParametersBase): + min_dist_to_center: float = None + max_dist_to_center: float = None + + def validate(self, ctx): + if not isinstance(self.min_dist_to_center, int) and not isinstance(self.min_dist_to_center, float): + raise ValueError( + f"{ctx}.min_dist_to_center needs to be a number", + ) + if not isinstance(self.max_dist_to_center, int) and not isinstance(self.max_dist_to_center, float): + raise ValueError( + f"{ctx}.max_dist_to_center needs to be a number", + ) + + fixed: LocationFixed = field(default_factory=LocationFixed) + cell: LocationDistributed = field( + default_factory=LocationDistributed, + ) + network: LocationDistributed = field( + default_factory=LocationDistributed, + ) + uniform_dist: LocationDistributedWithinCircle = field( + default_factory=LocationDistributedWithinCircle, + ) + + def validate(self, ctx): + match self.type: + case "FIXED": + self.fixed.validate(f"{ctx}.fixed") + case "CELL": + self.cell.validate(f"{ctx}.cell") + case "NETWORK": + self.network.validate(f"{ctx}.network") + case "UNIFORM_DIST": + self.uniform_dist.validate(f"{ctx}.uniform_dist") + case _: + raise NotImplementedError( + f"ParametersSingleEarthStation.Location.type = {self.type} has no validation implemented!", + ) + + location: Location = field(default_factory=Location) + + geometry: EarthStationGeometry = field( + default_factory=EarthStationGeometry, + ) + + def load_parameters_from_file(self, config_file: str): + """ + Load the parameters from a file and run a sanity check. + + Parameters + ---------- + config_file : str + The path to the configuration file. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + super().load_parameters_from_file(config_file) + + # this is needed because nested parameters + # don't know/cannot access parents attributes + self.antenna.set_external_parameters( + frequency=self.frequency, + ) + + # this parameter is required in system get description + self.antenna_pattern = self.antenna.pattern + + # this should be done by validating this parameters only if it is the selected system on the general section + # TODO: make this better by changing the Parameters class itself + should_validate = any( + v is not None for v in [ + self.frequency, self.bandwidth, + ] + ) + + if should_validate: + self.validate(self.section_name) + + def validate(self, ctx="single_earth_station"): + super().validate(ctx) + + if None in [self.frequency, self.bandwidth, self.channel_model]: + raise ValueError( + "ParametersSingleEarthStation required parameters are not all set", + ) + + if self.season not in ["WINTER", "SUMMER"]: + raise ValueError( + f"{ctx}.season needs to be either 'WINTER' or 'SUMMER'", + ) + + if self.channel_model not in ["FSPL", "P619", "P452", "TerrestrialSimple", "TVRO-URBAN", "TVRO-SUBURBAN", "P1411"]: + raise ValueError( + f"{ctx}.channel_model" + + "needs to be in ['FSPL', 'P619', 'P452', 'TerrestrialSimple', 'TVRO-URBAN', 'TVRO-SUBURBAN']", + ) diff --git a/sharc/parameters/parameters_space_station.py b/sharc/parameters/parameters_space_station.py new file mode 100644 index 000000000..8d4493124 --- /dev/null +++ b/sharc/parameters/parameters_space_station.py @@ -0,0 +1,136 @@ +from dataclasses import dataclass +from sharc.parameters.parameters_base import ParametersBase +from sharc.parameters.parameters_p619 import ParametersP619 +import math +from sharc.parameters.constants import EARTH_RADIUS + + +@dataclass +class ParametersSpaceStation(ParametersBase): + """ + Defines parameters that should be used for orbiting Space Stations. + TODO: use for FSS_SS in the future as well. + """ + section_name: str = "space_station" + is_space_to_earth: bool = True + + # Satellite center frequency [MHz] + frequency: float = 0.0 # Center frequency of the satellite in MHz + + # Satellite bandwidth [MHz] + bandwidth: float = 0.0 # Bandwidth of the satellite in MHz + + # Off-nadir pointing angle [deg]. Should only be set if elevation is not set + nadir_angle: float = 0.0 # Angle in degrees away from the nadir point + # Elevation angle [deg]. Should only be set if nadir_angle is not set + elevation: float = 0.0 + + # Satellite altitude [m] + altitude: float = 0.0 # Altitude of the satellite above the Earth's surface in meters + + # satellite latitude [deg] + lat_deg: float = 0.0 + + # Antenna pattern of the satellite + antenna_pattern: str | None = None # Antenna radiation pattern + + # Antenna efficiency for pattern + # Efficiency factor for the antenna, range from 0 to 1 + antenna_efficiency: float = 0.0 + + # Antenna diameter + antenna_diameter: float = 0.0 # Diameter of the antenna in meters + + # Receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain: float = 0.0 # Gain of the antenna in dBi + + # Channel model, possible values are "FSPL" (free-space path loss), "P619" + channel_model: str = "FSPL" # Channel model to be used + + # Parameters for the P.619 propagation model + # earth_station_alt_m - altitude of IMT system (in meters) + # earth_station_lat_deg - latitude of IMT system (in degrees) + # earth_station_long_diff_deg - difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + # season - season of the year. + param_p619 = ParametersP619() + earth_station_alt_m: float = 0.0 + earth_station_lat_deg: float = 0.0 + earth_station_long_diff_deg: float = 0.0 + season: str = "SUMMER" + + # This parameter is also used by P619, but should not be set manually. + # this should always == params.altitude + space_station_alt_m: float = None + + def load_parameters_from_file(self, config_file: str): + """ + Load the parameters from a file and run a sanity check. + + Parameters + ---------- + config_file : str + The path to the configuration file. + + Raises + ------ + ValueError + If a parameter is not valid. + """ + super().load_parameters_from_file(config_file) + + if self.space_station_alt_m is not None: + raise ValueError( + "'space_station_alt_m' should not be set manually. It is always equal to 'altitude'", + ) + + if self.nadir_angle != 0 and self.elevation != 0: + raise ValueError("'elevation' and 'nadir_angle' should not both be set at the same time. Choose either\ + parameter to set") + + # Check channel model + if self.channel_model not in ["FSPL", "P619"]: + raise ValueError( + "Invalid channel_model, must be either 'FSPL' or 'P619'", + ) + + if self.channel_model == "P619": + # check necessary parameters for P619 + if None in [ + self.param_p619, self.earth_station_alt_m, self.earth_station_lat_deg, self.earth_station_long_diff_deg, + ]: + raise ValueError("When using P619 should set 'self.earth_station_alt_m', 'self.earth_station_lat_deg',\ + 'self.earth_station_long_diff_deg' deafult or on parameter file and 'param_p619' \ + on child class default") + # Check season + if self.season not in ["SUMMER", "WINTER"]: + raise ValueError( + "Invalid season, must be either 'SUMMER' or 'WINTER'", + ) + + self.set_derived_parameters() + + def set_derived_parameters(self): + self.space_station_alt_m = self.altitude + + if self.param_p619: + self.param_p619.load_from_paramters(self) + + if self.elevation != 0.0: + # this relationship comes directly from law of sines + self.nadir_angle = math.degrees( + math.asin( + EARTH_RADIUS * math.sin(math.radians(self.elevation + 90)) / + (EARTH_RADIUS + self.altitude), + ), + ) + elif self.nadir_angle != 0.0: + # this relationship comes directly from law of sines + # can also be derived from incidence angle according to Rec. ITU-R RS.1861-0 + self.elevation = math.degrees( + math.asin( + (EARTH_RADIUS + self.altitude) * + math.sin(math.radians(self.nadir_angle)) / + EARTH_RADIUS, + ), + ) - 90 diff --git a/sharc/plot.py b/sharc/plot.py index 863391340..a99a18de0 100644 --- a/sharc/plot.py +++ b/sharc/plot.py @@ -5,8 +5,9 @@ @author: edgar """ + class Plot(object): - + def __init__(self, x, y, x_label, y_label, title, file_name, **kwargs): self.x = x self.y = y @@ -14,12 +15,12 @@ def __init__(self, x, y, x_label, y_label, title, file_name, **kwargs): self.y_label = y_label self.title = title self.file_name = file_name - + if "x_lim" in kwargs: self.x_lim = kwargs["x_lim"] else: self.x_lim = None - + if "y_lim" in kwargs: self.y_lim = kwargs["y_lim"] else: diff --git a/sharc/plots/plot_cdf.py b/sharc/plots/plot_cdf.py new file mode 100644 index 000000000..9583ac034 --- /dev/null +++ b/sharc/plots/plot_cdf.py @@ -0,0 +1,372 @@ +import os +import pandas as pd +import plotly.graph_objects as go + + +def plot_cdf( + base_dir, file_prefix, passo_xticks=5, xaxis_title='Value', legends=None, subfolders=None, save_file=True, + show_plot=False, +): + + label = 'CDF' + # Ensure at least one of save_file or show_plot is true + if not save_file and not show_plot: + raise ValueError("Either save_file or show_plot must be True.") + + # Define the base directory dynamically based on user input + workfolder = os.path.dirname(os.path.abspath(__file__)) + csv_folder = os.path.abspath( + os.path.join( + workfolder, '..', "campaigns", base_dir, "output", + ), + ) + figs_folder = os.path.abspath( + os.path.join( + workfolder, '..', "campaigns", base_dir, "output", "figs", + ), + ) + + # Check if the figs output folder exists, if not, create it + if not os.path.exists(figs_folder): + os.makedirs(figs_folder) + + # List all subfolders in the base directory or only those specified by the user + if subfolders: + subdirs = [ + os.path.join(csv_folder, d) for d in subfolders if os.path.isdir( + os.path.join(csv_folder, d), + ) + ] + else: + subdirs = [ + os.path.join(csv_folder, d) for d in os.listdir(csv_folder) + if os.path.isdir(os.path.join(csv_folder, d)) and d.startswith(f"output_{base_dir}_") + ] + + # Validate the number of legends + if legends and len(legends) != len(subdirs): + raise ValueError( + "The number of provided legends does not match the number of found subfolders.", + ) + + # Initialize global min and max values + global_min = float('inf') + global_max = float('-inf') + + # First, calculate the global min and max values + for subdir in subdirs: + all_files = [ + f for f in os.listdir(subdir) if f.endswith('.csv',) and label in f and file_prefix in f + ] + + for file_name in all_files: + file_path = os.path.join(subdir, file_name) + if os.path.exists(file_path): + try: + # Try reading the .csv file using pandas with different delimiters + try: + data = pd.read_csv( + file_path, delimiter=',', skiprows=1, + ) + except pd.errors.ParserError: + data = pd.read_csv( + file_path, delimiter=';', skiprows=1, + ) + + # Ensure the data has at least two columns + if data.shape[1] < 2: + print( + f"The file {file_name} does not have enough columns to plot.", + ) + continue + + # Remove rows that do not contain valid numeric values + data = data.apply(pd.to_numeric, errors='coerce').dropna() + + if not data.empty: + global_min = min(global_min, data.iloc[:, 0].min()) + global_max = max(global_max, data.iloc[:, 0].max()) + except Exception as e: + print(f"Error processing the file {file_name}: {e}") + + # If no valid data was found, set reasonable defaults for global_min and global_max + if global_min == float('inf') or global_max == float('-inf'): + global_min, global_max = 0, 1 + + # Plot the graphs adjusting the axes + fig = go.Figure() + for idx, subdir in enumerate(subdirs): + all_files = [ + f for f in os.listdir(subdir) if f.endswith('.csv',) and label in f and file_prefix in f + ] + legenda = legends[idx] if legends else os.path.basename( + subdir, + ).split(f"output_{base_dir}_")[1] + + for file_name in all_files: + file_path = os.path.join(subdir, file_name) + if os.path.exists(file_path): + try: + # Try reading the .csv file using pandas with different delimiters + try: + data = pd.read_csv( + file_path, delimiter=',', skiprows=1, + ) + except pd.errors.ParserError: + data = pd.read_csv( + file_path, delimiter=';', skiprows=1, + ) + + # Ensure the data has at least two columns + if data.shape[1] < 2: + print( + f"The file {file_name} does not have enough columns to plot.", + ) + continue + + # Remove rows that do not contain valid numeric values + data = data.apply(pd.to_numeric, errors='coerce').dropna() + + # Check if there are enough data points to plot + if data.empty or data.shape[0] < 2: + print( + f"The file {file_name} does not have enough data to plot.", + ) + continue + + # Plot the CDF + fig.add_trace( + go.Scatter(x=data.iloc[:, 0], y=data.iloc[:, 1], mode='lines', name=f'{legenda}',), + ) + except Exception as e: + print(f"Error processing the file {file_name}: {e}") + + # Graph configurations + fig.update_layout( + title=f'CDF Plot for {file_prefix}', + xaxis_title=xaxis_title, + yaxis_title='CDF', + yaxis=dict(tickmode='array', tickvals=[0, 0.25, 0.5, 0.75, 1]), + xaxis=dict(tickmode='linear', tick0=global_min, dtick=passo_xticks), + legend_title="Labels", + ) + + # Show the figure if requested + if show_plot: + fig.show() + + # Save the figure if requested + if save_file: + fig_file_path = os.path.join( + figs_folder, f"CDF_Plot_{file_prefix}.png", + ) + fig.write_image(fig_file_path) + print(f"Figure saved: {fig_file_path}") + +# Specific functions for different metrics + + +def plot_bs_antenna_gain_towards_the_ue( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'IMT_CDF_of_BS_antenna_gain_towards_the_UE', passo_xticks, xaxis_title='Antenna Gain (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_coupling_loss(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_coupling_loss', passo_xticks, xaxis_title='Coupling Loss (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_dl_sinr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_DL_SINR', passo_xticks, xaxis_title='DL SINR (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_dl_snr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_DL_SNR', passo_xticks, xaxis_title='DL SNR (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_dl_throughput(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_DL_throughput', passo_xticks, xaxis_title='DL Throughput (Mbps)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_dl_transmit_power(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_DL_transmit_power', passo_xticks, xaxis_title='DL Transmit Power (dBm)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_imt_station_antenna_gain_towards_system( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'IMT_CDF_of_IMT_station_antenna_gain_towards_system', passo_xticks, + xaxis_title='Antenna Gain (dB)', legends=legends, subfolders=subfolders, save_file=save_file, + show_plot=show_plot, + ) + + +def plot_path_loss(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'IMT_CDF_of_path_loss', passo_xticks, xaxis_title='Path Loss (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_ue_antenna_gain_towards_the_bs( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'IMT_CDF_of_UE_antenna_gain_towards_the_BS', passo_xticks, xaxis_title='Antenna Gain (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_imt_to_system_path_loss( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'SYS_CDF_of_IMT_to_system_path_loss', passo_xticks, xaxis_title='Path Loss (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_system_antenna_towards_imt_stations( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'SYS_CDF_of_system_antenna_gain_towards_IMT_stations', passo_xticks, + xaxis_title='Antenna Gain (dB)', legends=legends, subfolders=subfolders, save_file=save_file, + show_plot=show_plot, + ) + + +def plot_system_inr(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'SYS_CDF_of_system_INR', passo_xticks, xaxis_title='INR (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_system_interference_power_from_imt_dl( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'SYS_CDF_of_system_interference_power_from_IMT_DL', passo_xticks, + xaxis_title='Interference Power (dBm/MHz)', legends=legends, subfolders=subfolders, save_file=save_file, + show_plot=show_plot, + ) + + +def plot_system_interference_power_from_imt_ul( + base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, + show_plot=True, +): + plot_cdf( + base_dir, 'SYS_CDF_of_system_interference_power_from_IMT_UL', passo_xticks, + xaxis_title='Interference Power (dBm/MHz)', legends=legends, subfolders=subfolders, save_file=save_file, + show_plot=show_plot, + ) + + +def plot_system_pfd(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'SYS_CDF_of_system_PFD', passo_xticks, xaxis_title='PFD (dBW/mยฒ)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + + +def plot_inr_samples(base_dir, passo_xticks=5, legends=None, subfolders=None, save_file=True, show_plot=True): + plot_cdf( + base_dir, 'INR_samples', passo_xticks, xaxis_title='INR Samples (dB)', + legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + +# Main function to identify labels and call the appropriate functions + + +def all_plots(base_dir, legends=None, subfolders=None, save_file=True, show_plot=False): + plot_bs_antenna_gain_towards_the_ue( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_coupling_loss( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_dl_sinr( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_dl_snr( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_dl_throughput( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_dl_transmit_power( + base_dir, legends=legends, + subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_imt_station_antenna_gain_towards_system( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_path_loss( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_ue_antenna_gain_towards_the_bs( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_imt_to_system_path_loss( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_system_antenna_towards_imt_stations( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_system_inr( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + plot_system_interference_power_from_imt_dl( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_system_interference_power_from_imt_ul( + base_dir, legends=legends, subfolders=subfolders, save_file=save_file, show_plot=show_plot, + ) + plot_system_pfd( + base_dir, legends=legends, subfolders=subfolders, + save_file=save_file, show_plot=show_plot, + ) + + +if __name__ == "__main__": + # Example usage + name = "imt_hibs_ras_2600_MHz" + legends = None # Replace with a list of legends if needed + subfolders = None # Replace with a list of specific subfolders if needed + all_plots( + name, legends=legends, subfolders=subfolders, + save_file=True, show_plot=False, + ) diff --git a/sharc/post_processor.py b/sharc/post_processor.py new file mode 100644 index 000000000..0515cfda2 --- /dev/null +++ b/sharc/post_processor.py @@ -0,0 +1,456 @@ +from sharc.results import Results + +from dataclasses import dataclass, field +import plotly.graph_objects as go +import os +import numpy as np +import scipy +import typing + + +class FieldStatistics: + field_name: str + median: float + mean: float + variance: float + confidence_interval: (float, float) + standard_deviation: float + + def load_from_sample( + self, field_name: str, sample: list[float], *, confidence=0.95 + ) -> "FieldStatistics": + self.field_name = field_name + self.median = np.median(sample) + self.mean = np.mean(sample) + self.variance = np.var(sample) + self.standard_deviation = np.std(sample) + # @important TODO: check if using t distribution here is correct + self.confidence_interval = scipy.stats.norm.interval( + confidence, loc=self.mean, scale=self.standard_deviation + ) + return self + + def __str__(self): + attr_names = filter( + lambda x: x != "field_name" + and not x.startswith("__") + and not callable(getattr(self, x)), + dir(self), + ) + readable_attrs = "\n".join( + list( + map( + lambda attr_name: f"\t{attr_name}: {getattr(self, attr_name)}", + attr_names, + ) + ) + ) + return f"""{self.field_name}: +{readable_attrs}""" + + +class ResultsStatistics: + fields_statistics: list[FieldStatistics] + results_output_dir: str = "default_output" + + def load_from_results(self, result: Results) -> "ResultsStatistics": + """ + Loads all relevant attributes from result and generates their statistics + """ + self.results_output_dir = result.output_directory + self.fields_statistics = [] + attr_names = result.get_relevant_attributes() + for attr_name in attr_names: + samples = getattr(result, attr_name) + if len(samples) == 0: + continue + self.fields_statistics.append( + FieldStatistics().load_from_sample(attr_name, samples) + ) + + return self + + def write_to_results_dir(self, filename="stats.txt") -> "ResultsStatistics": + """ + Writes statistics file to the same directory of the results loaded into this class + """ + with open(os.path.join(self.results_output_dir, filename), "w") as f: + f.write(str(self)) + + return self + + def get_stat_by_name(self, field_name: str) -> typing.Union[None, FieldStatistics]: + """ + Gets a single field's statistics by its name. + E.g.: get_stat_by_name("system_dl_interf_power") + Returns + None if not found + FieldStatistics if found only one match + """ + stats_found = filter(lambda field_stat: field_stat.field_name == field_name, self.fields_statistics) + + if len(stats_found) > 1: + raise Exception( + f"ResultsStatistics.get_stat_by_name found more than one statistic by the field name '{field_name}'\n" + + "You probably loaded more than one result to the same ResultsStatistics object" + ) + + if len(stats_found) == 0: + return None + + return stats_found[0] + + def __str__(self): + return f"[{self.results_output_dir}]\n{'\n'.join(list(map(str, self.fields_statistics)))}" + + +@dataclass +class PostProcessor: + IGNORE_FIELD = { + "title": "ANTES NAO PLOTAVAMOS ISSO, ENTรƒO CONTINUA SEM PLOTAR", + "x_label": "", + } + # TODO: move units in the result to the Results class instead of Plot Info? + # TODO: rename x_label to something else when making plots other than cdf + RESULT_FIELDNAME_TO_PLOT_INFO = { + "imt_ul_tx_power_density": { + "x_label": "Transmit power density [dBm/Hz]", + "title": "[IMT] UE transmit power density", + }, + "imt_ul_tx_power": { + "x_label": "Transmit power [dBm]", + "title": "[IMT] UE transmit power", + }, + "imt_ul_sinr_ext": { + "x_label": "SINR [dB]", + "title": "[IMT] UL SINR with external interference", + }, + "imt_ul_snr": { + "title": "[IMT] UL SNR", + "x_label": "SNR [dB]", + }, + "imt_ul_inr": { + "title": "[IMT] UL interference-to-noise ratio", + "x_label": "$I/N$ [dB]", + }, + "imt_ul_sinr": { + "x_label": "SINR [dB]", + "title": "[IMT] UL SINR", + }, + "imt_system_build_entry_loss": { + "x_label": "Building entry loss [dB]", + "title": "[SYS] IMT to system building entry loss", + }, + "imt_ul_tput_ext": { + "title": "[IMT] UL throughput with external interference", + "x_label": "Throughput [bits/s/Hz]", + }, + "imt_ul_tput": { + "title": "[IMT] UL throughput", + "x_label": "Throughput [bits/s/Hz]", + }, + "imt_path_loss": { + "title": "[IMT] path loss", + "x_label": "Path loss [dB]", + }, + "imt_coupling_loss": { + "title": "[IMT] coupling loss", + "x_label": "Coupling loss [dB]", + }, + "imt_bs_antenna_gain": { + "x_label": "Antenna gain [dBi]", + "title": "[IMT] BS antenna gain towards the UE", + }, + "imt_ue_antenna_gain": { + "x_label": "Antenna gain [dBi]", + "title": "[IMT] UE antenna gain towards the BS", + }, + "system_imt_antenna_gain": { + "x_label": "Antenna gain [dBi]", + "title": "[SYS] system antenna gain towards IMT stations", + }, + "imt_system_antenna_gain": { + "x_label": "Antenna gain [dBi]", + "title": "[IMT] IMT station antenna gain towards system", + }, + "imt_system_path_loss": { + "x_label": "Path Loss [dB]", + "title": "[SYS] IMT to system path loss", + }, + "system_dl_interf_power": { + "x_label": "Interference Power [dBm/MHz]", + "title": "[SYS] system interference power from IMT DL", + }, + "imt_system_diffraction_loss": { + "x_label": "Building entry loss [dB]", + "title": "[SYS] IMT to system diffraction loss", + }, + "imt_dl_sinr_ext": { + "x_label": "SINR [dB]", + "title": "[IMT] DL SINR with external interference", + }, + "imt_dl_sinr": { + "x_label": "SINR [dB]", + "title": "[IMT] DL SINR", + }, + "imt_dl_snr": { + "title": "[IMT] DL SNR", + "x_label": "SNR [dB]", + }, + "imt_dl_inr": { + "title": "[IMT] DL interference-to-noise ratio", + "x_label": "$I/N$ [dB]", + }, + "imt_dl_tput_ext": { + "title": "[IMT] DL throughput with external interference", + "x_label": "Throughput [bits/s/Hz]", + }, + "imt_dl_tput": { + "title": "[IMT] DL throughput", + "x_label": "Throughput [bits/s/Hz]", + }, + "system_ul_interf_power": { + "title": "[SYS] system interference power from IMT UL", + "x_label": "Interference Power [dBm]", + }, + "system_inr": { + "title": "[SYS] system INR", + "x_label": "INR [dB]", + }, + "system_pfd": { + "title": "[SYS] system PFD", + "x_label": "PFD [dBm/m^2]", + }, + "imt_dl_tx_power": { + "x_label": "Transmit power [dBm]", + "title": "[IMT] DL transmit power", + }, + # these ones were not plotted already, so will continue to not be plotted: + "imt_dl_tx_power_density": IGNORE_FIELD, + "system_ul_coupling_loss": IGNORE_FIELD, + "system_dl_coupling_loss": IGNORE_FIELD, + "system_rx_interf": IGNORE_FIELD, + } + + plot_legend_patterns: list = field(default_factory=list) + + plots: list[go.Figure] = field(default_factory=list) + results: list[Results] = field(default_factory=list) + + def add_plot_legend_pattern( + self, *, dir_name_contains: str, legend: str + ) -> "PostProcessor": + self.plot_legend_patterns.append( + {"dir_name_contains": dir_name_contains, "legend": legend} + ) + self.plot_legend_patterns.sort(key=lambda p: -len(p["dir_name_contains"])) + + return self + + def generate_cdf_plots_from_results( + self, results: list[Results], *, n_bins=200 + ) -> list[go.Figure]: + figs: dict[str, list[go.Figure]] = {} + + for res in results: + possible_legends_mapping = list( + filter( + lambda pl: pl["dir_name_contains"] + in os.path.basename(res.output_directory), + self.plot_legend_patterns, + ) + ) + + if len(possible_legends_mapping): + legend = possible_legends_mapping[0]["legend"] + else: + legend = res.output_directory + + attr_names = res.get_relevant_attributes() + + for attr_name in attr_names: + attr_val = getattr(res, attr_name) + if not len(attr_val): + continue + if attr_name not in PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO: + print( + f"[WARNING]: {attr_name} is not a plottable field, because it does not have a configuration set on PostProcessor." + ) + continue + attr_plot_info = PostProcessor.RESULT_FIELDNAME_TO_PLOT_INFO[attr_name] + if attr_plot_info == PostProcessor.IGNORE_FIELD: + print( + f"[WARNING]: {attr_name} is currently being ignored on plots." + ) + continue + if attr_name not in figs: + figs[attr_name] = go.Figure() + figs[attr_name].update_layout( + title=f'CDF Plot for {attr_plot_info["title"]}', + xaxis_title=attr_plot_info["x_label"], + yaxis_title="CDF", + yaxis=dict(tickmode="array", tickvals=[0, 0.25, 0.5, 0.75, 1]), + xaxis=dict(tickmode="linear", dtick=5), + legend_title="Labels", + meta={"related_results_attribute": attr_name}, + ) + + # TODO: take this fn as argument, to plot more than only cdf's + x, y = PostProcessor.cdf_from(attr_val, n_bins=n_bins) + + fig = figs[attr_name] + + fig.add_trace( + go.Scatter( + x=x, + y=y, + mode="lines", + name=f"{legend}", + ), + ) + + return figs.values() + + def add_plots(self, plots: list[go.Figure]) -> None: + self.plots.extend(plots) + + def add_results(self, results: list[Results]) -> None: + self.results.extend(results) + + def get_results_by_output_dir(self, dir_name_contains: str, *, single_result=True): + filtered_results = list( + filter( + lambda res: dir_name_contains in os.path.basename(res.output_directory), + self.results, + ) + ) + if len(filtered_results) == 0: + raise ValueError( + f"Could not find result that contains '{dir_name_contains}'" + ) + + if len(filtered_results) > 1: + raise ValueError( + f"There is more than one possible result with pattern '{dir_name_contains}'" + ) + + return filtered_results[0] + + def get_plot_by_results_attribute_name(self, attr_name: str) -> go.Figure: + """ + You can get a plot using an attribute name from Results. + See Results class to check what attributes exist. + """ + filtered = list( + filter( + lambda x: x.layout.meta["related_results_attribute"] == attr_name, + self.plots, + ) + ) + + if 0 == len(filtered): + return None + + return filtered[0] + + @staticmethod + def aggregate_results( + *, + dl_samples: list[float], + ul_samples: list[float], + ul_tdd_factor: float, + n_bs_sim: int, + n_bs_actual: int, + random_number_gen=np.random.RandomState(31), + ): + """ + The method was adapted from document 'TG51_201805_E07_FSS_Uplink_ study 48GHz_GSMA_v1.5.pdf', + a document created for Task Group 5/1. + This is used to aggregate both uplink and downlink interference towards another system + into a result that makes more sense to the case study. + Inputs: + downlink/uplink_result: list[float] + Samples that should be aggregated. + ul_tdd_factor: float + The tdd ratio that uplink is activated for. + n_bs_sim: int + Number of simulated base stations. + Should probably be 7 * 19 * 3 * 3 or 1 * 19 * 3 * 3 + n_bs_actual: int + The number of base stations the study wants to have conclusions for. + random_number_gen: np.random.RandomState + Since this methods uses another montecarlo to aggregate results, + it needs a random number generator + """ + if ul_tdd_factor > 1 or ul_tdd_factor < 0: + raise ValueError( + "PostProcessor.aggregate_results() was called with invalid ul_tdd_factor parameter." + + f"ul_tdd_factor must be in interval [0, 1], but is {ul_tdd_factor}" + ) + + segment_factor = round(n_bs_actual / n_bs_sim) + + dl_tdd_factor = 1 - ul_tdd_factor + + if ul_tdd_factor == 0: + n_aggregate = len(dl_samples) + elif dl_tdd_factor == 0: + n_aggregate = len(ul_samples) + else: + n_aggregate = min(len(ul_samples), len(dl_samples)) + + aggregate_samples = np.empty(n_aggregate) + + for i in range(n_aggregate): + # choose S random samples + ul_random_indexes = np.floor( + random_number_gen.random(size=segment_factor) + * len(ul_samples) + ) + dl_random_indexes = np.floor( + random_number_gen.random(size=segment_factor) + * len(dl_samples) + ) + aggregate_samples[i] = 0 + + if ul_tdd_factor: + for j in ul_random_indexes: # random samples + aggregate_samples[i] += ( + np.power(10, ul_samples[int(j)] / 10) * ul_tdd_factor + ) + + if dl_tdd_factor: + for j in dl_random_indexes: # random samples + aggregate_samples[i] += ( + np.power(10, dl_samples[int(j)] / 10) * dl_tdd_factor + ) + + # convert back to dB or dBm (as was previously) + aggregate_samples[i] = 10 * np.log10( + aggregate_samples[i] + ) + + return aggregate_samples + + @staticmethod + def cdf_from(data: list[float], *, n_bins=200) -> (list[float], list[float]): + """ + Takes a dataset and returns both axis of a cdf (x, y) + """ + values, base = np.histogram( + data, + bins=n_bins, + ) + cumulative = np.cumsum(values) + x = base[:-1] + y = cumulative / cumulative[-1] + + return (x, y) + + @staticmethod + def generate_statistics(result: Results) -> ResultsStatistics: + return ResultsStatistics().load_from_results(result) + + @staticmethod + def generate_sample_statistics(fieldname: str, sample: list[float]) -> ResultsStatistics: + return FieldStatistics().load_from_sample(fieldname, sample) diff --git a/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv b/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv new file mode 100644 index 000000000..1a0534933 --- /dev/null +++ b/sharc/propagation/Dataset/BRASILIA_2680_1000m.csv @@ -0,0 +1,91 @@ +apparent_elevation,loss +0,1.6972748682898624 +1,1.014727203552297 +2,0.6949893820538064 +3,0.5192948386632888 +4,0.410974371448515 +5,0.3385733887990494 +6,0.2872066989323976 +7,0.2490691608495016 +8,0.21973085330778497 +9,0.19651352250780812 +10,0.17771245593414225 +11,0.1621954400421193 +12,0.14918320109111569 +13,0.13812292210399307 +14,0.12861214208533311 +15,0.12035122108906711 +16,0.11311266573642513 +17,0.10672076205657326 +18,0.1010377074383433 +19,0.09595395437041371 +20,0.09138135119434165 +21,0.0872481814845774 +22,0.08349551794281049 +23,0.08007450281263388 +24,0.0769442920534981 +25,0.07407048214413517 +26,0.07142389262271462 +27,0.06897961413419912 +28,0.06671625694285364 +29,0.06461535242677624 +30,0.06266087248080865 +31,0.060838840636564535 +32,0.05913701514414105 +33,0.05754462896743164 +34,0.05605217513545873 +35,0.05465122849189033 +36,0.053334296851397275 +37,0.0520946960619322 +38,0.05092644461617275 +39,0.0498241743398364 +40,0.04878305436991813 +41,0.04779872617585115 +42,0.0468672477974085 +43,0.04598504581454966 +44,0.045148873825493485 +45,0.044355776432720456 +46,0.043603057900702236 +47,0.04288825479785755 +48,0.04220911204462238 +49,0.04156356188438681 +50,0.040949705370952785 +51,0.04036579603002079 +52,0.03981022540253115 +53,0.03928151022567325 +54,0.03877828103708686 +55,0.03829927202645512 +56,0.03784331197626133 +57,0.03740931616114804 +58,0.036996279090277265 +59,0.03660326799403585 +60,0.03622941696784316 +61,0.03587392170174962 +62,0.03553603472480396 +63,0.035215061114966556 +64,0.0349103546176535 +65,0.034621314134231106 +66,0.034347380541628815 +67,0.03408803380253576 +68,0.033842790352832886 +69,0.03361120071962308 +70,0.03339284736158612 +71,0.033187342705105906 +72,0.032994327359448954 +73,0.03281346849436573 +74,0.032644458366651474 +75,0.03248701298141076 +76,0.03234087087874395 +77,0.03220579203443745 +78,0.03208155686639729 +79,0.03196796533784747 +80,0.03186483615274847 +81,0.031772006034729586 +82,0.0316893290847309 +83,0.0316166762140294 +84,0.03155393464639214 +85,0.031501007487167165 +86,0.03145781335580062 +87,0.03142428607934875 +88,0.03140037444348659 +89,0.03138604200265539 diff --git a/sharc/propagation/Dataset/generate_loss_table.py b/sharc/propagation/Dataset/generate_loss_table.py new file mode 100644 index 000000000..58a778e86 --- /dev/null +++ b/sharc/propagation/Dataset/generate_loss_table.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Aug 31 12:59:12 2017 + +""" + +import os +import csv +import numpy as np +from sharc.propagation.propagation_p619 import PropagationP619 + +# Constants +frequency_MHz = 2680.0 +space_station_alt_m = 35786.0 * 1000 # Geostationary orbit altitude in meters +earth_station_alt_m = 1000.0 +earth_station_lat_deg = -15.7801 +earth_station_long_diff_deg = 0.0 # Not used in the loss calculation +season = "SUMMER" +apparent_elevation = np.arange(0, 90) # Elevation angles from 0 to 90 degrees +city_name = "BRASILIA" + +# Initialize the propagation model +random_number_gen = np.random.RandomState(101) +propagation = PropagationP619( + random_number_gen=random_number_gen, + space_station_alt_m=space_station_alt_m, + earth_station_alt_m=earth_station_alt_m, + earth_station_lat_deg=earth_station_lat_deg, + earth_station_long_diff_deg=earth_station_long_diff_deg, + season=season, +) + +# Calculate the loss for each elevation angle +losses = [] +for elevation in apparent_elevation: + loss = propagation._get_atmospheric_gasses_loss( + frequency_MHz=frequency_MHz, + apparent_elevation=elevation, + ) + losses.append(loss) + +# Save results to CSV file +output_dir = os.path.join(os.path.dirname(__file__), 'BRASILIA') +os.makedirs(output_dir, exist_ok=True) +output_file = os.path.join( + output_dir, f'{city_name}_{int(frequency_MHz)}_{int(earth_station_alt_m)}m.csv', +) + +with open(output_file, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(['apparent_elevation', 'loss']) + for elevation, loss in zip(apparent_elevation, losses): + writer.writerow([elevation, loss]) + +print(f"Results saved to {output_file}") diff --git a/sharc/propagation/Dataset/localidades.csv b/sharc/propagation/Dataset/localidades.csv new file mode 100644 index 000000000..151eaae6f --- /dev/null +++ b/sharc/propagation/Dataset/localidades.csv @@ -0,0 +1,2 @@ +Cidade,Latitude +BRASILIA,-15.7801 diff --git a/sharc/propagation/__init__.py b/sharc/propagation/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/propagation/__init__.py +++ b/sharc/propagation/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/propagation/atmosphere.py b/sharc/propagation/atmosphere.py index e94065341..ae459b489 100644 --- a/sharc/propagation/atmosphere.py +++ b/sharc/propagation/atmosphere.py @@ -5,80 +5,195 @@ @author: Andre Noll Barreto """ -import math import numpy as np + class ReferenceAtmosphere: """ Implements diverse ITU recommendations on a reference atmosphere """ + def __init__(self): # Table C.1 of ITU-R P619 - Constants for the reference dry atmosphere - self.ref_atmosphere_altitude_km = [-float('inf'), 11, 20, 32, 47, 51, 71] + self.ref_atmosphere_altitude_km = [ + -float('inf'), 11, 20, 32, 47, 51, 71, + ] self.ref_atmosphere_temp_grad = [-6.5, 0, 1., 2.8, 0, -2.8, -2] - self.ref_atmosphere_temperature = [288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65] - self.ref_atmosphere_pressure = [1013.25, 226.323, 54.750, 8.68, 1.109, 0.669, 0.04] + self.ref_atmosphere_temperature = [ + 288.15, 216.65, 216.65, 228.65, 270.65, 270.65, 214.65, + ] + self.ref_atmosphere_pressure = [ + 1013.25, 226.323, 54.750, 8.68, 1.109, 0.669, 0.04, + ] # Tables 1 and 2, Spectroscopic data for oxygen and water attenuation, in ITU-T P.676-11 self.p676_oxygen_f0 = np.array( - [50.474214, 50.987745, 51.503360, 52.021429, 52.542418, 53.066934, 53.595775, - 54.130025, 54.671180, 55.221384, 55.783815, 56.264774, 56.363399, 56.968211, - 57.612486, 58.323877, 58.446588, 59.164204, 59.590983, 60.306056, 60.434778, - 61.150562, 61.800158, 62.411220, 62.486253, 62.997984, 63.568526, 64.127775, - 64.678910, 65.224078, 65.764779, 66.302096, 66.836834, 67.369601, 67.900868, - 68.431006, 68.960312, 118.750334, 368.498246, 424.763020, 487.249273, 715.392902, - 773.839490, 834.145546]) + [ + 50.474214, 50.987745, 51.503360, 52.021429, 52.542418, 53.066934, 53.595775, + 54.130025, 54.671180, 55.221384, 55.783815, 56.264774, 56.363399, 56.968211, + 57.612486, 58.323877, 58.446588, 59.164204, 59.590983, 60.306056, 60.434778, + 61.150562, 61.800158, 62.411220, 62.486253, 62.997984, 63.568526, 64.127775, + 64.678910, 65.224078, 65.764779, 66.302096, 66.836834, 67.369601, 67.900868, + 68.431006, 68.960312, 118.750334, 368.498246, 424.763020, 487.249273, 715.392902, + 773.839490, 834.145546, + ], + ) self.p676_a = np.array( - [[0.975, 9.651, 6.690, 0.0, 2.566, 6.850], [2.529, 8.653, 7.170, 0.0, 2.246, 6.800], - [6.193, 7.709, 7.640, 0.0, 1.947, 6.729], [14.320, 6.819, 8.110, 0.0, 1.667, 6.640], - [31.240, 5.983, 8.580, 0.0, 1.388, 6.526], [64.290, 5.201, 9.060, 0.0, 1.349, 6.206], - [124.600, 4.474, 9.550, 0.0, 2.227, 5.085], [227.300, 3.800, 9.960, 0.0, 3.170, 3.750], - [389.700, 3.182, 10.370, 0.0, 3.558, 2.654], [627.100, 2.618, 10.890, 0.0, 2.560, 2.952], - [945.300, 2.109, 11.340, 0.0, -1.172, 6.135], [543.400, 0.014, 17.030, 0.0, 3.525, -0.978], - [1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], [1746.600, 1.255, 12.230, 0.0, -3.545, 6.451], - [2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], [2363.700, 0.621, 12.950, 0.0, -1.932, 0.436], - [1442.100, 0.083, 14.910, 0.0, 6.768, -1.273], [2379.900, 0.387, 13.530, 0.0, -6.561, 2.309], - [2090.700, 0.207, 14.080, 0.0, 6.957, -0.776], [2103.400, 0.207, 14.150, 0.0, -6.395, 0.699], - [2438.000, 0.386, 13.390, 0.0, 6.342, -2.825], [2479.500, 0.621, 12.920, 0.0, 1.014, -0.584], - [2275.900, 0.910, 12.630, 0.0, 5.014, -6.619], [1915.400, 1.255, 12.170, 0.0, 3.029, -6.759], - [1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], [1490.200, 1.654, 11.740, 0.0, 1.856, -6.675], - [1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], [728.700, 2.617, 10.880, 0.0, -3.036, -2.895], - [461.300, 3.181, 10.380, 0.0, -3.968, -2.590], [274.000, 3.800, 9.960, 0.0, -3.528, -3.680], - [153.000, 4.473, 9.550, 0.0, -2.548, -5.002], [80.400, 5.200, 9.060, 0.0, -1.660, -6.091], - [39.800, 5.982, 8.580, 0.0, -1.680, -6.393], [18.560, 6.818, 8.110, 0.0, -1.956, -6.475], - [8.172, 7.708, 7.640, 0.0, -2.216, -6.545], [3.397, 8.652, 7.170, 0.0, -2.492, -6.600], - [1.334, 9.650, 6.690, 0.0, -2.773, -6.650], [940.300, 0.010, 16.640, 0.0, -0.439, 0.079], - [67.400, 0.048, 16.400, 0.0, 0.000, 0.000], [637.700, 0.044, 16.400, 0.0, 0.000, 0.000], - [237.400, 0.049, 16.000, 0.0, 0.000, 0.000], [98.100, 0.145, 16.000, 0.0, 0.000, 0.000], - [572.300, 0.141, 16.200, 0.0, 0.000, 0.000], [183.100, 0.145, 14.700, 0.0, 0.000, 0.000]]) - - self.p676_vapour_f0 = np.array([ 22.235080, 67.803960, 119.995940, 183.310087, 321.225630, 325.152888, - 336.227764, 380.197353, 390.134508, 437.346667, 439.150807, 443.018343, - 448.001085, 470.888999, 474.689092, 488.490108, 503.568532, 504.482692, - 547.676440, 552.020960, 556.935985, 620.700807, 645.766085, 658.005280, - 752.033113, 841.051732, 859.965698, 899.303175, 902.611085, 906.205957, - 916.171582, 923.112692, 970.315022, 987.926764, 1780.000000]) + [ + [0.975, 9.651, 6.690, 0.0, 2.566, 6.850], [2.529, 8.653, 7.170, 0.0, 2.246, 6.800], + [6.193, 7.709, 7.640, 0.0, 1.947, 6.729], [ + 14.320, 6.819, 8.110, 0.0, 1.667, 6.640, + ], + [31.240, 5.983, 8.580, 0.0, 1.388, 6.526], [ + 64.290, 5.201, 9.060, 0.0, 1.349, 6.206, + ], + [124.600, 4.474, 9.550, 0.0, 2.227, 5.085], [ + 227.300, 3.800, 9.960, 0.0, 3.170, 3.750, + ], + [389.700, 3.182, 10.370, 0.0, 3.558, 2.654], [ + 627.100, 2.618, 10.890, 0.0, 2.560, 2.952, + ], + [945.300, 2.109, 11.340, 0.0, -1.172, 6.135], [ + 543.400, + 0.014, 17.030, 0.0, 3.525, -0.978, + ], + [1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], [ + 1746.600, + 1.255, 12.230, 0.0, -3.545, 6.451, + ], + [2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], [ + 2363.700, + 0.621, 12.950, 0.0, -1.932, 0.436, + ], + [ + 1442.100, 0.083, 14.910, 0.0, 6.768, + - 1.273, + ], [2379.900, 0.387, 13.530, 0.0, -6.561, 2.309], + [ + 2090.700, 0.207, 14.080, 0.0, 6.957, + - 0.776, + ], [2103.400, 0.207, 14.150, 0.0, -6.395, 0.699], + [ + 2438.000, 0.386, 13.390, 0.0, 6.342, + - 2.825, + ], [2479.500, 0.621, 12.920, 0.0, 1.014, -0.584], + [ + 2275.900, 0.910, 12.630, 0.0, 5.014, + - 6.619, + ], [1915.400, 1.255, 12.170, 0.0, 3.029, -6.759], + [1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], [ + 1490.200, + 1.654, 11.740, 0.0, 1.856, -6.675, + ], + [1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], [ + 728.700, + 2.617, 10.880, 0.0, -3.036, -2.895, + ], + [ + 461.300, 3.181, 10.380, 0.0, -3.968, + - 2.590, + ], [274.000, 3.800, 9.960, 0.0, -3.528, -3.680], + [153.000, 4.473, 9.550, 0.0, -2.548, -5.002], [ + 80.400, + 5.200, 9.060, 0.0, -1.660, -6.091, + ], + [39.800, 5.982, 8.580, 0.0, -1.680, -6.393], [ + 18.560, + 6.818, 8.110, 0.0, -1.956, -6.475, + ], + [8.172, 7.708, 7.640, 0.0, -2.216, -6.545], [ + 3.397, + 8.652, 7.170, 0.0, -2.492, -6.600, + ], + [1.334, 9.650, 6.690, 0.0, -2.773, -6.650], [ + 940.300, + 0.010, 16.640, 0.0, -0.439, 0.079, + ], + [67.400, 0.048, 16.400, 0.0, 0.000, 0.000], [ + 637.700, 0.044, 16.400, 0.0, 0.000, 0.000, + ], + [237.400, 0.049, 16.000, 0.0, 0.000, 0.000], [ + 98.100, 0.145, 16.000, 0.0, 0.000, 0.000, + ], + [572.300, 0.141, 16.200, 0.0, 0.000, 0.000], [183.100, 0.145, 14.700, 0.0, 0.000, 0.000], + ], + ) + + self.p676_vapour_f0 = np.array([ + 22.235080, 67.803960, 119.995940, 183.310087, 321.225630, 325.152888, + 336.227764, 380.197353, 390.134508, 437.346667, 439.150807, 443.018343, + 448.001085, 470.888999, 474.689092, 488.490108, 503.568532, 504.482692, + 547.676440, 552.020960, 556.935985, 620.700807, 645.766085, 658.005280, + 752.033113, 841.051732, 859.965698, 899.303175, 902.611085, 906.205957, + 916.171582, 923.112692, 970.315022, 987.926764, 1780.000000, + ]) self.p676_vapour_indices = np.array([0, 3, 4, 5, 7, 12, 20, 24, 34]) self.p676_b = np.array( - [[.1079, 2.144, 26.38, .76, 5.087, 1.00], [.0011, 8.732, 28.58, .69, 4.930, .82], - [.0007, 8.353, 29.48, .70, 4.780, .79], [2.273, .668, 29.06, .77, 5.022, .85], - [.0470, 6.179, 24.04, .67, 4.398, .54], [1.514, 1.541, 28.23, .64, 4.893, .74], - [.0010, 9.825, 26.93, .69, 4.740, .61], [11.67, 1.048, 28.11, .54, 5.063, .89], - [.0045, 7.347, 21.52, .63, 4.810, .55], [.0632, 5.048, 18.45, .60, 4.230, .48], - [.9098, 3.595, 20.07, .63, 4.483, .52], [.1920, 5.048, 15.55, .60, 5.083, .50], - [10.41, 1.405, 25.64, .66, 5.028, .67], [.3254, 3.597, 21.34, .66, 4.506, .65], - [1.260, 2.379, 23.20, .65, 4.804, .64], [.2529, 2.852, 25.86, .69, 5.201, .72], - [.0372, 6.731, 16.12, .61, 3.980, .43], [.0124, 6.731, 16.12, .61, 4.010, .45], - [.9785, .158, 26.00, .70, 4.500, 1.00], [.1840, .158, 26.00, .70, 4.500, 1.00], - [497.0, .159, 30.86, .69, 4.552, 1.00], [5.015, 2.391, 24.38, .71, 4.856, .68], - [.0067, 8.633, 18.00, .60, 4.000, .50], [.2732, 7.816, 32.10, .69, 4.140, 1.00], - [243.4, .396, 30.86, .68, 4.352, .84], [.0134, 8.177, 15.90, .33, 5.760, .45], - [.1325, 8.055, 30.60, .68, 4.090, .84], [.0547, 7.914, 29.85, .68, 4.530, .90], - [.0386, 8.429, 28.65, .70, 5.100, .95], [.1836, 5.110, 24.08, .70, 4.700, .53], - [8.400, 1.441, 26.73, .70, 5.150, .78], [.0079, 10.293, 29.00, .70, 5.000, .80], - [9.009, 1.919, 25.50, .64, 4.940, .67], [134.6, .257, 29.85, .68, 4.550, .90], - [17506., .952, 196.3, 2.00, 24.15, 5.00]]) + [ + [.1079, 2.144, 26.38, .76, 5.087, 1.00], [.0011, 8.732, 28.58, .69, 4.930, .82], + [.0007, 8.353, 29.48, .70, 4.780, .79], [ + 2.273, .668, 29.06, .77, 5.022, .85, + ], + [.0470, 6.179, 24.04, .67, 4.398, .54], [ + 1.514, 1.541, 28.23, .64, 4.893, .74, + ], + [.0010, 9.825, 26.93, .69, 4.740, .61], [ + 11.67, 1.048, 28.11, .54, 5.063, .89, + ], + [.0045, 7.347, 21.52, .63, 4.810, .55], [ + .0632, + 5.048, 18.45, .60, 4.230, .48, + ], + [.9098, 3.595, 20.07, .63, 4.483, .52], [ + .1920, + 5.048, 15.55, .60, 5.083, .50, + ], + [10.41, 1.405, 25.64, .66, 5.028, .67], [ + .3254, + 3.597, 21.34, .66, 4.506, .65, + ], + [1.260, 2.379, 23.20, .65, 4.804, .64], [ + .2529, + 2.852, 25.86, .69, 5.201, .72, + ], + [.0372, 6.731, 16.12, .61, 3.980, .43], [ + .0124, + 6.731, 16.12, .61, 4.010, .45, + ], + [ + .9785, .158, 26.00, .70, 4.500, + 1.00, + ], [.1840, .158, 26.00, .70, 4.500, 1.00], + [497.0, .159, 30.86, .69, 4.552, 1.00], [ + 5.015, 2.391, 24.38, .71, 4.856, .68, + ], + [.0067, 8.633, 18.00, .60, 4.000, .50], [ + .2732, + 7.816, 32.10, .69, 4.140, 1.00, + ], + [243.4, .396, 30.86, .68, 4.352, .84], [ + .0134, + 8.177, 15.90, .33, 5.760, .45, + ], + [.1325, 8.055, 30.60, .68, 4.090, .84], [ + .0547, + 7.914, 29.85, .68, 4.530, .90, + ], + [.0386, 8.429, 28.65, .70, 5.100, .95], [ + .1836, + 5.110, 24.08, .70, 4.700, .53, + ], + [8.400, 1.441, 26.73, .70, 5.150, .78], [ + .0079, + 10.293, 29.00, .70, 5.000, .80, + ], + [9.009, 1.919, 25.50, .64, 4.940, .67], [ + 134.6, .257, 29.85, .68, 4.550, .90, + ], + [17506., .952, 196.3, 2.00, 24.15, 5.00], + ], + ) def _get_specific_attenuation(self, pressure, water_vapour_pressure, temperature, frequency_MHz): """ @@ -101,35 +216,43 @@ def _get_specific_attenuation(self, pressure, water_vapour_pressure, temperature # line strength s_oxygen = self.p676_a[:, 0] * 1e-7 * pressure * theta ** 3 * \ - np.exp(self.p676_a[:, 1] * (1 - theta)) + np.exp(self.p676_a[:, 1] * (1 - theta)) s_vapour = self.p676_b[:, 0] * 1e-1 * water_vapour_pressure * theta ** 3.5 \ - * np.exp(self.p676_b[:, 1] * (1 - theta)) + * np.exp(self.p676_b[:, 1] * (1 - theta)) # line width - df_oxygen = self.p676_a[:, 2] * 1e-4 * (pressure * theta ** (.8 - self.p676_a[:, 3]) - + 1.1 * water_vapour_pressure * theta) - df_vapour = self.p676_b[:, 2] * 1e-4 * (pressure * theta ** (self.p676_b[:, 3]) - + self.p676_b[:, 4] * water_vapour_pressure * theta ** self.p676_b[:, 5]) + df_oxygen = self.p676_a[:, 2] * 1e-4 * ( + pressure * theta ** (.8 - self.p676_a[:, 3]) + + 1.1 * water_vapour_pressure * theta + ) + df_vapour = self.p676_b[:, 2] * 1e-4 * ( + pressure * theta ** (self.p676_b[:, 3]) + + self.p676_b[:, 4] * water_vapour_pressure * theta ** self.p676_b[:, 5] + ) # correction factor delta_oxygen = (self.p676_a[:, 4] + self.p676_a[:, 5] * theta) * 1e-4 * \ (pressure + water_vapour_pressure) * theta ** .8 # line shape factor sf_oxygen = f_GHz / self.p676_oxygen_f0 * \ - ((df_oxygen - delta_oxygen * (self.p676_oxygen_f0 - f_GHz)) / - ((self.p676_oxygen_f0 - f_GHz) ** 2 + df_oxygen ** 2) + - (df_oxygen - delta_oxygen * (self.p676_oxygen_f0 + f_GHz)) / - ((self.p676_oxygen_f0 + f_GHz) ** 2 + df_oxygen ** 2)) + ((df_oxygen - delta_oxygen * (self.p676_oxygen_f0 - f_GHz)) / + ((self.p676_oxygen_f0 - f_GHz) ** 2 + df_oxygen ** 2) + + (df_oxygen - delta_oxygen * (self.p676_oxygen_f0 + f_GHz)) / + ((self.p676_oxygen_f0 + f_GHz) ** 2 + df_oxygen ** 2)) sf_vapour = f_GHz / self.p676_vapour_f0 * \ - (df_vapour / ((self.p676_vapour_f0 - f_GHz) ** 2 + df_vapour ** 2) + - df_vapour / ((self.p676_vapour_f0 + f_GHz) ** 2 + df_vapour ** 2)) + ( + df_vapour / ((self.p676_vapour_f0 - f_GHz) ** 2 + df_vapour ** 2) + + df_vapour / ((self.p676_vapour_f0 + f_GHz) ** 2 + df_vapour ** 2) + ) # Debye spectrum width dw = 5.6e-4 * (pressure + water_vapour_pressure) * theta ** .8 # dry continuum due to pressure-induced nitrogen absorption and the Debye spectrum nd = f_GHz * pressure * theta ** 2 * \ - (6.14e-5 / (dw * (1 + (f_GHz / dw) ** 2)) - + 1.4e-12 * pressure * theta ** 1.5 / (1 + 1.9e-5 * f_GHz ** 1.5)) + ( + 6.14e-5 / (dw * (1 + (f_GHz / dw) ** 2)) + + 1.4e-12 * pressure * theta ** 1.5 / (1 + 1.9e-5 * f_GHz ** 1.5) + ) att_oxygen = 0.182 * f_GHz * (np.sum(s_oxygen * sf_oxygen) + nd) att_vapour = 0.182 * f_GHz * np.sum(s_vapour * sf_vapour) @@ -156,7 +279,7 @@ def get_atmospheric_params(self, altitude_km, water_vapour_density_sea_level, f_ specific_attenuation (float): specific gaseous attenuation (dB/km) """ - index = np.where(altitude_km >= np.array(self.ref_atmosphere_altitude_km))[0][-1] + index = np.where(altitude_km >= np.array(self.ref_atmosphere_altitude_km,),)[0][-1] Ti = self.ref_atmosphere_temperature[index] Li = self.ref_atmosphere_temp_grad[index] @@ -167,22 +290,30 @@ def get_atmospheric_params(self, altitude_km, water_vapour_density_sea_level, f_ temperature = Ti + Li * (altitude_km - Hi) if Li: - pressure = Pi * (Ti / (Ti + Li * (altitude_km - Hi))) ** (34.163 / Li) + pressure = Pi * \ + (Ti / (Ti + Li * (altitude_km - Hi))) ** (34.163 / Li) else: pressure = Pi * np.exp(-34.163 * (altitude_km - Hi) / Ti) - water_vapour_density = water_vapour_density_sea_level * np.exp(-altitude_km / 2) + water_vapour_density = water_vapour_density_sea_level * \ + np.exp(-altitude_km / 2) water_vapour_pressure = water_vapour_density * temperature / 216.7 - refractive_index = 1 + 1e-6 * (77.6 / temperature * - (pressure + water_vapour_pressure + - 4810 * water_vapour_pressure / pressure)) - specific_attenuation = self._get_specific_attenuation(pressure, water_vapour_pressure, - temperature, f_MHz) + refractive_index = 1 + 1e-6 * ( + 77.6 / temperature * + ( + pressure + water_vapour_pressure + + 4810 * water_vapour_pressure / pressure + ) + ) + specific_attenuation = self._get_specific_attenuation( + pressure, water_vapour_pressure, + temperature, f_MHz, + ) return [temperature, pressure, water_vapour_pressure, refractive_index, specific_attenuation] @staticmethod - def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): + def get_reference_atmosphere_p835(latitude, altitude=1000, season="summer"): """ Returns reference atmosphere parameters based on ITU-R P835-5 @@ -204,7 +335,7 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): if latitude <= 22: # low latitude if h_km < 17.: - temperature = 300.4222 - 6.3533 * h_km + .005886 * h_km **2 + temperature = 300.4222 - 6.3533 * h_km + .005886 * h_km ** 2 elif h_km < 47: temperature = 194 + (h_km - 17) * 2.533 elif h_km < 52: @@ -223,15 +354,20 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): p10 = 1012.0306 - 109.0338 * 10 + 3.6316 * 10 ** 2 pressure_hPa = p10 * np.exp(-.147 * (h_km - 10.)) elif h_km <= 100: - p72 = ( 1012.0306 - 109.0338 * 10 + 3.6316 * 10 ** 2 ) * np.exp(-.147 * (72 - 10)) - pressure_hPa = p72 * np.exp(-.165 * (h - 72)) + p72 = ( + 1012.0306 - 109.0338 * 10 + 3.6316 * + 10 ** 2 + ) * np.exp(-.147 * (72 - 10)) + pressure_hPa = p72 * np.exp(-.165 * (h_km - 72)) else: error_message = "altitude > 100km not supported" raise ValueError(error_message) if h_km <= 15.: - water_vapour_density = 19.6542 * np.exp( -.2313 * h_km - .1122 * h_km ** 2 - + .01351 * h_km **3 - .0005923 * h_km ** 4) + water_vapour_density = 19.6542 * np.exp( + -.2313 * h_km - .1122 * h_km ** 2 + + .01351 * h_km ** 3 - .0005923 * h_km ** 4, + ) else: water_vapour_density = 0 @@ -247,7 +383,7 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): elif h_km < 53.: temperature = 275. elif h_km < 80.: - temperature = 275 + (1 - np.exp((h - 53.)*.06)) * 20. + temperature = 275 + (1 - np.exp((h_km - 53.) * .06)) * 20. elif h_km <= 100: temperature = 175. else: @@ -256,19 +392,24 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): if h_km <= 10: pressure_hPa = 1012.8186 - 111.5569 * h_km + 3.8646 * h_km ** 2 - elif h_km <=72.: + elif h_km <= 72.: p10 = 1012.8186 - 111.5569 * 10 + 3.8646 * 10 ** 2 - pressure_hPa = p10 * np.exp(-.147 * (h_km-10)) + pressure_hPa = p10 * np.exp(-.147 * (h_km - 10)) elif h_km <= 100.: - p72 = (1012.8186 - 111.5569 * 10 + 3.8646 * 10 ** 2) * np.exp(-.147 * (72-10)) + p72 = ( + 1012.8186 - 111.5569 * 10 + 3.8646 * + 10 ** 2 + ) * np.exp(-.147 * (72 - 10)) pressure_hPa = p72 * np.exp(-.165 * (h_km - 72)) else: error_message = "altitude > 100km not supported" raise ValueError(error_message) if h_km <= 15.: - water_vapour_density = 14.3542 * np.exp(-.4174 * h_km - .02290 * h_km ** 2 - + .001007 * h_km ** 3) + water_vapour_density = 14.3542 * np.exp( + -.4174 * h_km - .02290 * h_km ** 2 + + .001007 * h_km ** 3, + ) else: water_vapour_density = 0 @@ -291,19 +432,24 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): if h_km <= 10: pressure_hPa = 1018.8627 - 124.2954 * h_km + 4.8307 * h_km ** 2 - elif h_km <=72.: + elif h_km <= 72.: p10 = 1018.8627 - 124.2954 * 10 + 4.8307 * 10 ** 2 - pressure_hPa = p10 * np.exp(-.147 * (h_km-10)) + pressure_hPa = p10 * np.exp(-.147 * (h_km - 10)) elif h_km <= 100.: - p72 = (1018.8627 - 124.2954 * 10 + 4.8307 * 10 ** 2) * np.exp(-.147 * (72-10)) + p72 = ( + 1018.8627 - 124.2954 * 10 + 4.8307 * + 10 ** 2 + ) * np.exp(-.147 * (72 - 10)) pressure_hPa = p72 * np.exp(-.155 * (h_km - 72)) else: error_message = "altitude > 100km not supported" raise ValueError(error_message) if h_km <= 15.: - water_vapour_density = 3.4742 * np.exp(-.2697 * h_km - .03604 * h_km ** 2 - + .0004489 * h_km ** 3) + water_vapour_density = 3.4742 * np.exp( + -.2697 * h_km - .03604 * h_km ** 2 + + .0004489 * h_km ** 3, + ) else: water_vapour_density = 0 else: @@ -334,22 +480,29 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): p10 = 1008.0278 - 113.2494 * 10 + 3.9408 * (10 ** 2) pressure_hPa = p10 * np.exp(-.140 * (h_km - 10)) elif h_km <= 100.: - p72 = (1008.0278 - 113.2494 * 10 + 3.9408 * (10 ** 2)) * np.exp(-.140 * (72 - 10)) + p72 = ( + 1008.0278 - 113.2494 * 10 + 3.9408 * + (10 ** 2) + ) * np.exp(-.140 * (72 - 10)) pressure_hPa = p72 * np.exp(-.165 * (h_km - 72)) else: error_message = "altitude > 100km not supported" raise ValueError(error_message) if h_km <= 15.: - water_vapour_density = 8.988 * np.exp(-.3614 * h_km - .005402 * h_km ** 2 - + .001955 * h_km ** 3) + water_vapour_density = 8.988 * np.exp( + -.3614 * h_km - .005402 * h_km ** 2 + + .001955 * h_km ** 3, + ) else: water_vapour_density = 0 elif season == "winter": if h_km < 8.5: - temperature = (257.4345 + 2.3474 * h_km - .15479 * h_km ** 2 - + .08473 * h_km ** 3) + temperature = ( + 257.4345 + 2.3474 * h_km - .15479 * h_km ** 2 + + .08473 * h_km ** 3 + ) elif h_km < 30.: temperature = 217.5 elif h_km < 50: @@ -368,15 +521,20 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): p10 = 1010.8828 - 122.2411 * 10 + 4.554 * 10 ** 2 pressure_hPa = p10 * np.exp(-.147 * (h_km - 10)) elif h_km <= 100.: - p72 = (1010.8828 - 122.2411 * 10 + 4.554 * 10 ** 2) * np.exp(-.147 * (72 - 10)) + p72 = ( + 1010.8828 - 122.2411 * 10 + 4.554 * + 10 ** 2 + ) * np.exp(-.147 * (72 - 10)) pressure_hPa = p72 * np.exp(-.150 * (h_km - 72)) else: error_message = "altitude > 100km not supported" raise ValueError(error_message) if h_km <= 15.: - water_vapour_density = 1.2319 * np.exp(.07481 * h_km - .0981 * h_km ** 2 - + .00281 * h_km ** 3) + water_vapour_density = 1.2319 * np.exp( + .07481 * h_km - .0981 * h_km ** 2 + + .00281 * h_km ** 3, + ) else: water_vapour_density = 0 else: @@ -419,10 +577,12 @@ def get_reference_atmosphere_p835 (latitude, altitude=1000, season="summer"): print("Plotting specific attenuation:") for index in range(len(f_GHz_vec)): - specific_att[index] = atmosphere._get_specific_attenuation(pressure_hPa, - vapour_pressure_hPa, - temperature, - float(f_GHz_vec[index]) * 1000) + specific_att[index] = atmosphere._get_specific_attenuation( + pressure_hPa, + vapour_pressure_hPa, + temperature, + float(f_GHz_vec[index]) * 1000, + ) plt.figure() plt.semilogy(f_GHz_vec, specific_att) plt.xlabel('frequency(GHz)') diff --git a/sharc/propagation/clear_air_452_aux.py b/sharc/propagation/clear_air_452_aux.py index 3ab1e2ff8..dac65e56b 100644 --- a/sharc/propagation/clear_air_452_aux.py +++ b/sharc/propagation/clear_air_452_aux.py @@ -1,5 +1,6 @@ import numpy as np + def inv_cum_norm(x): if x < 0.000001: x = 0.000001 @@ -17,133 +18,139 @@ def inv_cum_norm(x): D3 = 0.001308 ksi = ((C2 * tx + C1) * tx + C0) / (((D3 * tx + D2) * tx + D1) * tx + 1) - I = ksi - tx - return I + return ksi - tx + def p676_ga(f, p, rho, T, d11): # spectroscopic data for oxygen # f0 a1 a2 a3 a4 a5 a6 - oxygen = [[50.474214, 0.975, 9.651, 6.690, 0.0, 2.566, 6.850], - [50.987745, 2.529, 8.653, 7.170, 0.0, 2.246, 6.800], - [51.503360, 6.193, 7.709, 7.640, 0.0, 1.947, 6.729], - [52.021429, 14.320, 6.819, 8.110, 0.0, 1.667, 6.640], - [52.542418, 31.240, 5.983, 8.580, 0.0, 1.388, 6.526], - [53.066934, 64.290, 5.201, 9.060, 0.0, 1.349, 6.206], - [53.595775, 124.600, 4.474, 9.550, 0.0, 2.227, 5.085], - [54.130025, 227.300, 3.800, 9.960, 0.0, 3.170, 3.750], - [54.671180, 389.700, 3.182, 10.370, 0.0, 3.558, 2.654], - [55.221384, 627.100, 2.618, 10.890, 0.0, 2.560, 2.952], - [55.783815, 945.300, 2.109, 11.340, 0.0, -1.172, 6.135], - [56.264774, 543.400, 0.014, 17.030, 0.0, 3.525, -0.978], - [56.363399, 1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], - [56.968211, 1746.600, 1.255, 12.230, 0.0, -3.545, 6.451], - [57.612486, 2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], - [58.323877, 2363.700, 0.621, 12.950, 0.0, -1.932, 0.436], - [58.446588, 1442.100, 0.083, 14.910, 0.0, 6.768, -1.273], - [59.164204, 2379.900, 0.387, 13.530, 0.0, -6.561, 2.309], - [59.590983, 2090.700, 0.207, 14.080, 0.0, 6.957, -0.776], - [60.306056, 2103.400, 0.207, 14.150, 0.0, -6.395, 0.699], - [60.434778, 2438.000, 0.386, 13.390, 0.0, 6.342, -2.825], - [61.150562, 2479.500, 0.621, 12.920, 0.0, 1.014, -0.584], - [61.800158, 2275.900, 0.910, 12.630, 0.0, 5.014, -6.619], - [62.411220, 1915.400, 1.255, 12.170, 0.0, 3.029, -6.759], - [62.486253, 1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], - [62.997984, 1490.200, 1.654, 11.740, 0.0, 1.856, -6.675], - [63.568526, 1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], - [64.127775, 728.700, 2.617, 10.880, 0.0, -3.036, -2.895], - [64.678910, 461.300, 3.181, 10.380, 0.0, -3.968, -2.590], - [65.224078, 274.000, 3.800, 9.960, 0.0, -3.528, -3.680], - [65.764779, 153.000, 4.473, 9.550, 0.0, -2.548, -5.002], - [66.302096, 80.400, 5.200, 9.060, 0.0, -1.660, -6.091], - [66.836834, 39.800, 5.982, 8.580, 0.0, -1.680, -6.393], - [67.369601, 18.560, 6.818, 8.110, 0.0, -1.956, -6.475], - [67.900868, 8.172, 7.708, 7.640, 0.0, -2.216, -6.545], - [68.431006, 3.397, 8.652, 7.170, 0.0, -2.492, -6.600], - [68.960312, 1.334, 9.650, 6.690, 0.0, -2.773, -6.650], - [118.750334, 940.300, 0.010, 16.640, 0.0, -0.439, 0.079], - [368.498246, 67.400, 0.048, 16.400, 0.0, 0.000, 0.000], - [424.763020, 637.700, 0.044, 16.400, 0.0, 0.000, 0.000], - [487.249273, 237.400, 0.049, 16.000, 0.0, 0.000, 0.000], - [715.392902, 98.100, 0.145, 16.000, 0.0, 0.000, 0.000], - [773.839490, 572.300, 0.141, 16.200, 0.0, 0.000, 0.000], - [834.145546, 183.100, 0.145, 14.700, 0.0, 0.000, 0.000]] + oxygen = [ + [50.474214, 0.975, 9.651, 6.690, 0.0, 2.566, 6.850], + [50.987745, 2.529, 8.653, 7.170, 0.0, 2.246, 6.800], + [51.503360, 6.193, 7.709, 7.640, 0.0, 1.947, 6.729], + [52.021429, 14.320, 6.819, 8.110, 0.0, 1.667, 6.640], + [52.542418, 31.240, 5.983, 8.580, 0.0, 1.388, 6.526], + [53.066934, 64.290, 5.201, 9.060, 0.0, 1.349, 6.206], + [53.595775, 124.600, 4.474, 9.550, 0.0, 2.227, 5.085], + [54.130025, 227.300, 3.800, 9.960, 0.0, 3.170, 3.750], + [54.671180, 389.700, 3.182, 10.370, 0.0, 3.558, 2.654], + [55.221384, 627.100, 2.618, 10.890, 0.0, 2.560, 2.952], + [55.783815, 945.300, 2.109, 11.340, 0.0, -1.172, 6.135], + [56.264774, 543.400, 0.014, 17.030, 0.0, 3.525, -0.978], + [56.363399, 1331.800, 1.654, 11.890, 0.0, -2.378, 6.547], + [56.968211, 1746.600, 1.255, 12.230, 0.0, -3.545, 6.451], + [57.612486, 2120.100, 0.910, 12.620, 0.0, -5.416, 6.056], + [58.323877, 2363.700, 0.621, 12.950, 0.0, -1.932, 0.436], + [58.446588, 1442.100, 0.083, 14.910, 0.0, 6.768, -1.273], + [59.164204, 2379.900, 0.387, 13.530, 0.0, -6.561, 2.309], + [59.590983, 2090.700, 0.207, 14.080, 0.0, 6.957, -0.776], + [60.306056, 2103.400, 0.207, 14.150, 0.0, -6.395, 0.699], + [60.434778, 2438.000, 0.386, 13.390, 0.0, 6.342, -2.825], + [61.150562, 2479.500, 0.621, 12.920, 0.0, 1.014, -0.584], + [61.800158, 2275.900, 0.910, 12.630, 0.0, 5.014, -6.619], + [62.411220, 1915.400, 1.255, 12.170, 0.0, 3.029, -6.759], + [62.486253, 1503.000, 0.083, 15.130, 0.0, -4.499, 0.844], + [62.997984, 1490.200, 1.654, 11.740, 0.0, 1.856, -6.675], + [63.568526, 1078.000, 2.108, 11.340, 0.0, 0.658, -6.139], + [64.127775, 728.700, 2.617, 10.880, 0.0, -3.036, -2.895], + [64.678910, 461.300, 3.181, 10.380, 0.0, -3.968, -2.590], + [65.224078, 274.000, 3.800, 9.960, 0.0, -3.528, -3.680], + [65.764779, 153.000, 4.473, 9.550, 0.0, -2.548, -5.002], + [66.302096, 80.400, 5.200, 9.060, 0.0, -1.660, -6.091], + [66.836834, 39.800, 5.982, 8.580, 0.0, -1.680, -6.393], + [67.369601, 18.560, 6.818, 8.110, 0.0, -1.956, -6.475], + [67.900868, 8.172, 7.708, 7.640, 0.0, -2.216, -6.545], + [68.431006, 3.397, 8.652, 7.170, 0.0, -2.492, -6.600], + [68.960312, 1.334, 9.650, 6.690, 0.0, -2.773, -6.650], + [118.750334, 940.300, 0.010, 16.640, 0.0, -0.439, 0.079], + [368.498246, 67.400, 0.048, 16.400, 0.0, 0.000, 0.000], + [424.763020, 637.700, 0.044, 16.400, 0.0, 0.000, 0.000], + [487.249273, 237.400, 0.049, 16.000, 0.0, 0.000, 0.000], + [715.392902, 98.100, 0.145, 16.000, 0.0, 0.000, 0.000], + [773.839490, 572.300, 0.141, 16.200, 0.0, 0.000, 0.000], + [834.145546, 183.100, 0.145, 14.700, 0.0, 0.000, 0.000], + ] # spectroscopic data for water - vapor # f0 b1 b2 b3 b4 b5 b6 if d11: - vapor = [[22.235080,.1079, 2.144, 26.38, .76, 5.087, 1.00], - [67.803960,.0011,8.732,28.58,.69, 4.930,.82], - [119.995940,.0007,8.353, 29.48,.70, 4.780,.79], - [183.310087, 2.273,.668, 29.06,.77, 5.022,.85], - [321.225630, .0470,6.179, 24.04, .67, 4.398,.54], - [325.152888,1.514, 1.541, 28.23, .64,4.893, .74], - [336.227764,.0010,9.825, 26.93, .69, 4.740, .61], - [380.197353, 11.67, 1.048, 28.11, .54, 5.063,.89], - [390.134508, .0045, 7.347, 21.52, .63, 4.810,.55], - [437.346667, .0632, 5.048, 18.45, .60, 4.230,.48], - [439.150807, .9098, 3.595, 20.07, .63, 4.483,.52], - [443.018343, .1920, 5.048, 15.55, .60, 5.083, .50], - [448.001085, 10.41, 1.405, 25.64, .66, 5.028, .67], - [470.888999, .3254, 3.597, 21.34, .66, 4.506,.65], - [474.689092,1.260, 2.379, 23.20,.65, 4.804,.64], - [488.490108,.2529, 2.852, 25.86,.69,5.201,.72], - [503.568532,.0372, 6.731, 16.12,.61, 3.980,.43], - [504.482692,.0124,6.731, 16.12,.61, 4.010,.45], - [547.676440,.9785,.158,26.00,.70,4.500,1.00], - [552.020960,.1840,.158,26.00,.70,4.500,1.00], - [556.935985,497.0,.159, 30.86,.69,4.552,1.00], - [620.700807,5.015, 2.391, 24.38,.71,4.856,.68], - [645.766085,.0067, 8.633, 18.00,.60,4.000,.50], - [658.005280,.2732,7.816,32.10,.69,4.140, 1.00], - [752.033113,243.4, .396,30.86,.68,4.352,.84], - [841.051732,.0134,8.177, 15.90, .33, 5.760,.45], - [859.965698,.1325,8.055, 30.60,.68, 4.090,.84], - [899.303175,.0547,7.914, 29.85,.68, 4.530,.90], - [902.611085,.0386,8.429,28.65, .70, 5.100,.95], - [906.205957,.1836, 5.110, 24.08,.70,4.700,.53], - [916.171582,8.400,1.441, 26.73,.70, 5.150,.78], - [923.112692,.0079,10.293,29.00,.70, 5.000,.80], - [970.315022, 9.009, 1.919, 25.50,.64,4.940,.67], - [987.926764,134.6,.257, 29.85, .68, 4.550,.90], - [1780.000000,17506., .952, 196.3, 2.00,24.15,5.00]] + vapor = [ + [22.235080, .1079, 2.144, 26.38, .76, 5.087, 1.00], + [67.803960, .0011, 8.732, 28.58, .69, 4.930, .82], + [119.995940, .0007, 8.353, 29.48, .70, 4.780, .79], + [183.310087, 2.273, .668, 29.06, .77, 5.022, .85], + [321.225630, .0470, 6.179, 24.04, .67, 4.398, .54], + [325.152888, 1.514, 1.541, 28.23, .64, 4.893, .74], + [336.227764, .0010, 9.825, 26.93, .69, 4.740, .61], + [380.197353, 11.67, 1.048, 28.11, .54, 5.063, .89], + [390.134508, .0045, 7.347, 21.52, .63, 4.810, .55], + [437.346667, .0632, 5.048, 18.45, .60, 4.230, .48], + [439.150807, .9098, 3.595, 20.07, .63, 4.483, .52], + [443.018343, .1920, 5.048, 15.55, .60, 5.083, .50], + [448.001085, 10.41, 1.405, 25.64, .66, 5.028, .67], + [470.888999, .3254, 3.597, 21.34, .66, 4.506, .65], + [474.689092, 1.260, 2.379, 23.20, .65, 4.804, .64], + [488.490108, .2529, 2.852, 25.86, .69, 5.201, .72], + [503.568532, .0372, 6.731, 16.12, .61, 3.980, .43], + [504.482692, .0124, 6.731, 16.12, .61, 4.010, .45], + [547.676440, .9785, .158, 26.00, .70, 4.500, 1.00], + [552.020960, .1840, .158, 26.00, .70, 4.500, 1.00], + [556.935985, 497.0, .159, 30.86, .69, 4.552, 1.00], + [620.700807, 5.015, 2.391, 24.38, .71, 4.856, .68], + [645.766085, .0067, 8.633, 18.00, .60, 4.000, .50], + [658.005280, .2732, 7.816, 32.10, .69, 4.140, 1.00], + [752.033113, 243.4, .396, 30.86, .68, 4.352, .84], + [841.051732, .0134, 8.177, 15.90, .33, 5.760, .45], + [859.965698, .1325, 8.055, 30.60, .68, 4.090, .84], + [899.303175, .0547, 7.914, 29.85, .68, 4.530, .90], + [902.611085, .0386, 8.429, 28.65, .70, 5.100, .95], + [906.205957, .1836, 5.110, 24.08, .70, 4.700, .53], + [916.171582, 8.400, 1.441, 26.73, .70, 5.150, .78], + [923.112692, .0079, 10.293, 29.00, .70, 5.000, .80], + [970.315022, 9.009, 1.919, 25.50, .64, 4.940, .67], + [987.926764, 134.6, .257, 29.85, .68, 4.550, .90], + [1780.000000, 17506., .952, 196.3, 2.00, 24.15, 5.00], + ] else: - vapor = [[22.235080, 0.1130, 2.143, 28.11, .69, 4.800, 1.00], - [ 67.803960,0.0012, 8.735, 28.58, .69, 4.930, .82], - [119.995940, 0.0008, 8.356, 29.48, .70, 4.780, .79], - [183.310091, 2.4200, .668, 30.50, .64, 5.300,.85], - [321.225644, 0.0483, 6.181, 23.03,.67, 4.690,.54], - [325.152919,1.4990,1.540, 27.83, .68, 4.850,.74], - [336.222601,0.0011, 9.829, 26.93, .69, 4.740,.61], - [380.197372, 11.5200,1.048, 28.73,.54,5.380,.89], - [390.134508,0.0046,7.350, 21.52,.63, 4.810,.55], - [437.346667,0.0650,5.050,18.45,.60,4.230,.48], - [439.150812,0.9218,3.596,21.00,.63, 4.290,.52], - [443.018295,0.1976,5.050,18.60,.60,4.230,.50], - [448.001075,10.3200,1.405,26.32,.66,4.840,.67], - [470.888947,0.3297,3.599,21.52,.66,4.570,.65], - [474.689127,1.2620,2.381,23.55,.65,4.650,.64], - [488.491133,0.2520,2.853,26.02,.69,5.040,.72], - [503.568532,0.0390,6.733,16.12,.61,3.980,.43], - [504.482692,0.0130,6.733,16.12,.61,4.010,.45], - [547.676440,9.7010,.114,26.00,.70,4.500,1.00], - [552.020960,14.7700,.114,26.00,.70,4.500,1.00], - [556.936002,487.4000,.159,32.10,.69,4.110,1.00], - [620.700807,5.0120,2.200,24.38,.71,4.680,.68], - [645.866155,0.0713,8.580,18.00,.60,4.000,.50], - [658.005280,0.3022,7.820,32.10,.69,4.140,1.00], - [752.033227,239.6000,.396,30.60,.68,4.090,.84], - [841.053973,0.0140,8.180,15.90,.33,5.760,.45], - [859.962313,0.1472,7.989,30.60,.68,4.090,.84], - [899.306675,0.0605,7.917,29.85,.68,4.530,.90], - [902.616173,0.0426, 8.432,28.65,.70,5.100,.95], - [906.207325,0.1876,5.111,24.08,.70,4.700,.53], - [916.171582,8.3400,1.442,26.70,.70,4.780,.78], - [923.118427,0.0869,10.220,29.00,.70,5.000,.80], - [970.315022,8.9720,1.920,25.50,.64,4.940,.67], - [987.926764,132.1000,.258,29.85,.68,4.550,.90], - [1780.000000,22300.0000,.952,176.20,.50,30.500,5.00]] + vapor = [ + [22.235080, 0.1130, 2.143, 28.11, .69, 4.800, 1.00], + [67.803960, 0.0012, 8.735, 28.58, .69, 4.930, .82], + [119.995940, 0.0008, 8.356, 29.48, .70, 4.780, .79], + [183.310091, 2.4200, .668, 30.50, .64, 5.300, .85], + [321.225644, 0.0483, 6.181, 23.03, .67, 4.690, .54], + [325.152919, 1.4990, 1.540, 27.83, .68, 4.850, .74], + [336.222601, 0.0011, 9.829, 26.93, .69, 4.740, .61], + [380.197372, 11.5200, 1.048, 28.73, .54, 5.380, .89], + [390.134508, 0.0046, 7.350, 21.52, .63, 4.810, .55], + [437.346667, 0.0650, 5.050, 18.45, .60, 4.230, .48], + [439.150812, 0.9218, 3.596, 21.00, .63, 4.290, .52], + [443.018295, 0.1976, 5.050, 18.60, .60, 4.230, .50], + [448.001075, 10.3200, 1.405, 26.32, .66, 4.840, .67], + [470.888947, 0.3297, 3.599, 21.52, .66, 4.570, .65], + [474.689127, 1.2620, 2.381, 23.55, .65, 4.650, .64], + [488.491133, 0.2520, 2.853, 26.02, .69, 5.040, .72], + [503.568532, 0.0390, 6.733, 16.12, .61, 3.980, .43], + [504.482692, 0.0130, 6.733, 16.12, .61, 4.010, .45], + [547.676440, 9.7010, .114, 26.00, .70, 4.500, 1.00], + [552.020960, 14.7700, .114, 26.00, .70, 4.500, 1.00], + [556.936002, 487.4000, .159, 32.10, .69, 4.110, 1.00], + [620.700807, 5.0120, 2.200, 24.38, .71, 4.680, .68], + [645.866155, 0.0713, 8.580, 18.00, .60, 4.000, .50], + [658.005280, 0.3022, 7.820, 32.10, .69, 4.140, 1.00], + [752.033227, 239.6000, .396, 30.60, .68, 4.090, .84], + [841.053973, 0.0140, 8.180, 15.90, .33, 5.760, .45], + [859.962313, 0.1472, 7.989, 30.60, .68, 4.090, .84], + [899.306675, 0.0605, 7.917, 29.85, .68, 4.530, .90], + [902.616173, 0.0426, 8.432, 28.65, .70, 5.100, .95], + [906.207325, 0.1876, 5.111, 24.08, .70, 4.700, .53], + [916.171582, 8.3400, 1.442, 26.70, .70, 4.780, .78], + [923.118427, 0.0869, 10.220, 29.00, .70, 5.000, .80], + [970.315022, 8.9720, 1.920, 25.50, .64, 4.940, .67], + [987.926764, 132.1000, .258, 29.85, .68, 4.550, .90], + [1780.000000, 22300.0000, .952, 176.20, .50, 30.500, 5.00], + ] oxygen = np.array(oxygen) vapor = np.array(vapor) @@ -179,13 +186,15 @@ def p676_ga(f, p, rho, T, d11): delta = (a5 + a6 * theta) * 1e-4 * (p + e) * theta ** (0.8) - Fi = f / fi* ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) + \ - (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2)) + Fi = f / fi * ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) + + (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2)) d = 5.6e-4 * (p + e) * theta ** (0.8) - Ndf = f * p * theta ** 2 * (6.14e-5 / (d * (1 + (f / d) ** 2)) + \ - 1.4e-12 * p * theta ** (1.5) / (1 + 1.9e-5 * f ** (1.5)) ) + Ndf = f * p * theta ** 2 * ( + 6.14e-5 / (d * (1 + (f / d) ** 2)) + + 1.4e-12 * p * theta ** (1.5) / (1 + 1.9e-5 * f ** (1.5)) + ) # specific attenuation due to dry air(oxygen, pressureinducednitrogen # and non - resonant Debye attenuation), equations(1 - 2) @@ -196,7 +205,7 @@ def p676_ga(f, p, rho, T, d11): if f <= 118.750343: g_0 = 0.182 * f * (sum(Si * Fi) + Ndf) else: - g_0 = 0.182 * f * (sum(Si[37:-1]*Fi[37:-1]) + Ndf) + g_0 = 0.182 * f * (sum(Si[37:-1] * Fi[37:-1]) + Ndf) # vapor computation fi = vapor[:, 0] @@ -212,10 +221,10 @@ def p676_ga(f, p, rho, T, d11): delta = 0 Fi = f / fi * ((df - delta * (fi - f)) / ((fi - f) ** 2 + df ** 2) + - (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2)) + (df - delta * (fi + f)) / ((fi + f) ** 2 + df ** 2)) # specific attenuation due to watervapour, equations(1 - 2) - g_w = 0.182 * f * np.sum(Si* Fi) + g_w = 0.182 * f * np.sum(Si * Fi) return g_0, g_w diff --git a/sharc/propagation/propagation.py b/sharc/propagation/propagation.py index 4c0a0f7fe..d72ca7b74 100644 --- a/sharc/propagation/propagation.py +++ b/sharc/propagation/propagation.py @@ -8,6 +8,10 @@ from abc import ABC, abstractmethod import numpy as np +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters + + class Propagation(ABC): """ Abstract base class for propagation models @@ -15,7 +19,33 @@ class Propagation(ABC): def __init__(self, random_number_gen: np.random.RandomState): self.random_number_gen = random_number_gen + # Inicates whether this propagation model is for links between earth and space + self.is_earth_space_model = False @abstractmethod - def get_loss(self, *args, **kwargs) -> np.array: - pass + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing station_a + station_b : StationManager + StationManager container representing station_a + params : Parameters + Simulation parameters needed for the propagation class. + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ diff --git a/sharc/propagation/propagation_abg.py b/sharc/propagation/propagation_abg.py index 88128fe2e..4e4628870 100644 --- a/sharc/propagation/propagation_abg.py +++ b/sharc/propagation/propagation_abg.py @@ -5,9 +5,12 @@ @author: LeticiaValle_Mac """ -from sharc.propagation.propagation import Propagation - import numpy as np +from multipledispatch import dispatch + +from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters class PropagationABG(Propagation): @@ -16,16 +19,73 @@ class PropagationABG(Propagation): Loss Models for 5G Urban Microand Macro-Cellular Scenarios" """ - def __init__(self, random_number_gen: np.random.RandomState): + def __init__( + self, random_number_gen: np.random.RandomState, + alpha=3.4, beta=19.2, gamma=2.3, building_loss=20, shadowing_sigma_dB=6.5, + ): super().__init__(random_number_gen) - self.alpha = 3.4 - self.beta = 19.2 - self.gamma = 2.3 + self.alpha = alpha + self.beta = beta + self.gamma = gamma self.building_loss = 20 self.shadowing_sigma_dB = 6.5 + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the get_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS - def get_loss(self, *args, **kwargs) -> np.array: + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled: + _, distances_3d, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distances_3d = station_a.get_3d_distance_to(station_b) + + indoor_stations = station_a.indoor + + loss = \ + self.get_loss( + distances_3d, + frequency * np.ones(distances_3d.shape), + indoor_stations, + params.imt.shadowing, + ) + + return loss + + @dispatch(np.ndarray, np.ndarray, np.ndarray, bool) + def get_loss(self, distance: np.array, frequency: np.array, indoor_stations: np.array, shadowing: bool) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing (if shadowing is to be added) @@ -45,40 +105,21 @@ def get_loss(self, *args, **kwargs) -> np.array: array with path loss values with dimensions of distance_2D """ - f = kwargs["frequency"] - indoor_stations = kwargs["indoor_stations"] - - if "distance_3D" in kwargs: - d = kwargs["distance_3D"] - else: - d = kwargs["distance_2D"] - - if "alpha" in kwargs: - self.alpha = kwargs["alpha"] - - if "beta" in kwargs: - self.beta = kwargs["beta"] - - if "gamma" in kwargs: - self.gamma = kwargs["gamma"] - - if "shadowing" in kwargs: - std = kwargs["shadowing"] - else: - std = False - - if std: - shadowing = self.random_number_gen.normal(0, self.shadowing_sigma_dB, d.shape) + if shadowing: + shadowing = self.random_number_gen.normal( + 0, self.shadowing_sigma_dB, distance.shape, + ) else: shadowing = 0 - building_loss = self.building_loss*indoor_stations + building_loss = self.building_loss * np.tile(indoor_stations, (distance.shape[1], 1)).transpose() - loss = 10*self.alpha*np.log10(d) + self.beta + 10*self.gamma*np.log10(f*1e-3) + \ - shadowing + building_loss + loss = 10 * self.alpha * np.log10(distance) + self.beta + 10 * self.gamma * np.log10(frequency * 1e-3) + \ + shadowing + building_loss return loss + if __name__ == '__main__': ########################################################################### @@ -90,30 +131,38 @@ def get_loss(self, *args, **kwargs) -> np.array: import matplotlib.pyplot as plt shadowing_std = 0 - distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis] - freq = 26000*np.ones(distance_2D.shape) - h_bs = 25*np.ones(len(distance_2D[:,0])) - h_ue = 1.5*np.ones(len(distance_2D[0,:])) + distance_2D = np.linspace(1, 1000, num=1000)[:, np.newaxis] + freq = 26000 * np.ones(distance_2D.shape) + h_bs = 25 * np.ones(len(distance_2D[:, 0])) + h_ue = 1.5 * np.ones(len(distance_2D[0, :])) h_e = np.zeros(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) + distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2) random_number_gen = np.random.RandomState(101) - + uma = PropagationUMa(random_number_gen) - umi = PropagationUMi(random_number_gen) + umi = PropagationUMi(random_number_gen, 18) abg = PropagationABG(random_number_gen) freespace = PropagationFreeSpace(random_number_gen) - uma_los = uma.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - uma_nlos = uma.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - umi_los = umi.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - umi_nlos = umi.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - fs = freespace.get_loss(distance_2D=distance_2D, frequency=freq) - abg_los = abg.get_loss(distance_2D=distance_2D, frequency=freq, indoor_stations=0) - - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + uma_los = uma.get_loss_los( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + uma_nlos = uma.get_loss_nlos( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + umi_los = umi.get_loss_los( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + umi_nlos = umi.get_loss_nlos( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + fs = freespace.get_loss(distance_2D, freq) + abg_los = abg.get_loss(distance_2D, freq, np.zeros(shape=distance_2D.shape, dtype=bool,), False,) + + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() - #ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) + # ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) ax.semilogx(distance_2D, uma_los, "-r", label="UMa LOS") ax.semilogx(distance_2D, uma_nlos, "--r", label="UMa NLOS") @@ -125,8 +174,8 @@ def get_loss(self, *args, **kwargs) -> np.array: plt.title("Path loss models") plt.xlabel("distance [m]") plt.ylabel("path loss [dB]") - plt.xlim((0, distance_2D[-1,0])) - #plt.ylim((0, 1.1)) + plt.xlim((0, distance_2D[-1, 0])) + # plt.ylim((0, 1.1)) plt.legend(loc="upper left") plt.tight_layout() plt.grid() diff --git a/sharc/propagation/propagation_building_entry_loss.py b/sharc/propagation/propagation_building_entry_loss.py index 1b1ee1112..02a78ff27 100644 --- a/sharc/propagation/propagation_building_entry_loss.py +++ b/sharc/propagation/propagation_building_entry_loss.py @@ -16,8 +16,10 @@ class PropagationBuildingEntryLoss(Propagation): Implements the building entry loss according to ITU-R P.2109-0 (Prediction of Building Entry Loss) """ - def get_loss(self, frequency_MHz, elevation, prob="RANDOM", - building_class="TRADITIONAL", test = False) -> np.array: + def get_loss( + self, frequency_MHz, elevation, prob="RANDOM", + building_class="TRADITIONAL", test=False, + ) -> np.array: """ Calculates building loss @@ -86,22 +88,33 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM", return loss + if __name__ == '__main__': entry_loss = PropagationBuildingEntryLoss(np.random.RandomState()) - freq_GHz_log = np.arange(-1,2.1,.1) + freq_GHz_log = np.arange(-1, 2.1, .1) freq_GHz = 10 ** freq_GHz_log freq_MHz = freq_GHz * 1000 # Plot median BLE mu_1, for comparison with ITU-R P2109-0 plt.figure() - median_loss_traditional = entry_loss.get_loss( freq_MHz, 0, prob=.5, - building_class="TRADITIONAL", test=True) - median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 0, prob=.5, - building_class="THERMALLY_EFFICIENT", test=True) - plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 0deg") - plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 0deg") + median_loss_traditional = entry_loss.get_loss( + freq_MHz, 0, prob=.5, + building_class="TRADITIONAL", test=True, + ) + median_loss_therm_eff = entry_loss.get_loss( + freq_MHz, 0, prob=.5, + building_class="THERMALLY_EFFICIENT", test=True, + ) + plt.semilogx( + freq_GHz, median_loss_traditional, + '-', label="TRADITIONAL, 0deg", + ) + plt.semilogx( + freq_GHz, median_loss_therm_eff, '--', + label="THERMALLY_EFFICIENT, 0deg", + ) plt.legend(title="Building Type, elevation") plt.grid() @@ -112,31 +125,61 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM", # Plot median loss at different angles, # 0 degrees plt.figure() - median_loss_traditional = entry_loss.get_loss( freq_MHz, 0, prob=.5, - building_class="TRADITIONAL") - median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 0, prob=.5, - building_class="THERMALLY_EFFICIENT") - - plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 0deg") - plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 0deg") + median_loss_traditional = entry_loss.get_loss( + freq_MHz, 0, prob=.5, + building_class="TRADITIONAL", + ) + median_loss_therm_eff = entry_loss.get_loss( + freq_MHz, 0, prob=.5, + building_class="THERMALLY_EFFICIENT", + ) + + plt.semilogx( + freq_GHz, median_loss_traditional, + '-', label="TRADITIONAL, 0deg", + ) + plt.semilogx( + freq_GHz, median_loss_therm_eff, '--', + label="THERMALLY_EFFICIENT, 0deg", + ) # 45 degrees - median_loss_traditional = entry_loss.get_loss( freq_MHz, 45, prob=.5, - building_class="TRADITIONAL") - median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 45, prob=.5, - building_class="THERMALLY_EFFICIENT") - - plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 45deg") - plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 45deg") + median_loss_traditional = entry_loss.get_loss( + freq_MHz, 45, prob=.5, + building_class="TRADITIONAL", + ) + median_loss_therm_eff = entry_loss.get_loss( + freq_MHz, 45, prob=.5, + building_class="THERMALLY_EFFICIENT", + ) + + plt.semilogx( + freq_GHz, median_loss_traditional, + '-', label="TRADITIONAL, 45deg", + ) + plt.semilogx( + freq_GHz, median_loss_therm_eff, '--', + label="THERMALLY_EFFICIENT, 45deg", + ) # 90 deg - median_loss_traditional = entry_loss.get_loss( freq_MHz, 90, prob=.5, - building_class="TRADITIONAL") - median_loss_therm_eff = entry_loss.get_loss(freq_MHz, 90, prob=.5, - building_class="THERMALLY_EFFICIENT") - - plt.semilogx(freq_GHz, median_loss_traditional, '-', label="TRADITIONAL, 90deg") - plt.semilogx(freq_GHz, median_loss_therm_eff, '--', label="THERMALLY_EFFICIENT, 90deg") + median_loss_traditional = entry_loss.get_loss( + freq_MHz, 90, prob=.5, + building_class="TRADITIONAL", + ) + median_loss_therm_eff = entry_loss.get_loss( + freq_MHz, 90, prob=.5, + building_class="THERMALLY_EFFICIENT", + ) + + plt.semilogx( + freq_GHz, median_loss_traditional, + '-', label="TRADITIONAL, 90deg", + ) + plt.semilogx( + freq_GHz, median_loss_therm_eff, '--', + label="THERMALLY_EFFICIENT, 90deg", + ) plt.legend(title="Building Type, elevation") plt.grid() @@ -146,30 +189,29 @@ def get_loss(self, frequency_MHz, elevation, prob="RANDOM", plt.show() plt.close() - + # parameters freq_MHz = 40e3 - probability = np.linspace(0,1,num=1000) + probability = np.linspace(0, 1, num=1000) elevations = np.array([0, 45, 90]) - loss = np.zeros((len(elevations),len(probability))) - + loss = np.zeros((len(elevations), len(probability))) + # calculate loss - for n,el in enumerate(elevations): - for m,pb in enumerate(probability): - loss[n,m] = entry_loss.get_loss( freq_MHz, el, prob=pb, - building_class="TRADITIONAL") - - for n,el in enumerate(elevations): + for n, el in enumerate(elevations): + for m, pb in enumerate(probability): + loss[n, m] = entry_loss.get_loss( + freq_MHz, el, prob=pb, + building_class="TRADITIONAL", + ) + + for n, el in enumerate(elevations): lbl = str(el) + " deg" - plt.plot(loss[n],probability,label=lbl) - + plt.plot(loss[n], probability, label=lbl) + plt.xlabel("Building Entry Loss [dB]") plt.ylabel("Probability that loss is exceeded") - plt.ylim((0,1)) + plt.ylim((0, 1)) # plt.xlim((0,65)) plt.grid() plt.legend() plt.show() - - - diff --git a/sharc/propagation/propagation_clear_air_452.py b/sharc/propagation/propagation_clear_air_452.py index 0aebd71f1..2db24e3e6 100644 --- a/sharc/propagation/propagation_clear_air_452.py +++ b/sharc/propagation/propagation_clear_air_452.py @@ -1,32 +1,37 @@ # -*- coding: utf-8 -*- - -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- """ Created on Mon May 22 15:10:11 2017 """ -from sharc.propagation.propagation import Propagation +import numpy as np +from multipledispatch import dispatch +from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters +from sharc.parameters.parameters_p452 import ParametersP452 from sharc.propagation.clear_air_452_aux import p676_ga from sharc.propagation.clear_air_452_aux import inv_cum_norm from sharc.support.enumerations import StationType from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss -import numpy as np class PropagationClearAir(Propagation): """ Basic transmission loss due to free-space propagation and attenuation by atmospheric gases """ - def __init__(self, random_number_gen: np.random.RandomState): + # pylint: disable=function-redefined + # pylint: disable=arguments-renamed + + def __init__(self, random_number_gen: np.random.RandomState, model_params: ParametersP452): super().__init__(random_number_gen) self.clutter = PropagationClutterLoss(random_number_gen) - self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen) - + self.building_entry = PropagationBuildingEntryLoss( + self.random_number_gen, + ) self.building_loss = 20 - + self.model_params = model_params @staticmethod def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r): @@ -45,10 +50,10 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r): if ha > htg: - Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a) - Aht = 10.25 * Ffc * np.exp(-dk) * (1 - np.tanh(6 * (htg / ha - 0.625))) - 0.33 # (57) - - flagAht = 1 + Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a) + Aht = 10.25 * Ffc * \ + np.exp(-dk) * (1 - np.tanh(6 * (htg / ha - 0.625))) - \ + 0.33 # (57) kk = np.nonzero(d >= dk) @@ -63,10 +68,10 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r): dk = dk_r if ha > hrg: - Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a) - Ahr = 10.25 * Ffc * np.exp(-dk) * (1 - np.tanh(6 * (hrg / ha - 0.625))) - 0.33 # (57) - - flagAhr = 1 + Ffc = 0.25 + 0.375 * (1 + np.tanh(7.5 * (f - 0.5))) # (57a) + Ahr = 10.25 * Ffc * \ + np.exp(-dk) * (1 - np.tanh(6 * (hrg / ha - 0.625))) - \ + 0.33 # (57) kk = np.nonzero(d <= d[-1] - dk) if kk.size: @@ -78,15 +83,15 @@ def closs_corr(f, d, h, zone, htg, hrg, ha_t, ha_r, dk_t, dk_r): # Modify the path - if (index2 - index1 < 3): # at least two points between the clutter at Tx and Rx sides + if (index2 - index1 < 3): # at least two points between the clutter at Tx and Rx sides error_message = "tl_p452: closs_corr: the sum of clutter nominal distances is larger than the path length." raise ValueError(error_message) - dc = d[index1-1:index2] - d[index1-1] - hc = h[index1-1:index2] - zonec = zone[index1-1:index2] + dc = d[index1 - 1:index2] - d[index1 - 1] + hc = h[index1 - 1:index2] + zonec = zone[index1 - 1:index2] - return dc, hc, zonec,htgc, hrgc, Aht, Ahr + return dc, hc, zonec, htgc, hrgc, Aht, Ahr @staticmethod def longest_cont_dist(d, zone, zone_r): @@ -97,10 +102,10 @@ def longest_cont_dist(d, zone, zone_r): else: aux = zone == zone_r - aux = np.append(0,np.append(aux,0)) + aux = np.append(0, np.append(aux, 0)) aux = np.diff(aux) - start = np.where(aux==1)[0] - stop = np.where(aux==-1)[0] - 1 + start = np.where(aux == 1)[0] + stop = np.where(aux == -1)[0] - 1 start = np.atleast_1d(start) stop = np.atleast_1d(stop) @@ -121,9 +126,12 @@ def longest_cont_dist(d, zone, zone_r): @staticmethod def beta0(phi, dtm, dlm): - tau = 1 - np.exp(-(4.12 * 1e-4 * dlm ** 2.41)) # (3a) + tau = 1 - np.exp(-(4.12 * 1e-4 * dlm ** 2.41)) # (3a) - mu1 = ( 10 ** (-dtm / (16 - 6.6 * tau)) + 10 ** (-5 * (0.496 + 0.354 * tau)))** 0.2 + mu1 = ( + 10 ** (-dtm / (16 - 6.6 * tau)) + 10 ** + (-5 * (0.496 + 0.354 * tau)) + ) ** 0.2 indices = np.nonzero(mu1 > 1) mu1[indices] = 1 @@ -159,26 +167,31 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f): # Section 5.1.6.2 v1 = 0 - for ii in range (1,n): - v1 = v1 + (d[ii] - d[ii - 1]) * (h[ii] + h[ii - 1]) # Eq(161) + for ii in range(1, n): + v1 = v1 + (d[ii] - d[ii - 1]) * (h[ii] + h[ii - 1]) # Eq(161) v2 = 0 for ii in range(2, n): - v2 = v2 + (d[ii] - d[ii - 1]) * (h[ii] * (2 * d[ii] + d[ii - 1]) + h[ii - 1] * (d[ii] + 2 * d[ii - 1])) # Eq(162) + v2 = v2 + (d[ii] - d[ii - 1]) * ( + h[ii] * ( + 2 * d[ii] + d[ii - 1] + # Eq(162) + ) + h[ii - 1] * (d[ii] + 2 * d[ii - 1]) + ) - hst = (2 * v1 * dtot - v2) / dtot ** 2 # Eq(163) - hsr = (v2 - v1 * dtot) / dtot ** 2 # Eq(164) + hst = (2 * v1 * dtot - v2) / dtot ** 2 # Eq(163) + hsr = (v2 - v1 * dtot) / dtot ** 2 # Eq(164) # Section 5.1.6.3 - HH = h - (hts * (dtot - d) + hrs * d) / dtot # Eq(165d) - hobs = max(HH[1:n - 1]) # Eq(165a) + HH = h - (hts * (dtot - d) + hrs * d) / dtot # Eq(165d) + hobs = max(HH[1:n - 1]) # Eq(165a) - alpha_obt = max(HH[1:n - 1]/ d[1:n - 1]) # Eq(165b) - alpha_obr = max(HH[1:n - 1]/ (dtot - d[1:n - 1])) # Eq(165c) + alpha_obt = max(HH[1:n - 1] / d[1:n - 1]) # Eq(165b) + alpha_obr = max(HH[1:n - 1] / (dtot - d[1:n - 1])) # Eq(165c) # Calculate provisional values for the Tx and Rx smooth surface heights - gt = alpha_obt / (alpha_obt + alpha_obr) # Eq(166e) - gr = alpha_obr / (alpha_obt + alpha_obr) # Eq(166f) + gt = alpha_obt / (alpha_obt + alpha_obr) # Eq(166e) + gr = alpha_obr / (alpha_obt + alpha_obr) # Eq(166f) if hobs <= 0: hstp = hst @@ -201,19 +214,22 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f): # Interfering antenna horizon elevation angle and distance ii = np.arange(1, n - 1) - theta = 1000 * np.arctan((h[ii] - hts)/ (1000 * d[ii]) - d[ii] / (2 * ae)) + theta = 1000 * np.arctan((h[ii] - hts) / + (1000 * d[ii]) - d[ii] / (2 * ae)) - #theta(theta < 0) = 0; % condition below equation(152) + # theta(theta < 0) = 0; % condition below equation(152) theta_t = max(theta) - theta_td = 1000 * np.arctan((hrs - hts)/ (1000 * dtot) - dtot / (2 * ae)) - theta_rd = 1000 * np.arctan((hts - hrs)/ (1000 * dtot) - dtot / (2 * ae)) + theta_td = 1000 * np.arctan((hrs - hts) / + (1000 * dtot) - dtot / (2 * ae)) + theta_rd = 1000 * np.arctan((hts - hrs) / + (1000 * dtot) - dtot / (2 * ae)) if theta_t > theta_td: - pathtype = 2 # transhorizon + pathtype = 2 # transhorizon else: - pathtype = 1 # los + pathtype = 1 # los kindex = np.nonzero(theta == theta_t) @@ -223,7 +239,9 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f): # Interfered-with antenna horizon elevation angle and distance - theta = 1000 * np.arctan((h[ii] - hrs)/(1000 * (dtot - d[ii])) - (dtot - d[ii])/(2 * ae)) + theta = 1000 * \ + np.arctan((h[ii] - hrs) / (1000 * (dtot - d[ii])) - + (dtot - d[ii]) / (2 * ae)) # theta(theta < 0) = 0; @@ -238,27 +256,27 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f): theta_t = theta_td theta_r = theta_rd - ii = np.arange(1,n - 1) + ii = np.arange(1, n - 1) lamb = 0.3 / f Ce = 1 / ae - nu = (h[ii] + 500 * Ce * d[ii] * (dtot-d[ii])- (hts * (dtot- d[ii]) + hrs * d[ii]) / dtot)* \ - np.sqrt(0.002 * dtot/( lamb * d[ii]*(dtot-d[ii]))) + nu = (h[ii] + 500 * Ce * d[ii] * (dtot - d[ii]) - (hts * (dtot - d[ii]) + hrs * d[ii]) / dtot) * \ + np.sqrt(0.002 * dtot / (lamb * d[ii] * (dtot - d[ii]))) numax = max(nu) kindex = np.nonzero(nu == numax) lt = kindex[-1] + 1 dlt = d[lt] dlr = dtot - dlt - kindex = np.nonzero(dlr <= dtot -d[ii]) + kindex = np.nonzero(dlr <= dtot - d[ii]) lr = kindex[0][-1] + 1 # Angular distance theta_tot = 1e3 * dtot / ae + theta_t + theta_r - #Section 5.1.6.4 Ducting / layer-reflection model + # Section 5.1.6.4 Ducting / layer-reflection model # Calculate the smooth-Earth heights at transmitter and receiver as # required for the roughness factor @@ -269,11 +287,11 @@ def smooth_earth_heights(d, h, htg, hrg, ae, f): # Slope of the smooth - Earth surface m = (hsr - hst) / dtot - #The terminal effective heigts for the ducting / layer - reflection model + # The terminal effective heigts for the ducting / layer - reflection model hte = htg + h[0] - hst hre = hrg + h[-1] - hsr - ii = np.arange(lt,lr+1) + ii = np.arange(lt, lr + 1) hm = max(h[ii] - (hst + m * d[ii])) return hst, hsr, hstd, hsrd, hte, hre, hm, dlt, dlr, theta_t, theta_r, theta_tot, pathtype @@ -283,7 +301,7 @@ def path_fraction(d, zone, zone_r): dm = 0 aux = np.nonzero(zone == zone_r) - start = aux[0] # actually find_intervals + start = aux[0] # actually find_intervals stop = aux[-1] start = np.atleast_1d(start) stop = np.atleast_1d(stop) @@ -334,7 +352,7 @@ def pl_los(d, f, p, b0, w, T, press, dlt, dlr): return Lbfsg, Lb0p, Lb0b @staticmethod - def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr ): + def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr): # Frequency dependent loss @@ -357,12 +375,15 @@ def tl_tropo(dtot, theta, f, p, T, press, N0, Gt, Gr ): # percentage p, below 50 # is given - Lbs = 190 + Lf + 20 * np.log10(dtot) + 0.573 * theta - 0.15 * N0 + Lc + Ag - 10.1 * (-np.log10(p / 50)) ** (0.7) + Lbs = 190 + Lf + 20 * np.log10(dtot) + 0.573 * theta - \ + 0.15 * N0 + Lc + Ag - 10.1 * (-np.log10(p / 50)) ** (0.7) return Lbs @staticmethod - def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, theta_r, f, p, T, press, - omega, ae, b0): + def tl_anomalous( + dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, theta_r, f, p, T, press, + omega, ae, b0, + ): Alf = 0 if f < 0.5: @@ -378,10 +399,16 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, Asr = 0 if theta_t1 > 0: - Ast = 20 * np.log10(1 + 0.361 * theta_t1 * np.sqrt(f * dlt)) + 0.264 * theta_t1 * f ** (1 / 3) + Ast = 20 * np.log10( + 1 + 0.361 * theta_t1 * + np.sqrt(f * dlt), + ) + 0.264 * theta_t1 * f ** (1 / 3) if theta_r1 > 0: - Asr = 20 * np.log10(1 + 0.361 * theta_r1 * np.sqrt(f * dlr)) + 0.264 * theta_r1 * f ** (1 / 3) + Asr = 20 * np.log10( + 1 + 0.361 * theta_r1 * + np.sqrt(f * dlr), + ) + 0.264 * theta_r1 * f ** (1 / 3) # over - sea surface duct coupling correction for the interfering and # interfered-with stations(49) and (49a) @@ -391,12 +418,14 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, if dct <= 5: if dct <= dlt: if omega >= 0.75: - Act = -3 * np.exp(-0.25 * dct * dct) * (1 + np.tanh(0.07 * (50 - hts))) + Act = -3 * np.exp(-0.25 * dct * dct) * \ + (1 + np.tanh(0.07 * (50 - hts))) if dcr <= 5: if dcr <= dlr: if omega >= 0.75: - Acr = -3 * np.exp(-0.25 * dcr * dcr) * (1 + np.tanh(0.07 * (50 - hrs))) + Acr = -3 * np.exp(-0.25 * dcr * dcr) * \ + (1 + np.tanh(0.07 * (50 - hrs))) # specific attenuation(51) gamma_d = 5e-5 * ae * f ** (1 / 3) @@ -437,13 +466,15 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, beta = b0 * mu2 * mu3 - #beta = max(beta, eps); % to avoid division by zero + # beta = max(beta, eps); % to avoid division by zero Gamma = 1.076 / (2.0058 - np.log10(beta)) ** 1.012 * \ - np.exp(-(9.51 - 4.8 * np.log10(beta) + 0.198 * (np.log10(beta)) ** 2) * 1e-6 * dtot ** (1.13)) + np.exp( + -(9.51 - 4.8 * np.log10(beta) + 0.198 * (np.log10(beta)) ** 2) * 1e-6 * dtot ** (1.13),) # time percentage variablity(cumulative distribution): - Ap = -12 + (1.2 + 3.7e-3 * dtot) * np.log10(p / beta) + 12 * (p / beta) ** Gamma + Ap = -12 + (1.2 + 3.7e-3 * dtot) * \ + np.log10(p / beta) + 12 * (p / beta) ** Gamma # time percentage and angular - distance dependent losses within the # anomalous propagation mechanism @@ -462,17 +493,18 @@ def tl_anomalous(dtot, dlt, dlr, dct, dcr, dlm, hts, hrs, hte, hre, hm, theta_t, # total of fixed coupling losses(except for local clutter losses) between # the antennas and the anomalous propagation structure within the atmosphere (47) - Af = 102.45 + 20 * np.log10(f) + 20 * np.log10(dlt + dlr) + Alf + Ast + Asr + Act + Acr; + Af = 102.45 + 20 * \ + np.log10(f) + 20 * np.log10(dlt + dlr) + \ + Alf + Ast + Asr + Act + Acr # total basic transmission loss occuring during periods of anomalaous # propagation - Lba = Af + Adp + Ag; + Lba = Af + Adp + Ag return Lba @staticmethod - def dl_bull(d, h, hts, hrs, ap, f): # Effective Earth curvature Ce(km ^ -1) @@ -482,7 +514,7 @@ def dl_bull(d, h, hts, hrs, ap, f): lamb = 0.3 / f # Complete path length - dtot = d[-1]-d[0] + dtot = d[-1] - d[0] # Find the intermediate profile point with the highest slope of the line # from the transmitter to the point @@ -496,33 +528,41 @@ def dl_bull(d, h, hts, hrs, ap, f): # LoS path Str = (hrs - hts) / dtot - if Stim < Str: #Case 1, Path is LoS + if Stim < Str: # Case 1, Path is LoS # Find the intermediate profile point with the highest diffraction parameter nu: numax = np.max( - (hi + 500 * Ce * di* (dtot - di) - (hts * (dtot - di) + hrs * di) / dtot)* - np.sqrt(0.002 * dtot/ (lamb *di * (dtot - di)))) + ( + hi + 500 * Ce * di * (dtot - di) - + (hts * (dtot - di) + hrs * di) / dtot + ) * + np.sqrt(0.002 * dtot / (lamb * di * (dtot - di))), + ) Luc = 0 if numax > -0.78: - Luc = 6.9 + 20 * np.log10(np.sqrt((numax - 0.1) ** 2 + 1) + numax - 0.1) + Luc = 6.9 + 20 * \ + np.log10(np.sqrt((numax - 0.1) ** 2 + 1) + numax - 0.1) else: # Path is transhorizon # Find the intermediate profile pointwith the highest slope of the # line from the receiver to the point - Srim = np.max((hi + 500 * Ce * di * (dtot - di) - hrs) / (dtot - di)) + Srim = np.max( + (hi + 500 * Ce * di * (dtot - di) - hrs) / (dtot - di), + ) # Calculate the distance of the Bullington point from the transmitter: dbp = (hrs - hts + Srim * dtot) / (Stim + Srim) # Calculate the diffraction parameter, nub, for the Bullington point nub = (hts + Stim * dbp - (hts * (dtot - dbp) + hrs * dbp) / dtot) * \ - np.sqrt(0.002 * dtot / (lamb *dbp*(dtot - dbp))) + np.sqrt(0.002 * dtot / (lamb * dbp * (dtot - dbp))) # The knife - edge loss for the Bullington point is given by Luc = 0 if nub > -0.78: - Luc = 6.9 + 20 * np.log10(np.sqrt((nub - 0.1) ** 2 + 1) + nub - 0.1) + Luc = 6.9 + 20 * \ + np.log10(np.sqrt((nub - 0.1) ** 2 + 1) + nub - 0.1) # For Luc calculated using either(17) or (21), Bullington diffraction loss # for the path is given by @@ -533,18 +573,22 @@ def dl_bull(d, h, hts, hrs, ap, f): def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f): # Normalized factor for surface admittance for horizontal (1) and vertical # (2) polarizations - K = np.empty(2) - K[0] = 0.036 * (adft * f)** (-1/3) * ((epsr - 1) ** 2 + (18 * sigma / f)** 2)** (-1 / 4) - K[1] = K[0] * (epsr** 2 + (18 * sigma / f)** 2)** (1/2) + K = np.empty(2) + K[0] = 0.036 * (adft * f) ** (-1 / 3) * ( + (epsr - 1) ** + 2 + (18 * sigma / f) ** 2 + ) ** (-1 / 4) + K[1] = K[0] * (epsr ** 2 + (18 * sigma / f) ** 2) ** (1 / 2) # Earth ground / polarization parameter - beta_dft = (1 + 1.6 * K** 2 + 0.67 * K**4)/(1 + 4.5 * K** 2 + 1.53 * K** 4) + beta_dft = (1 + 1.6 * K ** 2 + 0.67 * K**4) / \ + (1 + 4.5 * K ** 2 + 1.53 * K ** 4) # Normalized distance - X = 21.88 * beta_dft * (f/ adft ** 2)** (1 / 3) * d + X = 21.88 * beta_dft * (f / adft ** 2) ** (1 / 3) * d # Normalized transmitter and receiver heights - Yt = 0.9575 * beta_dft * (f** 2 / adft) ** (1 / 3) * hte + Yt = 0.9575 * beta_dft * (f ** 2 / adft) ** (1 / 3) * hte Yr = 0.9575 * beta_dft * (f ** 2 / adft) ** (1 / 3) * hre # Calculate the distance term given by: @@ -553,7 +597,7 @@ def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f): if X[ii] >= 1.6: Fx[ii] = 11 + 10 * np.log10(X[ii]) - 17.6 * X[ii] else: - Fx[ii] = -20 * np.log10(X[ii]) - 5.6488 * (X[ii])** 1.425 + Fx[ii] = -20 * np.log10(X[ii]) - 5.6488 * (X[ii]) ** 1.425 Bt = beta_dft * Yt Br = beta_dft * Yr @@ -563,12 +607,14 @@ def dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f): for ii in range(2): if Bt[ii] > 2: - GYt[ii] = 17.6 * (Bt[ii] - 1.1) ** 0.5 - 5 * np.log10(Bt[ii] - 1.1) - 8 + GYt[ii] = 17.6 * (Bt[ii] - 1.1) ** 0.5 - 5 * \ + np.log10(Bt[ii] - 1.1) - 8 else: GYt[ii] = 20 * np.log10(Bt[ii] + 0.1 * Bt[ii] ** 3) if Br[ii] > 2: - GYr[ii] = 17.6 * (Br[ii] - 1.1)** 0.5 - 5 * np.log10(Br[ii] - 1.1) - 8 + GYr[ii] = 17.6 * (Br[ii] - 1.1) ** 0.5 - 5 * \ + np.log10(Br[ii] - 1.1) - 8 else: GYr[ii] = 20 * np.log10(Br[ii] + 0.1 * Br[ii] ** 3) @@ -588,20 +634,23 @@ def dl_se_ft(d, hte, hre, adft, f, omega): epsr = 22 sigma = 0.003 - Ldft_land = PropagationClearAir.dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f) + Ldft_land = PropagationClearAir.dl_se_ft_inner( + epsr, sigma, d, hte, hre, adft, f, + ) # First - term part of the spherical - Earth diffraction loss over sea epsr = 80 sigma = 5 - Ldft_sea = PropagationClearAir.dl_se_ft_inner(epsr, sigma, d, hte, hre, adft, f) + Ldft_sea = PropagationClearAir.dl_se_ft_inner( + epsr, sigma, d, hte, hre, adft, f, + ) # First - term spherical diffraction loss Ldft = omega * Ldft_sea + (1 - omega) * Ldft_land return Ldft - @staticmethod def dl_se(d, hte, hre, ap, f, omega): # Wavelength in meters @@ -611,28 +660,32 @@ def dl_se(d, hte, hre, ap, f, omega): dlos = np.sqrt(2 * ap) * (np.sqrt(0.001 * hte) + np.sqrt(0.001 * hre)) if d >= dlos: - #calculate diffraction loss Ldft using the method in Sec.4.2.2.1 for + # calculate diffraction loss Ldft using the method in Sec.4.2.2.1 for # adft = ap and set Ldsph to Ldft Ldsph = PropagationClearAir.dl_se_ft(d, hte, hre, ap, f, omega) else: - #calculate the smallest clearance between the curved - Earth path and - #the ray between the antennas, hse + # calculate the smallest clearance between the curved - Earth path and + # the ray between the antennas, hse c = (hte - hre) / (hte + hre) m = 250 * d * d / (ap * (hte + hre)) - b = 2 * np.sqrt((m + 1) / (3*m)) * np.cos(np.pi / 3 + 1 / 3 * np.arccos(3*c / 2 * np.sqrt(3*m/(m+1)** 3))) + b = 2 * np.sqrt((m + 1) / (3 * m)) * np.cos( + np.pi / 3 + + 1 / 3 * np.arccos(3 * c / 2 * np.sqrt(3 * m / (m + 1) ** 3)), + ) dse1 = d / 2 * (1 + b) dse2 = d - dse1 - hse = (hte - 500 * dse1 * dse1 / ap) * dse2 + (hre - 500 * dse2 * dse2 / ap) * dse1 + hse = (hte - 500 * dse1 * dse1 / ap) * dse2 + \ + (hre - 500 * dse2 * dse2 / ap) * dse1 hse = hse / d # Calculate the required clearance for zero diffraction loss hreq = 17.456 * np.sqrt(dse1 * dse2 * lamb / d) if hse > hreq: - Ldsph =np.array([0,0]) + Ldsph = np.array([0, 0]) else: # calculate the modified effective Earth radius aem, which gives # marginal LoS at distance d @@ -642,7 +695,7 @@ def dl_se(d, hte, hre, ap, f, omega): Ldft = PropagationClearAir.dl_se_ft(d, hte, hre, aem, f, omega) if (Ldft < 0).any(): - Ldsph =np.array([0,0]) + Ldsph = np.array([0, 0]) else: Ldsph = (1 - hse / hreq) * Ldft @@ -681,7 +734,7 @@ def dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega): return Ld @staticmethod - def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ): + def dl_p(d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN): # Use the method in 4.2.3 to calculate diffraction loss Ld for effective # Earth radius ap = ae as given by equation(6a). Set median diffractino # loss to Ldp50 @@ -690,7 +743,9 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ): ap = ae - Ld50 = PropagationClearAir.dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega) + Ld50 = PropagationClearAir.dl_delta_bull( + d, h, hts, hrs, hstd, hsrd, ap, f, omega, + ) if p == 50: Ldp = Ld50 @@ -700,7 +755,9 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ): # not exceeded for beta0 % time Ldb = Ld ap = ab - Ldb = PropagationClearAir.dl_delta_bull(d, h, hts, hrs, hstd, hsrd, ap, f, omega); + Ldb = PropagationClearAir.dl_delta_bull( + d, h, hts, hrs, hstd, hsrd, ap, f, omega, + ) # Compute the interpolation factor Fi if p > b0: @@ -713,47 +770,126 @@ def dl_p( d, h, hts, hrs, hstd, hsrd, f, omega, p, b0, DN ): return Ldp, Ld50 - - def get_loss(self, *args, **kwargs) -> np.array: - - d_km = np.asarray(kwargs["distance_3D"])*(1e-3) #Km - f = np.asarray(kwargs["frequency"])*(1e-3) #GHz - number_of_sectors = kwargs.pop("number_of_sectors",1) - indoor_stations = kwargs.pop("indoor_stations",1) - elevation = kwargs["elevation"] - - f = np.unique(f) - if len(f) > 1: - error_message = "different frequencies not supported in P619" + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the get_loss method to fit the Propagation ABC class interface + Calculates the loss between station_a and station_b + + Parameters + ---------- + params : Parameters + Simulation parameters needed for the propagation class + frequency: float + Center frequency + station_a : StationManager + StationManager container representing the system station + station_b : StationManager + StationManager container representing the IMT station + station_a_gains: np.ndarray defaults to None + System antenna gains + station_b_gains: np.ndarray defaults to None + IMT antenna gains + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + distance = station_a.get_3d_distance_to( + station_b, + ) * (1e-3) # P.452 expects Kms + frequency_array = frequency * \ + np.ones(distance.shape) * (1e-3) # P.452 expects GHz + indoor_stations = np.tile( + station_b.indoor, (station_a.num_stations, 1), + ) + elevation = station_b.get_elevation(station_a) + if params.imt.interfered_with: + tx_gain = station_a_gains + rx_gain = station_b_gains + else: + tx_gain = station_b_gains + rx_gain = station_a_gains + + return self.get_loss( + distance, + frequency_array, + indoor_stations, + elevation, + tx_gain, + rx_gain, + ) + + # pylint: disable=arguments-differ + @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray) + def get_loss( + self, distance: np.ndarray, frequency: np.ndarray, + indoor_stations: np.ndarray, elevation: np.ndarray, + tx_gain: np.ndarray, rx_gain: np.ndarray, + ) -> np.array: + """Calculates the loss according to P.452 + + Parameters + ---------- + distance : np.ndarray + Distance array between stations in KMs + frequency : np.ndarray + Frequency array for the links + indoor_stations : np.ndarray + Whether the rx stations are indoor + elevation : np.ndarray + elevation angle between stations + tx_gain: np.ndarray + transmitter antenna gains + rx_gain: np.ndarray + receiver antenna gains + + Returns + ------- + np.array + array of losses + """ + frequency = np.unique(frequency) + if len(frequency) > 1: + error_message = "different frequencies not supported in P.452" raise ValueError(error_message) - es_params =kwargs["es_params"] - Ph = np.asarray(es_params.atmospheric_pressure) - T = np.asarray(es_params.air_temperature) - Dct = np.asarray(es_params.Dct) - Dcr = np.asarray(es_params.Dcr) - Hte = np.asarray(es_params.Hte) - Hre = np.asarray(es_params.Hre) - N0 = np.asarray(es_params.N0) - deltaN = np.asarray(es_params.delta_N) - if es_params.percentage_p == 'RANDOM': - p = 50*self.random_number_gen.rand(d_km.size) + # TODO: Remove unecessary assignments + Ph = np.asarray(self.model_params.atmospheric_pressure) + T = np.asarray(self.model_params.air_temperature) + Dct = np.asarray(self.model_params.Dct) + Dcr = np.asarray(self.model_params.Dcr) + Hte = np.asarray(self.model_params.Hte) + Hre = np.asarray(self.model_params.Hre) + N0 = np.asarray(self.model_params.N0) + deltaN = np.asarray(self.model_params.delta_N) + if self.model_params.percentage_p == 'RANDOM': + p = 50 * self.random_number_gen.rand(distance.size) else: - p = float(es_params.percentage_p)*np.ones(d_km.size) + p = float(self.model_params.percentage_p) * np.ones(distance.size) - tx_lat = es_params.tx_lat - rx_lat = es_params.rx_lat + tx_lat = self.model_params.tx_lat + rx_lat = self.model_params.rx_lat - Gt = np.ravel(np.asarray(kwargs["tx_gain"])) - Gr = np.ravel(np.asarray(kwargs["rx_gain"])) + tx_gain = np.ravel(tx_gain) + rx_gain = np.ravel(rx_gain) # Modify the path according to Section 4.5.4, Step 1 and compute clutter losses # consider no obstacles profile profile_length = 100 - num_dists = d_km.size + num_dists = distance.size d = np.empty([num_dists, profile_length]) for ii in range(num_dists): - d[ii, :] = np.linspace(0,d_km[0][ii],profile_length) + d[ii, :] = np.linspace(0, distance[0][ii], profile_length) h = np.zeros(d.shape) @@ -776,12 +912,12 @@ def get_loss(self, *args, **kwargs) -> np.array: for index in range(num_dists): zone_r = 12 - dtm[index] = self.longest_cont_dist(d[index,:], zone, zone_r) + dtm[index] = self.longest_cont_dist(d[index, :], zone, zone_r) zone_r = 2 - dlm[index] = self.longest_cont_dist(d[index,:], zone, zone_r) + dlm[index] = self.longest_cont_dist(d[index, :], zone, zone_r) - #compute beta0 + # compute beta0 b0 = self.beta0(phi_path, dtm, dlm) [ae, ab] = self.earth_rad_eff(deltaN) @@ -790,15 +926,14 @@ def get_loss(self, *args, **kwargs) -> np.array: # Modify the path according to Section 4.5.4, Step 1 and compute clutter losses # only if not isempty ha_t and ha_r - #[dc, hc, zonec, htgc, hrgc, Aht, Ahr] = self.closs_corr(f, d, h, zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r) + # [dc, hc, zonec, htgc, hrgc, Aht, Ahr] = self.closs_corr(f, d, h, zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r) - Lb = np.empty([1,num_dists]) + Lb = np.empty([1, num_dists]) # Effective Earth curvature Ce(km ^ -1) Ce = 1 / ae # Wavelength in meters - lamb = 0.3 / f # Calculate an interpolation factor Fj to take account of the path angular # distance(58) @@ -806,14 +941,18 @@ def get_loss(self, *args, **kwargs) -> np.array: KSI = 0.8 for ii in range(num_dists): - [dc, hc, zonec, htg, hrg, Aht, Ahr] = self.closs_corr(f, d[ii,:], h[ii,:], zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r) - d[ii,:] = dc - h[ii,:] = hc + [dc, hc, zonec, htg, hrg, Aht, Ahr] = self.closs_corr( + frequency, d[ii, :], h[ii, :], zone, Hte, Hre, ha_t, ha_r, dk_t, dk_r, + ) + d[ii, :] = dc + h[ii, :] = hc - [hst, hsr, hstd, hsrd, hte,hre, hm, dlt, - dlr, theta_t, theta_r, theta, pathtype] = self.smooth_earth_heights(d[ii,:], h[ii,:], htg, hrg, ae, f) + [ + hst, hsr, hstd, hsrd, hte, hre, hm, dlt, + dlr, theta_t, theta_r, theta, pathtype, + ] = self.smooth_earth_heights(d[ii, :], h[ii, :], htg, hrg, ae, frequency) - dtot = d[ii,-1] - d[ii,0] + dtot = d[ii, -1] - d[ii, 0] # Tx and Rx antenna heights above mean sea level amsl(m) hts = hc[0] + htg @@ -826,8 +965,8 @@ def get_loss(self, *args, **kwargs) -> np.array: error_message = "tl_p452: path profile requires at least 4 points." raise ValueError(error_message) - di = d[ii,1: -1] - hi = h[ii,1: -1] + di = d[ii, 1: -1] + hi = h[ii, 1: -1] Stim = max((hi + 500 * Ce * di * (dtot - di) - hts) / di) @@ -846,9 +985,13 @@ def get_loss(self, *args, **kwargs) -> np.array: Fk = 1.0 - 0.5 * (1.0 + np.tanh(3.0 * kappa * (dtot - dsw) / dsw)) - [Lbfsg, Lb0p, Lb0b] = self.pl_los(dtot, f, p[ii], b0[ii], omega[ii], T, Ph, dlt, dlr) + [Lbfsg, Lb0p, Lb0b] = self.pl_los( + dtot, frequency, p[ii], b0[ii], omega[ii], T, Ph, dlt, dlr, + ) - [Ldp, Ld50] = self.dl_p(d[ii], h[ii], hts, hrs, hstd, hsrd, f, omega[ii], p[ii], b0[ii], deltaN) + [Ldp, Ld50] = self.dl_p( + d[ii], h[ii], hts, hrs, hstd, hsrd, frequency, omega[ii], p[ii], b0[ii], deltaN, + ) # The median basic transmission loss associated with diffraction Eq (43) Lbd50 = Lbfsg + Ld50 @@ -868,8 +1011,11 @@ def get_loss(self, *args, **kwargs) -> np.array: # and transhorizon signal enhancements eta = 2.5 - Lba = self.tl_anomalous(dtot, dlt, dlr, Dct, Dcr, dlm[ii], hts, hrs, hte, hre, hm, theta_t, theta_r, f, p[ii], T, Ph, - omega[ii], ae, b0[ii]) + Lba = self.tl_anomalous( + dtot, dlt, dlr, Dct, Dcr, dlm[ii], hts, hrs, hte, hre, hm, theta_t, theta_r, + frequency, p[ii], T, Ph, + omega[ii], ae, b0[ii], + ) Lminbap = eta * np.log(np.exp(Lba / eta) + np.exp(Lb0p / eta)) @@ -885,36 +1031,39 @@ def get_loss(self, *args, **kwargs) -> np.array: # Calculate the basic transmission loss due to troposcatter not exceeded # for any time percantage p - Lbs = self.tl_tropo(dtot, theta, f, p[ii], T, Ph, N0, Gt[ii], Gr[ii]) + Lbs = self.tl_tropo( + dtot, theta, frequency, + p[ii], T, Ph, N0, tx_gain[ii], rx_gain[ii], + ) # Calculate the final transmission loss not exceeded for p % time - Lb_pol = -5 * np.log10(10 ** (-0.2 * Lbs) + 10** (-0.2 * Lbam)) + Aht + Ahr - - if (es_params.polarization).lower() == "horizontal": - Lb[0,ii] = Lb_pol[0] - elif (es_params.polarization).lower() == "vertical": - Lb[0,ii] = Lb_pol[1] + Lb_pol = -5 * np.log10( + 10 ** (-0.2 * Lbs) + + 10 ** (-0.2 * Lbam), + ) + Aht + Ahr + + if (self.model_params.polarization).lower() == "horizontal": + Lb[0, ii] = Lb_pol[0] + elif (self.model_params.polarization).lower() == "vertical": + Lb[0, ii] = Lb_pol[1] else: error_message = "invalid polarization" raise ValueError(error_message) - if es_params.clutter_loss: - clutter_loss = self.clutter.get_loss(frequency=f * 1000, - distance=d_km * 1000, - station_type=StationType.FSS_ES) + if self.model_params.clutter_loss: + clutter_loss = self.clutter.get_loss( + frequency=frequency * 1000, + distance=distance * 1000, + station_type=StationType.FSS_ES, + ) else: - clutter_loss = np.zeros(d_km.shape) + clutter_loss = np.zeros(distance.shape) # building_loss = self.building_loss * indoor_stations - b_loss = np.transpose(self.building_entry.get_loss(f, elevation)) + b_loss = np.transpose( + self.building_entry.get_loss(frequency, elevation), + ) building_loss = b_loss * indoor_stations + lb_new = Lb + clutter_loss + building_loss - if number_of_sectors > 1: - Lb = np.repeat(Lb, number_of_sectors, 1) - clutter_loss = np.repeat(clutter_loss, number_of_sectors, 1) - building_loss = np.repeat(building_loss, number_of_sectors, 1) - - Lb_new = Lb + clutter_loss + building_loss - - return Lb_new - + return lb_new diff --git a/sharc/propagation/propagation_clutter_loss.py b/sharc/propagation/propagation_clutter_loss.py index e35029fce..1149affff 100644 --- a/sharc/propagation/propagation_clutter_loss.py +++ b/sharc/propagation/propagation_clutter_loss.py @@ -1,9 +1,4 @@ # -*- coding: utf-8 -*- -""" -Created on Mon May 15 12:51:48 2017 - -@author: LeticiaValle_Mac -""" from sharc.propagation.propagation import Propagation from sharc.support.enumerations import StationType @@ -11,15 +6,54 @@ import numpy as np import scipy import math -from scipy import special +import matplotlib.pyplot as plt class PropagationClutterLoss(Propagation): + """ + This Recommendation (ITU_R_P_2108-1/2021) provides methods for + estimating loss through clutter at frequencies between 30 MHz and 100 GHz. + + The ITU Radiocommunication Assembly, + considering + a) that, for system planning and interference assessment it may be + necessary to account for the attenuation suffered by radio waves in + passing over or between buildings; + b) that, where a terrestrial station may be shielded by buildings a + detailed calculation for a general case can be difficult to formulate + and losses due to clutter must be considered dependant on the + deployment scenario; + c) that, where terrestrial stations are in motion the clutter envir- + onment of the radio path will be variable, + recognizing + a) that Recommendation ITU-R P.1411 contains data and models for + short-range radio system, mainly within an urban environment from + 300 MHz to 100 GHz; + b) that Recommendation ITU-R P.2040 contains basic expressions for + reflection from and penetration through building materials, and a + harmonised representation of building material electrical properties + above about 100 MHz; + c) that Recommendation ITU-R P.452 contains a prediction method for + the evaluation of interference between stations on the surface of + the Earth at frequencies from about 0.1 GHz to 50 GHz, accounting + for both clear-air and hydrometeor scattering interference mechanisms; + d) that Recommendation ITU-R P.1812 describes a propagation predict- + ion method suitable for terrestrial point-to-area services in the + frequency range 30 MHz to 6 000 MHz; + e) that Recommendation ITU-R P.833 presents several models to enable + the user to evaluate the effect of vegetation on radiowave signals + between 30 MHz and 60 GHz; + f) that Recommendation ITU-R P.2109 provides a statistical model + for building entry loss for frequencies between about 80 MHz and 100 GHz, + recommends + that the material in Recomendation ITU-R P.2108-1/2021 be used to + estimate clutter loss. + """ def get_loss(self, *args, **kwargs) -> np.array: """ - Calculates clutter loss. + Calculates clutter loss according to Recommendation P.2108-0 Parameters ---------- @@ -39,7 +73,7 @@ def get_loss(self, *args, **kwargs) -> np.array: """ f = kwargs["frequency"] - loc_per = kwargs.pop("loc_percentage","RANDOM") + loc_per = kwargs.pop("loc_percentage", "RANDOM") type = kwargs["station_type"] d = kwargs["distance"] @@ -50,7 +84,7 @@ def get_loss(self, *args, **kwargs) -> np.array: if isinstance(loc_per, str) and loc_per.upper() == "RANDOM": p = self.random_number_gen.random_sample(d.shape) else: - p = loc_per*np.ones(d.shape) + p = loc_per * np.ones(d.shape) if type is StationType.IMT_BS or type is StationType.IMT_UE or type is StationType.FSS_ES: loss = self.get_terrestrial_clutter_loss(f, d, p) @@ -59,114 +93,161 @@ def get_loss(self, *args, **kwargs) -> np.array: loss = self.get_spacial_clutter_loss(f, theta, p) return loss - def get_spacial_clutter_loss(self, frequency : float, - elevation_angle : float, - loc_percentage): + def get_spacial_clutter_loss( + self, frequency: float, + elevation_angle: float, + loc_percentage, + ): """ This method models the calculation of the statistical distribution of clutter loss where one end of the interference path is within man-made clutter, and the other is a satellite, aeroplane, or other platform - above the surface of the Earth. This model is applicable to urban and - suburban environments. + above the surface of the Earth. An additional "loss" is calculated + which can be added to the basic transmission loss of a path calculated. + This model is applicable to urban and suburban environments. The method + used to develop this model is described in Report ITU-R P.2402-0. The + clutter loss not exceeded for loc_percentage% of locations "loss" for + the terrestrial to airborne or satellite path is given by this method. Parameters ---- - frequency : center frequency [MHz] - elevation_angle : elevation angle [degrees] - loc_percentage : percentage of locations [0,1[ + frequency : center frequency [MHz] - Frequency range: 10 to 100 GHz + elevation_angle : elevation angle [degrees] - Elevation angle + range: 0 to 90 degrees + loc_percentage : percentage of locations - Percentage locations + range: 0 < p < 100 Returns ------- - loss : The clutter loss not exceeded for p% of locations for the - terrestrial to terrestrial path + loss : The clutter loss not exceeded for loc_percentage% of + locations for the terrestrial to terrestrial path """ - k1 = 93*(frequency*1e-3)**0.175 + k1 = 93 * (frequency * 1e-3)**0.175 A1 = 0.05 - y = np.sin(A1*(1 - (elevation_angle/90)) + math.pi*(elevation_angle/180)) - y1 = np.cos(A1*(1 - (elevation_angle/90)) + math.pi*(elevation_angle/180)) + y = np.tan(A1 * (1 - (elevation_angle / 90)) + + math.pi * (elevation_angle / 180)) + cot = (1 / y) - cot = (y1/y) - Q = np.sqrt(2)*scipy.special.erfcinv(2*loc_percentage) - loss = (-k1*(np.log(1 - loc_percentage))*cot)**(0.5*(90 - elevation_angle)/90) - 1 - 0.6*Q + invQ = np.sqrt(2) * scipy.special.erfcinv(2 * loc_percentage) - return loss + loss = (-k1 * (np.log(1 - loc_percentage)) * cot)**(0.5 * (90 - elevation_angle) / 90) - 1 - 0.6 * invQ + return loss - def get_terrestrial_clutter_loss(self, - frequency: float, - distance: float, - loc_percentage: float, - apply_both_ends = True): + def get_terrestrial_clutter_loss( + self, + frequency: float, + distance: float, + loc_percentage: float, + apply_both_ends=True, + ): """ - This method gives models the statistical distribution of clutter loss. - The model can be applied for urban and suburban clutter loss modelling. + This method gives a statistical distribution of clutter loss. The model + can be applied for urban and suburban clutter loss modelling. An + additional "loss" is calculated which can be added to the transmission + loss or basic transmission loss. Clutter loss will vary depending on + clutter type, location within the clutter and movement in the clutter. + If the transmission loss or basic transmission loss has been calculated + using a model (e.g. Recommendation ITU-R P.1411) that inherently + accounts for clutter over the entire path then the method below should + not be applied. The clutter loss not exceeded for loc_percentage% of + locations for the terrestrial to terrestrial path, "loss"", is given + by this method. The clutter loss must not exceed a maximum value + calculated for ๐‘‘ = 2 ๐‘˜m (loss_2km) +. Parameters ---- - frequency : center frequency [MHz] - distance : distance [m] - loc_percentage : percentage of locations [0,1] - apply_both_ends : if correction will be applied at both ends of the path + frequency : center frequency [MHz] - Frequency range: 0.5 to 67 GHz + distance : distance [m] - Minimum path length: 0.25 km (for the + correction to be applied at only one end of the path) + 1.0 km (for the + correction to be applied at both ends of the path) + loc_percentage : percentage of locations [0,1] - Percentage + locations range: 0 < p < 100 + + apply_both_ends : if correction will be applied at both ends of the + path Returns ------- - loss : The clutter loss not exceeded for p% of locations for the - terrestrial to terrestrial path + loss : The clutter loss not exceeded for loc_percentage% of + locations for the terrestrial to terrestrial path """ - d = distance.reshape((-1, 1)) + d = distance.copy() + d = d.reshape((-1, 1)) f = frequency.reshape((-1, 1)) p = loc_percentage.reshape((-1, 1)) - loss = np.zeros(d.shape) - - # minimum path length for the correction to be applied at only one end of the path - id_1 = np.where(d >= 250)[0] - - if len(id_1): - Lt = 23.5 + 9.6 * np.log10(f[id_1] * 1e-3) - Ls = 32.98 + 23.9 * np.log10(d[id_1] * 1e-3) + 3 * np.log10(f[id_1] * 1e-3) - Q = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_1])) - loss[id_1] = -5 * np.log10(10 ** (-0.2 * Lt) + 10 ** (-0.2 * Ls)) - 6 * Q + sigma_l = 4.0 + sigma_s = 6.0 - # minimum path length for the correction to be applied at only one end of the path - id_2 = np.where(d >= 1000)[0] - - if apply_both_ends and len(id_2): - Lt = 23.5 + 9.6 * np.log10(f[id_2] * 1e-3) - Ls = 32.98 + 23.9 * np.log10(d[id_2] * 1e-3) + 3 * np.log10(f[id_2] * 1e-3) - Q = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_2])) - loss[id_2] = loss[id_2] + (-5 * np.log10(10 ** (-0.2 * Lt) + 10 ** (-0.2 * Ls)) - 6 * Q) + loss = np.zeros(d.shape) + loss_2km = np.zeros(d.shape) + if apply_both_ends: + # minimum path length for the correction to be applied at only one end of the path + id_d = np.where(d >= 1000)[0] + else: + # minimum path length for the correction to be applied at both ends of the path + id_d = np.where(d >= 250)[0] + + if len(id_d): + Ll = -2.0 * \ + np.log10( + 10 ** (-5.0 * np.log10(f[id_d] * 1e-3) - 12.5) + 10 ** (-16.5)) + Ls_temp = 32.98 + 3.0 * np.log10(f[id_d] * 1e-3) + Ls = 23.9 * np.log10(d[id_d] * 1e-3) + Ls_temp + invQ = np.sqrt(2) * scipy.special.erfcinv(2 * (p[id_d])) + sigma_cb = np.sqrt(((sigma_l**(2.0)) * (10.0**(-0.2 * Ll)) + (sigma_s**(2.0)) * + (10.0**(-0.2 * Ls))) / (10.0**(-0.2 * Ll) + 10.0**(-0.2 * Ls))) + loss[id_d] = -5.0 * \ + np.log10(10 ** (-0.2 * Ll) + 10 ** + (-0.2 * Ls)) - sigma_cb * invQ + + # The clutter loss must not exceed a maximum value calculated for ๐‘‘ = 2 ๐‘˜m (loss_2km) + Ls_2km = 23.9 * np.log10(2) + Ls_temp + sigma_cb_2km = np.sqrt(((sigma_l**(2.0)) * (10.0**(-0.2 * Ll)) + (sigma_s**(2.0)) * + (10.0**(-0.2 * Ls_2km))) / (10.0**(-0.2 * Ll) + 10.0**(-0.2 * Ls_2km))) + loss_2km[id_d] = -5.0 * \ + np.log10(10 ** (-0.2 * Ll) + 10 ** (-0.2 * Ls_2km)) - \ + sigma_cb_2km * invQ + id_max = np.where(loss >= loss_2km)[0] + loss[id_max] = loss_2km[id_max] + + loss *= 2 loss = loss.reshape(distance.shape) return loss if __name__ == '__main__': - import matplotlib.pyplot as plt elevation_angle = np.array([90, 80, 70, 60, 50, 40, 30, 20, 15, 10, 5, 0]) - loc_percentage = np.linspace(0.01, 0.99, 1000) - frequency = 27250 * np.ones(elevation_angle.shape) + loc_percentage = np.linspace(0.01, 0.995, 1000) + frequency = 30000 * np.ones(elevation_angle.shape) random_number_gen = np.random.RandomState(101) cl = PropagationClutterLoss(random_number_gen) clutter_loss = np.empty([len(elevation_angle), len(loc_percentage)]) for i in range(len(loc_percentage)): - clutter_loss[:, i] = cl.get_spacial_clutter_loss(frequency, - elevation_angle, - loc_percentage[i]) + clutter_loss[:, i] = cl.get_spacial_clutter_loss( + frequency, + elevation_angle, + loc_percentage[i], + ) fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() for j in range(len(elevation_angle)): - ax.plot(clutter_loss[j, :], 100 * loc_percentage, label="%i deg" % elevation_angle[j], linewidth=1) + ax.plot(clutter_loss[j, :], 100 * loc_percentage, + label="%i deg" % elevation_angle[j], linewidth=1) - plt.title("Cumulative distribution of clutter loss not exceeded for 27 GHz") + plt.title("Cumulative distribution of clutter loss not exceeded for 30 GHz") plt.xlabel("clutter loss [dB]") plt.ylabel("percent of locations [%]") @@ -178,7 +259,7 @@ def get_terrestrial_clutter_loss(self, plt.show() distance = np.linspace(250, 100000, 100000) - frequency = np.array([2, 3, 6, 16, 40, 67]) * 1e3 + frequency = np.array([1, 2, 4, 8, 16, 32, 67]) * 1e3 loc_percentage = 0.5 * np.ones(distance.shape) apply_both_ends = False @@ -186,10 +267,12 @@ def get_terrestrial_clutter_loss(self, clutter_loss_ter = np.empty([len(frequency), len(distance)]) for i in range(len(frequency)): - clutter_loss_ter[i, :] = cl.get_terrestrial_clutter_loss(frequency[i] * np.ones(distance.shape), - distance, - loc_percentage, - apply_both_ends) + clutter_loss_ter[i, :] = cl.get_terrestrial_clutter_loss( + frequency[i] * np.ones(distance.shape), + distance, + loc_percentage, + apply_both_ends, + ) fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() @@ -197,8 +280,14 @@ def get_terrestrial_clutter_loss(self, for j in range(len(frequency)): freq = frequency[j] * 1e-3 - ax.semilogx(distance * 1e-3, clutter_loss_ter[j, :], label="%i GHz" % freq, linewidth=1) + ax.semilogx(distance * 1e-3, + clutter_loss_ter[j, :], label="%i GHz" % freq, linewidth=1) plt.title("Median clutter loss for terrestrial paths") plt.xlabel("Distance [km]") + plt.xlim((0.1, 100.0)) + plt.ylim((15.0, 70.0)) + plt.legend(loc="lower right") + plt.tight_layout() + plt.grid() plt.show() diff --git a/sharc/propagation/propagation_ehf.py b/sharc/propagation/propagation_ehf.py new file mode 100644 index 000000000..dc853dc80 --- /dev/null +++ b/sharc/propagation/propagation_ehf.py @@ -0,0 +1,355 @@ +""" +Created on Mon Nov 18 17:28:47 2024 + +@author: joaocabeca2 +""" +import numpy as np +from sharc.parameters.parameters import Parameters +from sharc.parameters.parameters_p1411 import ParametersP1411 +from sharc.parameters.parameters_p452 import ParametersP452 +from sharc.propagation.propagation import Propagation +from sharc.propagation.propagation_p1411_12 import PropagationP1411 +from sharc.propagation.propagation_free_space import PropagationFreeSpace +from sharc.propagation.propagation_clear_air_452 import PropagationClearAir +from sharc.station_manager import StationManager + + +class PropagationEHF(Propagation): + """ + A class for calculating millimetre-wave propagation losses at frequencies above 10 GHz, + especially in the Extremely High Frequency (EHF) band described in ITU-R P1411-2 + """ + def __init__( + self, + random_number_gen: np.random.RandomState, + los_adjustment_factor: float, + model_params: ParametersP1411 + ): + super().__init__(random_number_gen) + self.los_adjustment_factor = los_adjustment_factor + self.model_params = model_params + + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMa get_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()): + distance_2D, distance_3D, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distance_2D = station_a.get_distance_to(station_b) + distance_3D = station_a.get_3d_distance_to(station_b) + + distance = station_a.get_3d_distance_to( + station_b, + ) * (1e-3) # P.452 expects Kms + frequency_array = frequency * \ + np.ones(distance.shape) * (1e-3) # P.452 expects GHz + indoor_stations = np.tile( + station_b.indoor, (station_a.num_stations, 1), + ) + elevation = station_b.get_elevation(station_a) + if params.imt.interfered_with: + tx_gain = station_a_gains + rx_gain = station_b_gains + else: + tx_gain = station_b_gains + rx_gain = station_a_gains + + los_probability = self.get_los_probability( + distance_2D, self.los_adjustment_factor, + ) + los_condition = self.get_los_condition(los_probability) + + i_los = np.where(los_condition == True)[:2] + i_nlos = np.where(los_condition == False)[:2] + + frequency *= np.ones(distance_2D.shape) + + gas_att = self.get_gaseous_attenuation(distance, frequency_array, indoor_stations, elevation, tx_gain, rx_gain) + rain_att = self.get_rain_attenuation() * np.ones(distance_2D.shape) + + loss = np.empty(distance_2D.shape) + + n = self.model_params.n + environment = self.model_params.environment + d_corner = self.model_params.d_corner + street_width1 = self.model_params.street_width1 + distance1 = self.model_params.distance1 + distance2 = self.model_params.distance2 + ref_dist = self.model_params.ref_dist + + ref_dist = np.asarray(ref_dist * np.ones(distance_2D.shape)) + n = np.asarray(n * np.ones(distance_2D.shape)) + + if len(i_los[0]): + loss_los = self.get_loss_los( + frequency, distance_3D, ref_dist, gas_att, rain_att, n + ) + loss[i_los] = loss_los[i_los] + + if len(i_nlos[0]): + loss_nlos = self.get_loss_nlos( + loss_los, environment, d_corner, street_width1, distance1, distance2 + ) + loss[i_nlos] = loss_nlos[i_nlos] + + return loss + + def get_loss_los(self, + frequency:np.array, + distance_3d:np.array, + ref_dist:np.array, + gas_attenuation:np.array, + rain_attenuation:np.array, + n:np.array): + """ + Calculate the line-of-sight (LoS) transmission loss in decibels (dB) + for millimeter-wave propagation with directional antennas. + + Parameters: + ----------- + frequency : np.array + The frequency of the transmitted signal in MHz. + ref_dist: np.array + reference distance of 1 meter + distance_3d : np,array + The distance between Station 1 and Station 2 in meters. + gas_attenuation : np.array + The attenuation caused by atmospheric gases in dB. + rain_attenuation : np.array + The attenuation caused by rain in dB. + n : np.array + The basic transmission loss exponent (default is 2 for free-space propagation). + """ + + #calculate a free space loss at a fixed distance of 1m + free_space_loss = self.get_free_space_loss(frequency, ref_dist) + return ( + free_space_loss + (10 * n * np.log10((distance_3d / ref_dist) + + gas_attenuation + rain_attenuation)) + ) + + def get_loss_nlos(self, + loss_los: np.array, + environment: str, + d_corner: float, + street_width1: float, + distance1: float, + distance2: float): + """ + Calculates path loss for the NLOS (non line-of-sight) case based + on measurements at a frequency range from 2 to 38 GHz, Station 1 antenna height and Station 2 antenna height + less than average height of buildings and the street width + at the position of the Station 2 is up to 10m (or sidewalk) + """ + + l_corner = self.calculate_Lcorner(environment, d_corner, street_width1, distance2) + l_att = self.calculate_Latt(d_corner, street_width1, distance2, distance1) + + return loss_los + l_corner + l_att + + + def calculate_Lcorner(self, + environment: str, + d_corner: float, + street_width1: float, + distance2: float): + """ + Calculates the corner loss based on the environment. + + Returns: + - float: Corner loss in dB. + """ + + L_c = 20 if environment.lower() == "urban" else 30 # Base corner loss depending on the environment + L_c + + if distance2 > (street_width1 / (2 + 1 + d_corner)): # Check if any element satisfies the condition + # Beyond the corner region, use the fixed L_c value + return L_c + + elif (distance2 >= (street_width1 / 3)) & (distance2 <= (street_width1 / (2 + 1 + d_corner))): + # Within the corner region, calculate dynamically + return (L_c / np.log10(1 + d_corner)) * np.log10(distance2 - street_width1 / 2) + + else: + # If x2 is out of the expected range + raise ValueError("x2 must be greater than or equal to w1/2 + 1.") + + def calculate_Latt(self, d_corner, street_width1, distance2, distance1): + """ + Calculates the attenuation in the NLoS region beyond the corner. + + Returns: + - float: NLoS attenuation (in dB). + """ + + if distance2 <= (street_width1 / 2 + 1 + d_corner): + return 0 + else: + beta = 6 # Coefficient for urban and residential environments + return 10 * beta * np.log10((distance1 + distance2) / ((distance1 + street_width1) /(2 + d_corner))) + + def get_los_condition(self, p_los: np.array) -> np.array: + """ + Evaluates if user equipments are LOS (True) of NLOS (False). + + Parameters + ---------- + p_los : array with LOS probabilities for each user equipment. + + Returns + ------- + An array with True or False if user equipments are in LOS of NLOS + condition, respectively. + """ + los_condition = self.random_number_gen.random_sample( + p_los.shape, + ) < p_los + return los_condition + + def get_los_probability( + self, + distance_2D: np.array, + los_adjustment_factor: float, + ) -> np.array: + """ + Returns the line-of-sight (LOS) probability + + Parameters + ---------- + distance_2D : Two-dimensional array with 2D distance values from + base station to user terminal [m] + los_adjustment_factor : adjustment factor to increase/decrease the + LOS probability. Original value is 18 as per 3GPP + + Returns + ------- + LOS probability as a numpy array with same length as distance + """ + + p_los = np.ones(distance_2D.shape) + idl = np.where(distance_2D > los_adjustment_factor) + p_los[idl] = ( + los_adjustment_factor / distance_2D[idl] + + np.exp(-distance_2D[idl] / 36) * (1 - los_adjustment_factor / distance_2D[idl]) + ) + + return p_los + + def get_rain_attenuation(self) -> np.array: + return 0.0 + + def get_gaseous_attenuation(self, distance: np.array, + frequency: np.array, + indoor_stations: np.array, + elevation: np.array, + tx_gain: np.array, + rx_gain: np.array) -> np.array: + gaseous_att = PropagationClearAir(self.random_number_gen, ParametersP452) + return gaseous_att.get_loss(distance, frequency, indoor_stations, elevation, tx_gain, rx_gain) + + def get_free_space_loss(self, frequency: np.array, distance_3d: np.array) -> np.array: + free_space_prop = PropagationFreeSpace(self.random_number_gen) + return free_space_prop.get_free_space_loss(frequency * 1000, distance_3d) + +if __name__ == '__main__': + + import matplotlib.pyplot as plt + from cycler import cycler + + # Configuraรงรฃo de parรขmetros + num_ue = 1 + num_bs = 1000 + h_bs = 6 * np.ones(num_bs) + h_ue = 1.5 * np.ones(num_ue) + + model_params = ParametersP1411() + environment = model_params.environment + d_corner = model_params.d_corner + street_width1 = model_params.street_width1 + distance1 = model_params.distance1 + distance2 = model_params.distance2 + + + # Configuraรงรฃo da distรขncia para o cenรกrio + distance_2D = np.repeat(np.linspace(1, 1000, num=num_bs)[np.newaxis, :], num_ue, axis=0) + frequency = 7 * np.ones(num_bs) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2) + + # Gerador de nรบmeros aleatรณrios + random_number_gen = np.random.RandomState(101) + ref_dist = np.asarray(1.0 * np.ones(distance_2D.shape)) + n = np.asarray(2 * np.ones(distance_2D.shape)) + + indoor_stations = np.asarray(np.zeros(distance_3D.shape, dtype=bool)) + tx_gain = 0 * np.ones(distance_2D.shape) + rx_gain = 0 * np.ones(distance_2D.shape) + + elevation = np.empty(num_bs) + los_adjustment_factor = 18.0 + + ehf = PropagationEHF(random_number_gen, los_adjustment_factor, ParametersP1411) + free_space_prop = PropagationFreeSpace(random_number_gen) + free_space_loss = free_space_prop.get_loss(distance_3D, frequency * 1000) + + gas_att = ehf.get_gaseous_attenuation(np.asarray(distance_3D), np.asarray(frequency), indoor_stations, elevation, tx_gain, rx_gain) + rain_att = 0 * np.ones(distance_2D.shape) + + p1411 = PropagationP1411(random_number_gen , environment) + p1411_loss = p1411.calculate_median_basic_loss(distance_3D, frequency, random_number_gen) + + loss_los = ehf.get_loss_los(frequency, distance_3D, ref_dist, gas_att, rain_att, n) + loss_nlos = ehf.get_loss_nlos(loss_los, environment, d_corner, street_width1, distance1, distance2) + + # Plotando os grรกficos + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') + ax = fig.gca() + ax.set_prop_cycle(cycler('color', plt.cm.tab10.colors)) + + ax.semilogx(distance_2D[0, :], loss_los[0, :], label="Milimetric wave loss (LOS)") + ax.semilogx(distance_2D[0, :], p1411_loss[0, :], label="Median basic loss") + ax.semilogx(distance_2D[0, :], free_space_loss[0, :], label="Free space loss") + ax.semilogx(distance_2D[0, :], loss_nlos[0, :], label="Milimetric wave loss (NLOS)") + + plt.title('Milimetric wave loss') + plt.xlabel("Distance [m]") + plt.ylabel("Path Loss [dB]") + plt.xlim((0, distance_2D[0, -1])) + plt.legend(loc="upper left") + plt.tight_layout() + plt.grid() + + plt.show() \ No newline at end of file diff --git a/sharc/propagation/propagation_factory.py b/sharc/propagation/propagation_factory.py index 1a37bf575..a3e61b244 100644 --- a/sharc/propagation/propagation_factory.py +++ b/sharc/propagation/propagation_factory.py @@ -6,14 +6,23 @@ """ import sys + import numpy.random as rnd +from sharc.parameters.imt.parameters_imt import ParametersImt from sharc.parameters.parameters import Parameters +from sharc.parameters.parameters_base import ParametersBase from sharc.propagation.propagation import Propagation +from sharc.propagation.propagation_abg import PropagationABG +from sharc.propagation.propagation_clear_air_452 import PropagationClearAir from sharc.propagation.propagation_free_space import PropagationFreeSpace +from sharc.propagation.propagation_hdfss import PropagationHDFSS +from sharc.propagation.propagation_indoor import PropagationIndoor from sharc.propagation.propagation_p619 import PropagationP619 +from sharc.propagation.propagation_p1411 import PropagationP1411 from sharc.propagation.propagation_sat_simple import PropagationSatSimple from sharc.propagation.propagation_ter_simple import PropagationTerSimple +from sharc.propagation.propagation_tvro import PropagationTvro from sharc.propagation.propagation_uma import PropagationUMa from sharc.propagation.propagation_umi import PropagationUMi from sharc.propagation.propagation_abg import PropagationABG @@ -21,11 +30,42 @@ from sharc.propagation.propagation_tvro import PropagationTvro from sharc.propagation.propagation_indoor import PropagationIndoor from sharc.propagation.propagation_hdfss import PropagationHDFSS +from sharc.propagation.propagation_p1411 import PropagationP1411 +from sharc.propagation.propagation_ehf import PropagationEHF + class PropagationFactory(object): @staticmethod - def create_propagation(channel_model: str, param: Parameters, random_number_gen: rnd.RandomState) -> Propagation: + def create_propagation( + channel_model: str, + param: Parameters, + param_system: ParametersBase, + random_number_gen: rnd.RandomState, + ) -> Propagation: + """Creates a propagation model object + + Parameters + ---------- + channel_model : str + The channel model + param : Parameters + The simulation paramters. + param_system : ParametersBase + Specific system paramters. It can be either ParametersIMT or other system parameters. + random_number_gen : rnd.RandomState + Random number generator + + Returns + ------- + Propagation + Propagation object + + Raises + ------ + ValueError + Raises ValueError if the channel model is not implemented. + """ if channel_model == "FSPL": return PropagationFreeSpace(random_number_gen) elif channel_model == "ABG": @@ -39,18 +79,50 @@ def create_propagation(channel_model: str, param: Parameters, random_number_gen: elif channel_model == "TerrestrialSimple": return PropagationTerSimple(random_number_gen) elif channel_model == "P619": - return PropagationP619(random_number_gen) + if isinstance(param_system, ParametersImt): + if param_system.topology.type != "NTN": + raise ValueError( + f"PropagationFactory: Channel model P.619 is invalid for topolgy {param.imt.topology.type}", + ) + else: + # P.619 model is used only for space-to-earth links + if param.imt.topology.type != "NTN" and not param_system.is_space_to_earth: + raise ValueError(( + "PropagationFactory: Channel model P.619 is invalid" + f"for system {param.general.system} and IMT " + f"topology {param.imt.topology.type}" + )) + return PropagationP619( + random_number_gen=random_number_gen, + space_station_alt_m=param_system.param_p619.space_station_alt_m, + earth_station_alt_m=param_system.param_p619.earth_station_alt_m, + earth_station_lat_deg=param_system.param_p619.earth_station_lat_deg, + earth_station_long_diff_deg=param_system.param_p619.earth_station_lat_deg, + season=param_system.season, + ) elif channel_model == "P452": - return PropagationClearAir(random_number_gen) + return PropagationClearAir(random_number_gen, param_system.param_p452) elif channel_model == "TVRO-URBAN": return PropagationTvro(random_number_gen, "URBAN") elif channel_model == "TVRO-SUBURBAN": return PropagationTvro(random_number_gen, "SUBURBAN") elif channel_model == "HDFSS": - return PropagationHDFSS(param.fss_es,random_number_gen) + if param.general.system == "FSS_ES": + # TODO: use param_hdfss in fss_es as well + return PropagationHDFSS(param.fss_es, random_number_gen) + else: + return PropagationHDFSS(param_system.param_hdfss, random_number_gen) elif channel_model == "INDOOR": - return PropagationIndoor(random_number_gen, param.indoor, - param.imt.ue_k*param.imt.ue_k_m) + return PropagationIndoor( + random_number_gen, + param.imt.topology.indoor, + param.imt.ue.k * param.imt.ue.k_m, + ), + elif channel_model == "P1411": + return PropagationP1411(random_number_gen, "URBAN") + + elif channel_model == "EHF": + return PropagationEHF(random_number_gen, param.imt.los_adjustment_factor, param.imt) else: sys.stderr.write("ERROR\nInvalid channel_model: " + channel_model) sys.exit(1) diff --git a/sharc/propagation/propagation_free_space.py b/sharc/propagation/propagation_free_space.py index 2ab3557f1..408b53b61 100644 --- a/sharc/propagation/propagation_free_space.py +++ b/sharc/propagation/propagation_free_space.py @@ -4,29 +4,71 @@ @author: edgar """ +import numpy as np +from multipledispatch import dispatch from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters -import numpy as np class PropagationFreeSpace(Propagation): """ Implements the Free Space propagation model. - Frequency in MHz and distance in meters + + Frequency in MHz and distance are in meters """ - def get_loss(self, *args, **kwargs) -> np.array: - if "distance_2D" in kwargs: - d = kwargs["distance_2D"] - else: - d = kwargs["distance_3D"] + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper for the calculation loss between station_a and station_b. + + Parameters + ---------- + station_a : StationManager + StationManager container representing station_a + station_b : StationManager + StationManager container representing station_a + params : Parameters + Simulation parameters needed for the propagation class. + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + distance_3d = station_a.get_3d_distance_to(station_b) + loss = self.get_free_space_loss(frequency=frequency, distance=distance_3d) + + return loss - f = kwargs["frequency"] - number_of_sectors = kwargs.pop("number_of_sectors",1) + @dispatch(np.ndarray, np.ndarray) + def get_loss(self, distance_3D: np.array, frequency: float) -> np.array: + return self.get_free_space_loss(np.unique(frequency), distance_3D) - loss = 20*np.log10(d) + 20*np.log10(f) - 27.55 + def get_free_space_loss(self, frequency: float, distance: np.array) -> np.array: + """Calculates the free-space loss for the given distance and frequency - if number_of_sectors > 1: - loss = np.repeat(loss, number_of_sectors, 1) + Parameters + ---------- + distance : float + 3D distance array between stations + frequency : float + wave frequency + Returns + ------- + np.array + returns the path loss array with shape distance.shape + """ + loss = 20 * np.log10(distance) + 20 * np.log10(frequency) - 27.55 return loss diff --git a/sharc/propagation/propagation_hdfss.py b/sharc/propagation/propagation_hdfss.py index 557513bad..48a70e385 100644 --- a/sharc/propagation/propagation_hdfss.py +++ b/sharc/propagation/propagation_hdfss.py @@ -10,57 +10,78 @@ from sharc.parameters.parameters_fss_es import ParametersFssEs from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters from sharc.propagation.propagation_hdfss_roof_top import PropagationHDFSSRoofTop from sharc.propagation.propagation_hdfss_building_side import PropagationHDFSSBuildingSide + class PropagationHDFSS(Propagation): """ - + High-Density Fixed Satellite System Propagation Model + This is a compoposition of HDFSS Rooftop and Indoor models using during simulation run-time. """ + def __init__(self, param: ParametersFssEs, rnd_num_gen: np.random.RandomState): - """ - - """ super().__init__(rnd_num_gen) - + if param.es_position == "ROOFTOP": - self.propagation = PropagationHDFSSRoofTop(param,rnd_num_gen) + self.propagation = PropagationHDFSSRoofTop(param, rnd_num_gen) elif param.es_position == "BUILDINGSIDE": - self.propagation = PropagationHDFSSBuildingSide(param,rnd_num_gen) + self.propagation = PropagationHDFSSBuildingSide(param, rnd_num_gen) else: - sys.stderr.write("ERROR\nInvalid es_position: " + param.es_position) + sys.stderr.write( + "ERROR\nInvalid es_position: " + param.es_position, + ) sys.exit(1) - - def get_loss(self, *args, **kwargs) -> np.array: - """ - + + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the get_loss method to fit the Propagation ABC class interface + Calculates the loss between station_a and station_b + + Parameters + ---------- + params : Parameters + Simulation parameters needed for the propagation class + frequency: float + Center frequency + station_a : StationManager + StationManager container representing the system station + station_b : StationManager + StationManager container representing the IMT station + station_a_gains: np.ndarray defaults to None + System antenna gains + station_b_gains: np.ndarray defaults to None + IMT antenna gains + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station """ - if "distance_3D" in kwargs: - d = kwargs["distance_3D"] - else: - d = kwargs["distance_2D"] - - ele = kwargs["elevation"] - sta_type = kwargs["imt_sta_type"] - f = kwargs["frequency"] - num_sec = kwargs.pop("number_of_sectors",1) - - i_x = kwargs['imt_x'] - i_y = kwargs['imt_y'] - i_z = kwargs['imt_z'] - e_x = kwargs["es_x"] - e_y = kwargs["es_y"] - e_z = kwargs["es_z"] - - return self.propagation.get_loss(distance_3D = d, - elevation = ele, - imt_sta_type = sta_type, - frequency = f, - number_of_sectors = num_sec, - imt_x = i_x, - imt_y = i_y, - imt_z = i_z, - es_x = e_x, - es_y = e_y, - es_z = e_z) - \ No newline at end of file + distance = station_a.get_3d_distance_to(station_b) # P.452 expects Kms + frequency_array = frequency * \ + np.ones(distance.shape) # P.452 expects GHz + elevation = station_b.get_elevation(station_a) + + return self.propagation.get_loss( + distance_3D=distance, + elevation=elevation, + imt_sta_type=station_b.station_type, + frequency=frequency_array, + imt_x=station_b.x, + imt_y=station_b.y, + imt_z=station_b.height, + es_x=station_a.x, + es_y=station_a.y, + es_z=station_a.height, + ) diff --git a/sharc/propagation/propagation_hdfss_building_side.py b/sharc/propagation/propagation_hdfss_building_side.py index c80da936c..0abba8648 100644 --- a/sharc/propagation/propagation_hdfss_building_side.py +++ b/sharc/propagation/propagation_hdfss_building_side.py @@ -8,165 +8,200 @@ import numpy as np import sys -from shapely.geometry import LineString, Polygon, Point -from sharc.parameters.parameters_fss_es import ParametersFssEs from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_p1411 import PropagationP1411 from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss from sharc.support.enumerations import StationType +from sharc.parameters.parameters_hdfss import ParametersHDFSS + class PropagationHDFSSBuildingSide(Propagation): """ - + """ - def __init__(self, param: ParametersFssEs, random_number_gen: np.random.RandomState): + + def __init__(self, param: ParametersHDFSS, random_number_gen: np.random.RandomState): super().__init__(random_number_gen) - + self.param = param - + # Building dimentions self.b_w = 120 self.b_d = 50 self.b_h = 3 self.s_w = 30 self.b_tol = 0.05 - + self.HIGH_LOSS = 4000 self.LOSS_PER_FLOOR = 50 - + self.propagation_fspl = PropagationFreeSpace(random_number_gen) - self.propagation_p1411 = PropagationP1411(random_number_gen, - above_clutter = False) - self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen) - + self.propagation_p1411 = PropagationP1411( + random_number_gen, + above_clutter=False, + ) + self.building_entry = PropagationBuildingEntryLoss( + self.random_number_gen, + ) + def get_loss(self, *args, **kwargs) -> np.array: """ - + """ # Parse entries if "distance_3D" in kwargs: d = kwargs["distance_3D"] else: d = kwargs["distance_2D"] - + elevation = np.transpose(kwargs["elevation"]) imt_sta_type = kwargs["imt_sta_type"] f = kwargs["frequency"] - number_of_sectors = kwargs.pop("number_of_sectors",1) - + number_of_sectors = kwargs.pop("number_of_sectors", 1) + imt_x = kwargs['imt_x'] imt_y = kwargs['imt_y'] - imt_z = kwargs['imt_z'] es_x = kwargs["es_x"] es_y = kwargs["es_y"] - es_z = kwargs["es_z"] - + # Define which stations are on the same building - same_build = self.is_same_building(imt_x,imt_y, - es_x, es_y) + same_build = self.is_same_building( + imt_x, imt_y, + es_x, es_y, + ) not_same_build = np.logical_not(same_build) - + # Define which stations are on the building in front - next_build = self.is_next_building(imt_x,imt_y, - es_x, es_y) + next_build = self.is_next_building( + imt_x, imt_y, + es_x, es_y, + ) not_next_build = np.logical_not(next_build) - + # Define which stations are in other buildings - other_build = np.logical_and(not_same_build,not_next_build) - + other_build = np.logical_and(not_same_build, not_next_build) + # Path loss loss = np.zeros_like(d) - + # # Use a loss per floor # loss[:,same_build] += self.get_same_build_loss(imt_z[same_build], # es_z) if not self.param.same_building_enabled: - loss[:,same_build] += self.HIGH_LOSS - loss[:,same_build] += self.propagation_fspl.get_loss(distance_3D=d[:,same_build], - frequency=f[:,same_build]) - - loss[:,next_build] += self.propagation_p1411.get_loss(distance_3D=d[:,next_build], - frequency=f[:,next_build], - los=True, - shadow=self.param.shadow_enabled) - - loss[:,other_build] += self.propagation_p1411.get_loss(distance_3D=d[:,other_build], - frequency=f[:,other_build], - los=False, - shadow=self.param.shadow_enabled) - + loss[:, same_build] += self.HIGH_LOSS + loss[:, same_build] += self.propagation_fspl.get_loss( + d[:, same_build], + f[:, same_build], + ) + + loss[:, next_build] += self.propagation_p1411.get_loss( + distance_3D=d[:, next_build], + frequency=f[ + :, + next_build, + ], + los=True, + shadow=self.param.shadow_enabled, + ) + + loss[:, other_build] += self.propagation_p1411.get_loss( + distance_3D=d[:, other_build], + frequency=f[ + :, + other_build, + ], + los=False, + shadow=self.param.shadow_enabled, + ) + # Building entry loss if self.param.building_loss_enabled: - build_loss = self.get_building_loss(imt_sta_type, - f, - elevation) + build_loss = self.get_building_loss(imt_sta_type, f, elevation) else: build_loss = 0.0 - + # Diffraction loss diff_loss = np.zeros_like(loss) - + # Compute final loss loss = loss + build_loss + diff_loss - + if number_of_sectors > 1: loss = np.repeat(loss, number_of_sectors, 1) - + return loss, build_loss, diff_loss - - - def get_building_loss(self,imt_sta_type,f,elevation): + + def get_building_loss(self, imt_sta_type, f, elevation): if imt_sta_type is StationType.IMT_UE: build_loss = self.building_entry.get_loss(f, elevation) elif imt_sta_type is StationType.IMT_BS: if self.param.bs_building_entry_loss_type == 'P2109_RANDOM': build_loss = self.building_entry.get_loss(f, elevation) elif self.param.bs_building_entry_loss_type == 'P2109_FIXED': - build_loss = self.building_entry.get_loss(f, elevation, prob=self.param.bs_building_entry_loss_prob) + build_loss = self.building_entry.get_loss( + f, elevation, prob=self.param.bs_building_entry_loss_prob, + ) elif self.param.bs_building_entry_loss_type == 'FIXED_VALUE': build_loss = self.param.bs_building_entry_loss_value else: - sys.stderr.write("ERROR\nBuilding entry loss type: " + - self.param.bs_building_entry_loss_type) + sys.stderr.write( + "ERROR\nBuilding entry loss type: " + + self.param.bs_building_entry_loss_type, + ) sys.exit(1) - + return build_loss - - def is_same_building(self,imt_x,imt_y, es_x, es_y): - - building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2]) - building_y_range = (es_y - self.b_d/2) + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2]) - - is_in_x = np.logical_and(imt_x >= building_x_range[0],imt_x <= building_x_range[1]) - is_in_y = np.logical_and(imt_y >= building_y_range[0],imt_y <= building_y_range[1]) - - is_in_building = np.logical_and(is_in_x,is_in_y) - + + def is_same_building(self, imt_x, imt_y, es_x, es_y): + + building_x_range = es_x + (1 + self.b_tol) * \ + np.array([-self.b_w / 2, +self.b_w / 2]) + building_y_range = (es_y - self.b_d / 2) + \ + (1 + self.b_tol) * np.array([-self.b_d / 2, +self.b_d / 2]) + + is_in_x = np.logical_and( + imt_x >= building_x_range[0], imt_x <= building_x_range[1], + ) + is_in_y = np.logical_and( + imt_y >= building_y_range[0], imt_y <= building_y_range[1], + ) + + is_in_building = np.logical_and(is_in_x, is_in_y) + return is_in_building - - def get_same_build_loss(self,imt_z,es_z): + + def get_same_build_loss(self, imt_z, es_z): floor_number = imt_z - es_z - floor_number[floor_number >= 0] = np.floor(floor_number[floor_number >= 0]/self.b_h) - floor_number[floor_number < 0] = np.ceil(floor_number[floor_number < 0]/self.b_h) - - loss = self.LOSS_PER_FLOOR*floor_number - + floor_number[floor_number >= 0] = np.floor( + floor_number[floor_number >= 0] / self.b_h, + ) + floor_number[floor_number < 0] = np.ceil( + floor_number[floor_number < 0] / self.b_h, + ) + + loss = self.LOSS_PER_FLOOR * floor_number + return loss - - def is_next_building(self,imt_x,imt_y, es_x, es_y): - same_building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2]) - same_building_y_range = (es_y - self.b_d/2) + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2]) - + + def is_next_building(self, imt_x, imt_y, es_x, es_y): + same_building_x_range = es_x + \ + (1 + self.b_tol) * np.array([-self.b_w / 2, +self.b_w / 2]) + same_building_y_range = ( + es_y - self.b_d / 2 + ) + (1 + self.b_tol) * np.array([-self.b_d / 2, +self.b_d / 2]) + next_building_x_range = same_building_x_range next_building_y_range = same_building_y_range + self.b_d + self.s_w - - is_in_x = np.logical_and(imt_x >= next_building_x_range[0],imt_x <= next_building_x_range[1]) - is_in_y = np.logical_and(imt_y >= next_building_y_range[0],imt_y <= next_building_y_range[1]) - - is_in_next_building = np.logical_and(is_in_x,is_in_y) - + + is_in_x = np.logical_and( + imt_x >= next_building_x_range[0], imt_x <= next_building_x_range[1], + ) + is_in_y = np.logical_and( + imt_y >= next_building_y_range[0], imt_y <= next_building_y_range[1], + ) + + is_in_next_building = np.logical_and(is_in_x, is_in_y) + return is_in_next_building - - \ No newline at end of file diff --git a/sharc/propagation/propagation_hdfss_roof_top.py b/sharc/propagation/propagation_hdfss_roof_top.py index 7481963be..2b47e2c35 100644 --- a/sharc/propagation/propagation_hdfss_roof_top.py +++ b/sharc/propagation/propagation_hdfss_roof_top.py @@ -8,7 +8,9 @@ import numpy as np import sys from shapely.geometry import LineString, Polygon, Point +from sharc.parameters.constants import SPEED_OF_LIGHT +from sharc.parameters.parameters_hdfss import ParametersHDFSS from sharc.parameters.parameters_fss_es import ParametersFssEs from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_p1411 import PropagationP1411 @@ -16,6 +18,7 @@ from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss from sharc.support.enumerations import StationType + class PropagationHDFSSRoofTop(Propagation): """ This is a wrapper class which can be used for indoor simulations. It @@ -25,30 +28,33 @@ class PropagationHDFSSRoofTop(Propagation): P.1411 LOS for distances 55m < distance < 260m P.1411 NLOS for distances distance > 260m """ - def __init__(self, param: ParametersFssEs, random_number_gen: np.random.RandomState): + + def __init__(self, param: ParametersHDFSS, random_number_gen: np.random.RandomState): super().__init__(random_number_gen) - + self.param = param - + self.fspl_dist = 35 self.fspl_to_los_dist = 55 self.los_dist = 100 self.los_to_nlos_dist = 260 - + # Building dimentions self.b_w = 120 self.b_d = 50 self.b_tol = 0.05 - + self.HIGH_LOSS = 4000 self.LOSS_PER_FLOOR = 50 - + self.propagation_fspl = PropagationFreeSpace(random_number_gen) self.propagation_p1411 = PropagationP1411(random_number_gen) - self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen) - - self.SPEED_OF_LIGHT = 299792458.0 - + self.building_entry = PropagationBuildingEntryLoss( + self.random_number_gen, + ) + + self.SPEED_OF_LIGHT = SPEED_OF_LIGHT + def get_loss(self, *args, **kwargs) -> np.array: """ Calculates path loss for given distances and frequencies @@ -72,38 +78,46 @@ def get_loss(self, *args, **kwargs) -> np.array: d = kwargs["distance_3D"] else: d = kwargs["distance_2D"] - + elevation = np.transpose(kwargs["elevation"]) imt_sta_type = kwargs["imt_sta_type"] f = kwargs["frequency"] - number_of_sectors = kwargs.pop("number_of_sectors",1) - + number_of_sectors = kwargs.pop("number_of_sectors", 1) + imt_x = kwargs['imt_x'] imt_y = kwargs['imt_y'] imt_z = kwargs['imt_z'] es_x = kwargs["es_x"] es_y = kwargs["es_y"] es_z = kwargs["es_z"] - + # Define which stations are on the same building - same_build = self.is_same_building(imt_x,imt_y, - es_x, es_y) + same_build = self.is_same_building( + imt_x, imt_y, + es_x, es_y, + ) not_same_build = np.logical_not(same_build) - + # Define boolean ranges fspl_bool = d <= self.fspl_dist - - fspl_to_los_bool = np.logical_and(d > self.fspl_dist, - d <= self.fspl_to_los_dist) - - los_bool = np.logical_and(d > self.fspl_to_los_dist, - d <= self.los_dist) - - los_to_nlos_bool = np.logical_and(d > self.los_dist, - d <= self.los_to_nlos_dist) - + + fspl_to_los_bool = np.logical_and( + d > self.fspl_dist, + d <= self.fspl_to_los_dist, + ) + + los_bool = np.logical_and( + d > self.fspl_to_los_dist, + d <= self.los_dist, + ) + + los_to_nlos_bool = np.logical_and( + d > self.los_dist, + d <= self.los_to_nlos_dist, + ) + nlos_bool = d > self.los_to_nlos_dist - + # Define indexes same_build_idx = np.where(same_build)[0] fspl_idx = np.where(fspl_bool)[1] @@ -111,183 +125,234 @@ def get_loss(self, *args, **kwargs) -> np.array: los_idx = np.where(los_bool)[1] los_to_nlos_idx = np.where(los_to_nlos_bool)[1] nlos_idx = np.where(nlos_bool)[1] - + # Path loss loss = np.zeros_like(d) - + if not self.param.same_building_enabled: - loss[:,same_build_idx] += self.HIGH_LOSS + loss[:, same_build_idx] += self.HIGH_LOSS else: - loss[:,same_build_idx] += self.get_same_build_loss(imt_z[same_build_idx], - es_z) - - loss[:,fspl_idx] += self.propagation_fspl.get_loss(distance_3D=d[:,fspl_idx], - frequency=f[:,fspl_idx]) - loss[:,fspl_to_los_idx] += self.interpolate_fspl_to_los(d[:,fspl_to_los_idx], - f[:,fspl_to_los_idx], - self.param.shadow_enabled) - loss[:,los_idx] += self.propagation_p1411.get_loss(distance_3D=d[:,los_idx], - frequency=f[:,los_idx], - los=True, - shadow=self.param.shadow_enabled) - loss[:,los_to_nlos_idx] += self.interpolate_los_to_nlos(d[:,los_to_nlos_idx], - f[:,los_to_nlos_idx], - self.param.shadow_enabled) - loss[:,nlos_idx] += self.propagation_p1411.get_loss(distance_3D=d[:,nlos_idx], - frequency=f[:,nlos_idx], - los=False, - shadow=self.param.shadow_enabled) - + loss[:, same_build_idx] += self.get_same_build_loss( + imt_z[same_build_idx], + es_z, + ) + + loss[:, fspl_idx] += self.propagation_fspl.get_free_space_loss( + distance=d[:, fspl_idx], frequency=f[:, fspl_idx], + ) + loss[:, fspl_to_los_idx] += self.interpolate_fspl_to_los( + d[:, fspl_to_los_idx], + f[:, fspl_to_los_idx], + self.param.shadow_enabled, + ) + loss[:, los_idx] += self.propagation_p1411.get_loss( + distance_3D=d[:, los_idx], + frequency=f[ + :, + los_idx, + ], + los=True, + shadow=self.param.shadow_enabled, + ) + loss[:, los_to_nlos_idx] += self.interpolate_los_to_nlos( + d[:, los_to_nlos_idx], + f[:, los_to_nlos_idx], + self.param.shadow_enabled, + ) + loss[:, nlos_idx] += self.propagation_p1411.get_loss( + distance_3D=d[:, nlos_idx], + frequency=f[ + :, + nlos_idx, + ], + los=False, + shadow=self.param.shadow_enabled, + ) + # Building entry loss if self.param.building_loss_enabled: build_loss = np.zeros_like(loss) - build_loss[0,not_same_build] = self.get_building_loss(imt_sta_type, - f[:,not_same_build], - elevation[:,not_same_build]) + build_loss[0, not_same_build] = self.get_building_loss( + imt_sta_type, + f[:, not_same_build], + elevation[:, not_same_build], + ) else: build_loss = 0.0 - + # Diffraction loss diff_loss = np.zeros_like(loss) if self.param.diffraction_enabled: - h, d1, d2 = self.get_diff_distances(imt_x,imt_y, imt_z, - es_x, es_y, es_z) + h, d1, d2 = self.get_diff_distances( + imt_x, imt_y, imt_z, + es_x, es_y, es_z, + ) diff_loss = np.zeros_like(loss) - diff_loss[0,not_same_build] = self.get_diffraction_loss(h[not_same_build], - d1[not_same_build], - d2[not_same_build], - f[:,not_same_build]) - + diff_loss[0, not_same_build] = self.get_diffraction_loss( + h[not_same_build], + d1[not_same_build], + d2[not_same_build], + f[:, not_same_build], + ) + # Compute final loss loss = loss + build_loss + diff_loss - + if number_of_sectors > 1: loss = np.repeat(loss, number_of_sectors, 1) - + return loss, build_loss, diff_loss - - def interpolate_fspl_to_los(self,dist,freq,shad): - fspl_loss = self.propagation_fspl.get_loss(distance_3D=self.fspl_dist, - frequency=freq) - los_loss = self.propagation_p1411.get_loss(distance_3D=self.fspl_to_los_dist, - frequency=freq, - los=True, - shadow=False) - - loss = (dist - self.fspl_dist)*(los_loss - fspl_loss)/(self.fspl_to_los_dist - self.fspl_dist) + fspl_loss - + + def interpolate_fspl_to_los(self, dist, freq, shad): + fspl_loss = self.propagation_fspl.get_free_space_loss( + distance=self.fspl_dist, + frequency=freq, + ) + los_loss = self.propagation_p1411.get_loss( + distance_3D=self.fspl_to_los_dist, + frequency=freq, + los=True, + shadow=False, + ) + + loss = (dist - self.fspl_dist) * (los_loss - fspl_loss) / \ + (self.fspl_to_los_dist - self.fspl_dist) + fspl_loss + if shad: - interp_sigma = (dist - self.fspl_dist)*(self.propagation_p1411.los_sigma)/(self.fspl_to_los_dist - self.fspl_dist) - loss = loss + self.random_number_gen.normal(0.0,interp_sigma) - + interp_sigma = (dist - self.fspl_dist) * (self.propagation_p1411.los_sigma) / \ + (self.fspl_to_los_dist - self.fspl_dist) + loss = loss + self.random_number_gen.normal(0.0, interp_sigma) + return loss - - def interpolate_los_to_nlos(self,dist,freq,shad): - los_loss = self.propagation_p1411.get_loss(distance_3D=self.los_dist, - frequency=freq, - los=True, - shadow=False) - nlos_loss = self.propagation_p1411.get_loss(distance_3D=self.los_to_nlos_dist, - frequency=freq, - los=False, - shadow=False) - - loss = (dist-self.los_dist)*(nlos_loss - los_loss)/(self.los_to_nlos_dist - self.los_dist) + los_loss - + + def interpolate_los_to_nlos(self, dist, freq, shad): + los_loss = self.propagation_p1411.get_loss( + distance_3D=self.los_dist, + frequency=freq, + los=True, + shadow=False, + ) + nlos_loss = self.propagation_p1411.get_loss( + distance_3D=self.los_to_nlos_dist, + frequency=freq, + los=False, + shadow=False, + ) + + loss = (dist - self.los_dist) * (nlos_loss - los_loss) / \ + (self.los_to_nlos_dist - self.los_dist) + los_loss + if shad: - interp_sigma = (dist-self.los_dist)*(self.propagation_p1411.nlos_sigma - self.propagation_p1411.los_sigma)/(self.los_to_nlos_dist - self.los_dist) +\ - self.propagation_p1411.los_sigma - loss = loss + self.random_number_gen.normal(0.0,interp_sigma) - + interp_sigma = (dist - self.los_dist) * \ + (self.propagation_p1411.nlos_sigma - self.propagation_p1411.los_sigma) / \ + (self.los_to_nlos_dist - self.los_dist) + \ + self.propagation_p1411.los_sigma + loss = loss + self.random_number_gen.normal(0.0, interp_sigma) + return loss - - def get_building_loss(self,imt_sta_type,f,elevation): + + def get_building_loss(self, imt_sta_type, f, elevation): if imt_sta_type is StationType.IMT_UE: build_loss = self.building_entry.get_loss(f, elevation) elif imt_sta_type is StationType.IMT_BS: if self.param.bs_building_entry_loss_type == 'P2109_RANDOM': build_loss = self.building_entry.get_loss(f, elevation) elif self.param.bs_building_entry_loss_type == 'P2109_FIXED': - build_loss = self.building_entry.get_loss(f, elevation, prob=self.param.bs_building_entry_loss_prob) + build_loss = self.building_entry.get_loss( + f, elevation, prob=self.param.bs_building_entry_loss_prob, + ) elif self.param.bs_building_entry_loss_type == 'FIXED_VALUE': build_loss = self.param.bs_building_entry_loss_value else: - sys.stderr.write("ERROR\nBuilding entry loss type: " + - self.param.bs_building_entry_loss_type) + sys.stderr.write( + "ERROR\nBuilding entry loss type: " + + self.param.bs_building_entry_loss_type, + ) sys.exit(1) - + return build_loss - - def is_same_building(self,imt_x,imt_y, es_x, es_y): - - building_x_range = es_x + (1 + self.b_tol)*np.array([-self.b_w/2,+self.b_w/2]) - building_y_range = es_y + (1 + self.b_tol)*np.array([-self.b_d/2,+self.b_d/2]) - - is_in_x = np.logical_and(imt_x >= building_x_range[0],imt_x <= building_x_range[1]) - is_in_y = np.logical_and(imt_y >= building_y_range[0],imt_y <= building_y_range[1]) - - is_in_building = np.logical_and(is_in_x,is_in_y) - + + def is_same_building(self, imt_x, imt_y, es_x, es_y): + + building_x_range = es_x + (1 + self.b_tol) * \ + np.array([-self.b_w / 2, +self.b_w / 2]) + building_y_range = es_y + (1 + self.b_tol) * \ + np.array([-self.b_d / 2, +self.b_d / 2]) + + is_in_x = np.logical_and( + imt_x >= building_x_range[0], imt_x <= building_x_range[1], + ) + is_in_y = np.logical_and( + imt_y >= building_y_range[0], imt_y <= building_y_range[1], + ) + + is_in_building = np.logical_and(is_in_x, is_in_y) + return is_in_building - - def get_same_build_loss(self,imt_z,es_z): - floor_number = np.floor_divide((es_z - imt_z),3) + 1 - - loss = self.LOSS_PER_FLOOR*floor_number - + + def get_same_build_loss(self, imt_z, es_z): + floor_number = np.floor_divide((es_z - imt_z), 3) + 1 + + loss = self.LOSS_PER_FLOOR * floor_number + return loss - - def get_diff_distances(self,imt_x,imt_y, imt_z, es_x, es_y, es_z, dist_2D=False): - - build_poly = Polygon([[es_x + self.b_w/2, es_y + self.b_d/2], - [es_x - self.b_w/2, es_y + self.b_d/2], - [es_x - self.b_w/2, es_y - self.b_d/2], - [es_x + self.b_w/2, es_y - self.b_d/2]]) - es_point = Point([es_x,es_y]) - + + def get_diff_distances(self, imt_x, imt_y, imt_z, es_x, es_y, es_z, dist_2D=False): + + build_poly = Polygon([ + [es_x + self.b_w / 2, es_y + self.b_d / 2], + [es_x - self.b_w / 2, es_y + self.b_d / 2], + [es_x - self.b_w / 2, es_y - self.b_d / 2], + [es_x + self.b_w / 2, es_y - self.b_d / 2], + ]) + es_point = Point([es_x, es_y]) + d1_2D = np.zeros_like(imt_x) d2_2D = np.zeros_like(imt_x) dist = np.zeros_like(imt_x) - for k,(x,y) in enumerate(zip(imt_x,imt_y)): - line = LineString([[es_x,es_y],[x,y]]) + for k, (x, y) in enumerate(zip(imt_x, imt_y)): + line = LineString([[es_x, es_y], [x, y]]) intersection_line = line.intersection(build_poly) d1_2D[k] = intersection_line.length - - imt_point = Point([x,y]) + + imt_point = Point([x, y]) dist[k] = es_point.distance(imt_point) d2_2D[k] = dist[k] - d1_2D[k] - + if dist_2D: return d1_2D, d2_2D - + build_height = es_z - 1 - z_in_build = imt_z + (es_z - imt_z)*d2_2D/dist + z_in_build = imt_z + (es_z - imt_z) * d2_2D / dist h = build_height - z_in_build - - d1 = np.sqrt(1**2 + np.power(d1_2D,2)) - d2 = np.sqrt(np.power((build_height - imt_z),2) + np.power(d2_2D,2)) - + + d1 = np.sqrt(1**2 + np.power(d1_2D, 2)) + d2 = np.sqrt(np.power((build_height - imt_z), 2) + np.power(d2_2D, 2)) + return h, d1, d2 - - def get_diffraction_loss(self,h, d1, d2, f): - - wavelength = self.SPEED_OF_LIGHT/(f*1e6) - - v = h*np.sqrt((2/wavelength)*(1/d1 + 1/d2)) - + + def get_diffraction_loss(self, h, d1, d2, f): + + wavelength = self.SPEED_OF_LIGHT / (f * 1e6) + + v = h * np.sqrt((2 / wavelength) * (1 / d1 + 1 / d2)) + loss = np.zeros_like(v) - + v_idx = v > -0.7 - - loss[v_idx] = 6.9 + 20*np.log10(np.sqrt(np.power((v[v_idx] - 0.1), 2) + 1)\ - + v[v_idx] - 0.1) + + loss[v_idx] = 6.9 + 20 * np.log10( + np.sqrt(np.power((v[v_idx] - 0.1), 2) + 1) + + v[v_idx] - 0.1, + ) return loss - + + if __name__ == '__main__': - + from sharc.propagation.propagation_hdfss import PropagationHDFSS import matplotlib.pyplot as plt - + rnd = np.random.RandomState(101) par = ParametersFssEs() par.building_loss_enabled = False @@ -297,50 +362,54 @@ def get_diffraction_loss(self,h, d1, d2, f): par.bs_building_entry_loss_prob = 0.5 par.bs_building_entry_loss_value = 50 par.es_position = "ROOFTOP" - prop = PropagationHDFSS(par,rnd) - - d = np.linspace(5,1000,num=2000) + prop = PropagationHDFSS(par, rnd) + + d = np.linspace(5, 1000, num=2000) d = np.array([list(d)]) - f = 40e3*np.ones_like(d) + f = 40e3 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) sta_type = StationType.IMT_BS - + # Without shadow - loss_interp = prop.get_loss(distance_3D=d, - frequency=f, - elevation=ele, - imt_sta_type=sta_type) + loss_interp = prop.get_loss( + distance_3D=d, + frequency=f, + elevation=ele, + imt_sta_type=sta_type, + ) prop.fspl_dist = prop.fspl_to_los_dist prop.los_dist = prop.los_to_nlos_dist - loss_no_interp = prop.get_loss(distance_3D=d, - frequency=f, - elevation=ele, - imt_sta_type=sta_type) - + loss_no_interp = prop.get_loss( + distance_3D=d, + frequency=f, + elevation=ele, + imt_sta_type=sta_type, + ) + ravel_d = np.ravel(d) ravel_loss_interp = np.ravel(loss_interp) ravel_loss_no_interp = np.ravel(loss_no_interp) - plt.plot(ravel_d,ravel_loss_interp,'k-',label='Interpolated') - plt.plot(ravel_d,ravel_loss_no_interp,'k--',label='Not Interpolated') + plt.plot(ravel_d, ravel_loss_interp, 'k-', label='Interpolated') + plt.plot(ravel_d, ravel_loss_no_interp, 'k--', label='Not Interpolated') plt.legend() plt.xlabel("Distance [m]") plt.ylabel("Path Loss [dB]") plt.grid() plt.show() - + # With shadow par.shadow_enabled = True - prop = PropagationHDFSS(par,rnd) - loss = prop.get_loss(distance_3D=d, - frequency=f, - elevation=ele, - imt_sta_type=sta_type) - + prop = PropagationHDFSS(par, rnd) + loss = prop.get_loss( + distance_3D=d, + frequency=f, + elevation=ele, + imt_sta_type=sta_type, + ) + ravel_loss = np.ravel(loss) - plt.plot(ravel_d,ravel_loss) + plt.plot(ravel_d, ravel_loss) plt.xlabel("Distance [m]") plt.ylabel("Path Loss [dB]") plt.grid() plt.show() - - \ No newline at end of file diff --git a/sharc/propagation/propagation_indoor.py b/sharc/propagation/propagation_indoor.py index 7cc377444..fb47b86b3 100644 --- a/sharc/propagation/propagation_indoor.py +++ b/sharc/propagation/propagation_indoor.py @@ -4,17 +4,18 @@ @author: edgar """ +from multipledispatch import dispatch +import sys +import numpy as np -from sharc.parameters.parameters_indoor import ParametersIndoor +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters +from sharc.parameters.imt.parameters_indoor import ParametersIndoor from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_inh_office import PropagationInhOffice from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss -import sys -import numpy as np -import matplotlib.pyplot as plt -from cycler import cycler class PropagationIndoor(Propagation): """ @@ -31,7 +32,7 @@ class PropagationIndoor(Propagation): # interference is much higher than inter-building interference HIGH_PATH_LOSS = 400 - def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIndoor,ue_per_cell): + def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIndoor, ue_per_cell): super().__init__(random_number_gen) if param.basic_path_loss == "FSPL": @@ -39,15 +40,87 @@ def __init__(self, random_number_gen: np.random.RandomState, param: ParametersIn elif param.basic_path_loss == "INH_OFFICE": self.bpl = PropagationInhOffice(random_number_gen) else: - sys.stderr.write("ERROR\nInvalid indoor basic path loss model: " + param.basic_path_loss) + sys.stderr.write( + "ERROR\nInvalid indoor basic path loss model: " + param.basic_path_loss, + ) sys.exit(1) self.bel = PropagationBuildingEntryLoss(random_number_gen) self.building_class = param.building_class self.bs_per_building = param.num_cells - self.ue_per_building = ue_per_cell*param.num_cells + self.ue_per_building = ue_per_cell * param.num_cells + + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the get_loss method to fit the Propagation ABC class interface + Calculates the loss between station_a and station_b - def get_loss(self, *args, **kwargs) -> np.array: + Parameters + ---------- + params : Parameters + Simulation parameters needed for the propagation class + frequency: float + Center frequency + station_a : StationManager + StationManager container + station_b : StationManager + StationManager container + station_a_gains: np.ndarray defaults to None + Not used + station_b_gains: np.ndarray defaults to None + Not used + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled: + bs_to_ue_dist_2d, bs_to_ue_dist_3d, _, _ = \ + station_b.get_dist_angles_wrap_around(station_a) + else: + bs_to_ue_dist_2d = station_b.get_distance_to(station_a) + bs_to_ue_dist_3d = station_b.get_3d_distance_to(station_a) + + frequency_array = frequency * np.ones(bs_to_ue_dist_2d.shape) + indoor_stations = np.tile( + station_a.indoor, (station_b.num_stations, 1), + ) + elevation = np.transpose(station_a.get_elevation(station_b)) + + return self.get_loss( + bs_to_ue_dist_3d, + bs_to_ue_dist_2d, + frequency_array, + elevation, + indoor_stations, + params.imt.shadowing, + ) + + # pylint: disable=function-redefined + # pylint: disable=arguments-renamed + @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool) + def get_loss( + self, distance_3D: np.ndarray, distance_2D: np.ndarray, frequency: float, + elevation: np.ndarray, indoor_stations: np.ndarray, shadowing_flag: bool, + ) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing (if shadowing has to be added) @@ -66,33 +139,30 @@ def get_loss(self, *args, **kwargs) -> np.array: array with path loss values with dimensions of distance_2D """ - distance_3D = kwargs["distance_3D"] - distance_2D = kwargs["distance_2D"] - elevation = kwargs["elevation"] - frequency = kwargs["frequency"] - indoor = kwargs["indoor_stations"] - shadowing = kwargs["shadowing"] - - loss = PropagationIndoor.HIGH_PATH_LOSS*np.ones(frequency.shape) - iter = int(frequency.shape[0]/self.bs_per_building) + loss = PropagationIndoor.HIGH_PATH_LOSS * np.ones(frequency.shape) + iter = int(frequency.shape[0] / self.bs_per_building) for i in range(iter): - bi = int(self.bs_per_building*i) - bf = int(self.bs_per_building*(i+1)) - ui = int(self.ue_per_building*i) - uf = int(self.ue_per_building*(i+1)) + bi = int(self.bs_per_building * i) + bf = int(self.bs_per_building * (i + 1)) + ui = int(self.ue_per_building * i) + uf = int(self.ue_per_building * (i + 1)) # calculate basic path loss - loss[bi:bf,ui:uf] = self.bpl.get_loss(distance_3D = distance_3D[bi:bf,ui:uf], - distance_2D = distance_2D[bi:bf,ui:uf], - frequency = frequency[bi:bf,ui:uf], - indoor = indoor[0,ui:uf], - shadowing = shadowing) + loss[bi:bf, ui:uf] = self.bpl.get_loss( + distance_3D=distance_3D[bi:bf, ui:uf], + distance_2D=distance_2D[bi:bf, ui:uf], + frequency=frequency[bi:bf, ui:uf], + indoor=indoor_stations[0, ui:uf], + shadowing=shadowing_flag, + ) # calculates the additional building entry loss for outdoor UE's # that are served by indoor BS's - bel = (~ indoor[0,ui:uf]) * self.bel.get_loss(frequency[bi:bf,ui:uf], elevation[bi:bf,ui:uf], "RANDOM", self.building_class) + bel = (~ indoor_stations[0, ui:uf]) * self.bel.get_loss( + frequency[bi:bf, ui:uf], elevation[bi:bf, ui:uf], "RANDOM", self.building_class, + ) - loss[bi:bf,ui:uf] = loss[bi:bf,ui:uf] + bel + loss[bi:bf, ui:uf] = loss[bi:bf, ui:uf] + bel return loss @@ -110,24 +180,27 @@ def get_loss(self, *args, **kwargs) -> np.array: bs_per_building = 3 ue_per_bs = 3 - num_bs = bs_per_building*params.n_rows*params.n_colums - num_ue = num_bs*ue_per_bs - distance_2D = 150*np.random.random((num_bs, num_ue)) - frequency = 27000*np.ones(distance_2D.shape) - indoor = np.random.rand(num_bs) < params.ue_indoor_percent - h_bs = 3*np.ones(num_bs) - h_ue = 1.5*np.ones(num_ue) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) + num_bs = bs_per_building * params.n_rows * params.n_colums + num_ue = num_bs * ue_per_bs + distance_2D = 150 * np.random.random((num_bs, num_ue)) + frequency = 27000 * np.ones(distance_2D.shape) + indoor = np.random.rand(1, num_ue) < params.ue_indoor_percent + indoor = np.tile(indoor, (num_bs, 1)) + h_bs = 3 * np.ones(num_bs) + h_ue = 1.5 * np.ones(num_ue) + distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2) height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1)) - elevation = np.degrees(np.arctan(height_diff/distance_2D)) - - propagation_indoor = PropagationIndoor(np.random.RandomState(),params,ue_per_bs) - loss_indoor = propagation_indoor.get_loss(distance_3D = distance_3D, - distance_2D = distance_2D, - elevation = elevation, - frequency = frequency, - indoor_stations = indoor, - shadowing = False) - - - + elevation = np.degrees(np.arctan(height_diff / distance_2D)) + + propagation_indoor = PropagationIndoor( + np.random.RandomState(), params, ue_per_bs, + ) + loss_indoor = propagation_indoor.get_loss( + distance_3D, + distance_2D, + frequency, + elevation, + indoor, + False, + ) + print(loss_indoor) diff --git a/sharc/propagation/propagation_inh_office.py b/sharc/propagation/propagation_inh_office.py index 256edee9c..16c56761b 100644 --- a/sharc/propagation/propagation_inh_office.py +++ b/sharc/propagation/propagation_inh_office.py @@ -11,13 +11,13 @@ import matplotlib.pyplot as plt from cycler import cycler + class PropagationInhOffice(Propagation): """ Implements the Indoor Hotspot - Office path loss model with LOS probability according to 3GPP TR 38.900 v14.2.0. """ - def get_loss(self, *args, **kwargs) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing @@ -58,18 +58,23 @@ def get_loss(self, *args, **kwargs) -> np.array: loss = np.empty(d_2D.shape) if len(i_los[0]): - loss[i_los] = self.get_loss_los(d_3D[i_los], f[i_los], shadowing_los) + loss[i_los] = self.get_loss_los( + d_3D[i_los], f[i_los], shadowing_los, + ) if len(i_nlos[0]): - loss[i_nlos] = self.get_loss_nlos(d_3D[i_nlos], f[i_nlos], shadowing_nlos) + loss[i_nlos] = self.get_loss_nlos( + d_3D[i_nlos], f[i_nlos], shadowing_nlos, + ) return loss - - def get_loss_los(self, - distance_3D: np.array, - frequency: np.array, - shadowing_std: float): + def get_loss_los( + self, + distance_3D: np.array, + frequency: np.array, + shadowing_std: float, + ): """ Calculates path loss for the LOS (line-of-sight) case. @@ -84,20 +89,24 @@ def get_loss_los(self, array with path loss values with dimensions of distance_3D """ - loss = 32.4 + 17.3*np.log10(distance_3D) + 20*np.log10(frequency/1e3) + loss = 32.4 + 17.3 * np.log10(distance_3D) + \ + 20 * np.log10(frequency / 1e3) if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_3D.shape, + ) else: shadowing = 0 return loss + shadowing - - def get_loss_nlos(self, - distance_3D: np.array, - frequency: np.array, - shadowing_std: float): + def get_loss_nlos( + self, + distance_3D: np.array, + frequency: np.array, + shadowing_std: float, + ): """ Calculates path loss for the NLOS (non line-of-sight) case. @@ -112,19 +121,21 @@ def get_loss_nlos(self, array with path loss values with dimensions of distance_3D """ - loss_nlos = 17.3 + 38.3*np.log10(distance_3D) + 24.9*np.log10(frequency/1e3) + loss_nlos = 17.3 + 38.3 * \ + np.log10(distance_3D) + 24.9 * np.log10(frequency / 1e3) loss_los = self.get_loss_los(distance_3D, frequency, 0) loss = np.maximum(loss_los, loss_nlos) if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_3D.shape, + ) else: shadowing = 0 return loss + shadowing - def get_los_condition(self, p_los: np.array, indoor: np.array) -> np.array: """ Evaluates if user equipments are LOS (True) of NLOS (False). If UE is @@ -140,11 +151,12 @@ def get_los_condition(self, p_los: np.array, indoor: np.array) -> np.array: An array with True or False if user equipments are in LOS of NLOS condition, respectively. """ - los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los + los_condition = self.random_number_gen.random_sample( + p_los.shape, + ) < p_los los_condition = los_condition & indoor return los_condition - def get_los_probability(self, distance_2D: np.array) -> np.array: """ Returns the line-of-sight (LOS) probability @@ -161,9 +173,9 @@ def get_los_probability(self, distance_2D: np.array) -> np.array: p_los = np.ones(distance_2D.shape) id1 = np.where((distance_2D > 1.2) & (distance_2D < 6.5)) - p_los[id1] = np.exp(-(distance_2D[id1] - 1.2)/4.7) + p_los[id1] = np.exp(-(distance_2D[id1] - 1.2) / 4.7) id2 = np.where(distance_2D >= 6.5) - p_los[id2] = np.exp(-(distance_2D[id2] - 6.5)/32.6)*0.32 + p_los[id2] = np.exp(-(distance_2D[id2] - 6.5) / 32.6) * 0.32 return p_los @@ -172,18 +184,18 @@ def get_los_probability(self, distance_2D: np.array) -> np.array: ########################################################################### # Print LOS probability - distance_2D = np.linspace(0.1, 150, num=1000)[:,np.newaxis] + distance_2D = np.linspace(0.1, 150, num=1000)[:, np.newaxis] inh = PropagationInhOffice(np.random.RandomState()) los_probability = inh.get_los_probability(distance_2D) - plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') plt.loglog(distance_2D, los_probability) plt.title("InH Office - LOS probability") plt.xlabel("distance [m]") plt.ylabel("probability") - plt.xlim((0, distance_2D[-1,0])) + plt.xlim((0, distance_2D[-1, 0])) plt.ylim((0, 1.1)) plt.tight_layout() plt.grid() @@ -193,33 +205,37 @@ def get_los_probability(self, distance_2D: np.array) -> np.array: # Print path loss for InH-LOS, InH-NLOS and Free Space from sharc.propagation.propagation_free_space import PropagationFreeSpace shadowing = 0 - distance_2D = np.linspace(1, 150, num=1000)[:,np.newaxis] - frequency = 27000*np.ones(distance_2D.shape) - h_bs = 3*np.ones(len(distance_2D[:,0])) - h_ue = 1.5*np.ones(len(distance_2D[0,:])) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - indoor = np.ones(len(distance_2D[0,:]), dtype = bool) - - loss_fs = PropagationFreeSpace(np.random.RandomState()).get_loss(distance_3D=distance_3D, frequency=frequency) - - loss_inh = inh.get_loss(distance_3D = distance_3D, - distance_2D = distance_2D, - frequency = frequency, - indoor = indoor, - shadowing = shadowing) - - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + distance_2D = np.linspace(1, 150, num=1000)[:, np.newaxis] + frequency = 27000 * np.ones(distance_2D.shape) + h_bs = 3 * np.ones(len(distance_2D[:, 0])) + h_ue = 1.5 * np.ones(len(distance_2D[0, :])) + distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2) + indoor = np.ones(len(distance_2D[0, :]), dtype=bool) + + loss_fs = PropagationFreeSpace(np.random.RandomState()).get_loss( + distance_3D=distance_3D, frequency=frequency, + ) + + loss_inh = inh.get_loss( + distance_3D=distance_3D, + distance_2D=distance_2D, + frequency=frequency, + indoor=indoor, + shadowing=shadowing, + ) + + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() - ax.set_prop_cycle( cycler('color', ['r', 'b', 'g', 'y']) ) + ax.set_prop_cycle(cycler('color', ['r', 'b', 'g', 'y'])) - ax.scatter(distance_2D, loss_inh, label = "InH-Office") - ax.plot(distance_2D, loss_fs, "-b", label = "free space") + ax.scatter(distance_2D, loss_inh, label="InH-Office") + ax.plot(distance_2D, loss_fs, "-b", label="free space") ax.set_xscale('log') plt.title("InH-Office - path loss") plt.xlabel("distance [m]") plt.ylabel("path loss [dB]") - plt.xlim((0, distance_2D[-1,0])) + plt.xlim((0, distance_2D[-1, 0])) plt.legend(loc="upper left") plt.tight_layout() diff --git a/sharc/propagation/propagation_p1411.py b/sharc/propagation/propagation_p1411.py index c74d209b2..7f7980c76 100644 --- a/sharc/propagation/propagation_p1411.py +++ b/sharc/propagation/propagation_p1411.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Tue Jul 3 17:13:33 2018 @@ -9,66 +8,69 @@ import numpy as np + class PropagationP1411(Propagation): """ Implements the propagation model described in ITU-R P.1411-9, section 4.2 - + Frequency in MHz and distance in meters! """ - def __init__(self, random_number_gen: np.random.RandomState, above_clutter = True): + + def __init__(self, random_number_gen: np.random.RandomState, above_clutter=True): super().__init__(random_number_gen) - + if above_clutter: - self.los_alpha = 2.29 - self.los_beta = 28.6 - self.los_gamma = 1.96 + self.los_alpha = 2.29 + self.los_beta = 28.6 + self.los_gamma = 1.96 self.los_sigma = 3.48 - + self.nlos_alpha = 4.39 self.nlos_beta = -6.27 self.nlos_gamma = 2.30 self.nlos_sigma = 6.89 else: - self.los_alpha = 2.12 + self.los_alpha = 2.12 self.los_beta = 29.2 - self.los_gamma = 2.11 + self.los_gamma = 2.11 self.los_sigma = 5.06 - + self.nlos_alpha = 4.00 self.nlos_beta = 10.20 self.nlos_gamma = 2.36 self.nlos_sigma = 7.60 - + def get_loss(self, *args, **kwargs) -> np.array: if "distance_3D" in kwargs: d = kwargs["distance_3D"] else: d = kwargs["distance_2D"] - f = kwargs["frequency"]/1e3 - los = kwargs.pop("los",True) - shadow = kwargs.pop("shadow",True) - number_of_sectors = kwargs.pop("number_of_sectors",1) - + f = kwargs["frequency"] / 1e3 + los = kwargs.pop("los", True) + shadow = kwargs.pop("shadow", True) + number_of_sectors = kwargs.pop("number_of_sectors", 1) + if los: - alpha = self.los_alpha - beta = self.los_beta - gamma = self.los_gamma + alpha = self.los_alpha + beta = self.los_beta + gamma = self.los_gamma sigma = self.los_sigma else: alpha = self.nlos_alpha beta = self.nlos_beta gamma = self.nlos_gamma sigma = self.nlos_sigma - + if shadow: - shadow_loss = self.random_number_gen.normal(0.0,sigma,d.shape) + shadow_loss = self.random_number_gen.normal(0.0, sigma, d.shape) else: shadow_loss = 0.0 - loss = 10*alpha*np.log10(d) + 10*gamma*np.log10(f) + beta + shadow_loss + loss = 10 * alpha * np.log10(d) + 10 * \ + gamma * np.log10(f) + beta + shadow_loss if number_of_sectors > 1: loss = np.repeat(loss, number_of_sectors, 1) - return loss + return loss \ No newline at end of file diff --git a/sharc/propagation/propagation_p1411_12.py b/sharc/propagation/propagation_p1411_12.py new file mode 100644 index 000000000..ede41f66e --- /dev/null +++ b/sharc/propagation/propagation_p1411_12.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +""" +Created on wed November 05 15:29:47 2024 + +@author: joaocabeca2 +""" +import os +import sys +sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "..")) +import numpy as np +from multipledispatch import dispatch + +from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation import Propagation +from sharc.propagation.propagation_free_space import PropagationFreeSpace +from sharc.station_manager import StationManager + + +class PropagationP1411(Propagation): + """ + Implements the propagation general model described in ITU-R P.1411-12, section 4.1.1 + + Frequency in MHz and distance in meters! + """ + + def __init__(self, + random_number_gen: np.random.RandomState, + environment: str, + ): + super().__init__(random_number_gen) + self.environment = environment + + if self.environment.upper() == 'URBAN': + self.alfa = 4.0 + self.beta = 10.2 + self.gamma = 2.36 + self.sigma = 7.60 + elif self.environment.upper() == 'SUBURBAN': + self.alfa = 5.06 + self.beta = -4.68 + self.gamma = 2.02 + self.sigma = 9.33 + elif self.environment.upper() == 'RESIDENTIAL': + self.alfa = 3.01 + self.beta = 18.8 + self.gamma = 2.07 + self.sigma = 3.07 + + + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMa get_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()): + distances_2d, distances_3d, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distances_2d = station_a.get_distance_to(station_b) + distances_3d = station_a.get_3d_distance_to(station_b) + + median_basic_loss = self.calculate_median_basic_loss( + distances_3d, + frequency * np.ones(distances_2d.shape), + self.random_number_gen + ) + + self.alfa = self.alfa * np.ones(distances_2d.shape) + self.beta = self.beta * np.ones(distances_2d.shape) + self.gamma = self.gamma * np.ones(distances_2d.shape) + self.sigma = self.sigma * np.ones(distances_2d.shape) + + return median_basic_loss + + def calculate_median_basic_loss(self, distance_3D: np.array, + frequency: np.array, + random_number_gen: np.random.Generator + ) -> np.array: + """ + This site-general model is applicable to situations where both the transmitting and receiving stations + are located below-rooftop, regardless of their antenna heights. + + Parameters + ---------- + distance_3D : np.array + 3D direct distance between the transmitting and receiving stations (in meters). + frequency : np.array + Operating frequency in GHz. + + Returns + ------- + np.array + Median basic transmission loss in dB. + """ + median_loss = (10 * self.alfa * np.log10(distance_3D)) + self.beta + (10 * self.gamma * np.log10(frequency)) + # Add zero-mean Gaussian random variable for shadowing + shadowing = random_number_gen.normal(0, self.sigma, distance_3D.shape) + + return median_loss + shadowing + + + def calculate_excess_loss(self, lfs: np.array, median_basic_loss: np.array, distance_3D: np.array) -> np.array: + """ + Calculates the excess basic transmission loss for NLoS scenarios. + + This method computes the excess loss with respect to the free-space basic transmission loss + using a Monte Carlo approach. It uses a random variable A, which is normally distributed + with mean ฮผ and standard deviation ฯƒ, to account for the variability in the signal propagation + environment. + + Parameters + ---------- + lfs : np.array + Free-space basic transmission loss, LFS, in dB. + median_basic_loss : np.array + Median basic transmission loss, Lb(d, f), in dB. + distance_3D : np.array + 3D direct distance between the transmitting and receiving stations (in meters). + + Returns + ------- + np.array + Excess basic transmission loss in dB. + """ + # Calculate ฮผ as the difference between the median basic loss and free-space loss + u = median_basic_loss - lfs + + # Generate the random variable A using a normal distribution with mean ฮผ and standard deviation ฯƒ + a = self.random_number_gen.normal(u, self.sigma, distance_3D.shape) + + # Compute the excess loss using the specified formula + return 10 * np.log10(10**(0.1 * a) + 1) + + +if __name__ == '__main__': + + import matplotlib.pyplot as plt + from cycler import cycler + + # Configuraรงรฃo de parรขmetros + num_ue = 1 + num_bs = 1000 + h_bs = 6 * np.ones(num_bs) + h_ue = 1.5 * np.ones(num_ue) + + # Configuraรงรฃo da distรขncia para o cenรกrio + distance_2D = np.repeat(np.linspace(5, 660, num=num_bs)[np.newaxis, :], num_ue, axis=0) + frequency = 7 * np.ones(num_bs) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2) + + # Gerador de nรบmeros aleatรณrios + random_number_gen = np.random.RandomState(101) + + p1411 = PropagationP1411(random_number_gen, 'URBAN') + free_space_prop = PropagationFreeSpace(random_number_gen) + + free_space_loss = free_space_prop.get_free_space_loss(frequency * 1000, distance_3D) + median_basic_loss = p1411.calculate_median_basic_loss(distance_3D, frequency, random_number_gen) + excess_loss = p1411.calculate_excess_loss(free_space_loss, median_basic_loss, distance_3D) + + # Plotando os grรกficos + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') + ax = fig.gca() + ax.set_prop_cycle(cycler('color', ['r', 'g', 'b'])) + + ax.semilogx(distance_2D[0, :], median_basic_loss[0, :], label="Median basic Loss") + ax.semilogx(distance_2D[0, :], excess_loss[0, :], label="Excess Loss") + ax.semilogx(distance_2D[0, :], free_space_loss[0, :], label="Free space Loss") + + plt.title(p1411.environment) + plt.xlabel("Distance [m]") + plt.ylabel("Path Loss [dB]") + plt.xlim((0, distance_2D[0, -1])) + plt.legend(loc="upper left") + plt.tight_layout() + plt.grid() + + plt.show() \ No newline at end of file diff --git a/sharc/propagation/propagation_p619.py b/sharc/propagation/propagation_p619.py index 9d5ed3b8d..ace9c30ef 100644 --- a/sharc/propagation/propagation_p619.py +++ b/sharc/propagation/propagation_p619.py @@ -5,16 +5,22 @@ @author: andre barreto """ -from sharc.propagation.propagation import Propagation -import math +import os +import csv +from scipy.interpolate import interp1d import numpy as np +from multipledispatch import dispatch +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters +from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss from sharc.propagation.atmosphere import ReferenceAtmosphere from sharc.support.enumerations import StationType from sharc.propagation.scintillation import Scintillation +from sharc.parameters.constants import EARTH_RADIUS class PropagationP619(Propagation): @@ -25,23 +31,79 @@ class PropagationP619(Propagation): get_loss: Calculates path loss for earth-space link """ - def __init__(self, random_number_gen: np.random.RandomState): + def __init__( + self, + random_number_gen: np.random.RandomState, + space_station_alt_m: float, + earth_station_alt_m: float, + earth_station_lat_deg: float, + earth_station_long_diff_deg: float, + season: str, + ): + """Implements the earth-to-space channel model from ITU-R P.619 + + Parameters + ---------- + random_number_gen : np.random.RandomState + randon number generator + space_station_alt_m : float + The space station altitude in meters + earth_station_alt_m : float + The Earth station altitude in meters + earth_station_lat_deg : float + The Earth station latitude in degrees + earth_station_long_diff_deg : float + The difference between longitudes of earth-station and and space-sation. + (positive if space-station is to the East of earth-station) + season: str + Possible values are "SUMMER" or "WINTER". + """ + super().__init__(random_number_gen) + self.is_earth_space_model = True self.clutter = PropagationClutterLoss(self.random_number_gen) self.free_space = PropagationFreeSpace(self.random_number_gen) - self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen) + self.building_entry = PropagationBuildingEntryLoss( + self.random_number_gen, + ) self.scintillation = Scintillation(self.random_number_gen) self.atmosphere = ReferenceAtmosphere() - self.depolarization_loss = 0 # 1.5 - self.polarization_mismatch_loss = 0 # 3 + self.depolarization_loss = 0 + self.polarization_mismatch_loss = 0 self.elevation_has_atmospheric_loss = [] self.freq_has_atmospheric_loss = [] self.surf_water_dens_has_atmospheric_loss = [] self.atmospheric_loss = [] self.elevation_delta = .01 + self.space_station_alt_m = space_station_alt_m + self.earth_station_alt_m = earth_station_alt_m + self.earth_station_lat_deg = earth_station_lat_deg + self.earth_station_long_diff_deg = earth_station_long_diff_deg + + if season.upper() not in ["SUMMER", "WINTER"]: + raise ValueError( + f"PropagationP619: Invalid value for parameter season - {season}. Possible values are \"SUMMER\", \"WINTER\".", + ) + self.season = season + # Load city name based on latitude + self.lookup_table = False + self.city_name = self._get_city_name_by_latitude() + if self.city_name != "Unknown": + self.lookup_table = True + + def _get_city_name_by_latitude(self): + localidades_file = os.path.join( + os.path.dirname(__file__), 'Dataset/localidades.csv', + ) + with open(localidades_file, mode='r') as file: + reader = csv.DictReader(file) + for row in reader: + if abs(float(row['Latitude']) - self.earth_station_lat_deg) < 1e-5: + return row['Cidade'] + return 'Unknown' def _get_atmospheric_gasses_loss(self, *args, **kwargs) -> float: """ @@ -50,85 +112,122 @@ def _get_atmospheric_gasses_loss(self, *args, **kwargs) -> float: Parameters ---------- frequency_MHz (float) : center frequencies [MHz] - apparent_elevation (float) : apparent elevation angle (degrees) - sat_params (ParametersFss) : parameters of satellite system - + apparent_elevation (float) : apparent elevation angle at the Earth-based station (degrees) Returns ------- path_loss (float): scalar with atmospheric loss """ frequency_MHz = kwargs["frequency_MHz"] apparent_elevation = kwargs["apparent_elevation"] - sat_params = kwargs["sat_params"] - - surf_water_vapour_density = kwargs.pop("surf_water_vapour_density", False) - - earth_radius_km = sat_params.EARTH_RADIUS/1000 - a_acc = 0. # accumulated attenuation (in dB) - h = sat_params.imt_altitude/1000 # ray altitude in km - beta = (90-abs(apparent_elevation)) * np.pi / 180. # incidence angle + lookupTable = kwargs.pop("lookupTable", True) + surf_water_vapour_density = kwargs.pop( + "surf_water_vapour_density", False, + ) + + if lookupTable and self.city_name != 'Unknown': + # Define the path to the CSV file + output_dir = os.path.join(os.path.dirname(__file__), 'Dataset') + csv_file = os.path.join( + output_dir, f'{self.city_name}_{int(frequency_MHz)}_{int(self.earth_station_alt_m)}m.csv', + ) + if os.path.exists(csv_file): + elevations = [] + losses = [] + with open(csv_file, mode='r') as file: + reader = csv.reader(file) + next(reader) # Skip header + for row in reader: + elevations.append(float(row[0])) + losses.append(float(row[1])) + interpolation_function = interp1d( + elevations, losses, kind='linear', fill_value='extrapolate', + ) + return interpolation_function(apparent_elevation) + + earth_radius_km = EARTH_RADIUS / 1000 + a_acc = 0. # accumulated attenuation (in dB) + h = self.earth_station_alt_m / 1000 # ray altitude in km + beta = (90 - abs(apparent_elevation)) * np.pi / 180. # incidence angle if not surf_water_vapour_density: - dummy, dummy, surf_water_vapour_density = \ - self.atmosphere.get_reference_atmosphere_p835(sat_params.imt_lat_deg, - 0, season = sat_params.season) + _, _, surf_water_vapour_density = self.atmosphere.get_reference_atmosphere_p835( + self.earth_station_lat_deg, 0, season=self.season, + ) - # first, check if atmospheric loss was already calculated if len(self.elevation_has_atmospheric_loss): - elevation_diff = np.abs(apparent_elevation - np.array(self.elevation_has_atmospheric_loss)) + elevation_diff = np.abs( + apparent_elevation - np.array(self.elevation_has_atmospheric_loss), + ) + elevation_diff = np.abs( + apparent_elevation - np.array(self.elevation_has_atmospheric_loss), + ) indices = np.where(elevation_diff <= self.elevation_delta) if indices[0].size: index = np.argmin(elevation_diff) if self.freq_has_atmospheric_loss[index] == frequency_MHz and \ self.surf_water_dens_has_atmospheric_loss[index] == surf_water_vapour_density: - loss = self.atmospheric_loss[index] - return loss + return self.atmospheric_loss[index] - rho_s = surf_water_vapour_density * np.exp(h/2) # water vapour density at h + rho_s = surf_water_vapour_density * \ + np.exp(h / 2) # water vapour density at h if apparent_elevation < 0: - # get temperature (t), dry-air pressure (p), water-vapour pressure (e), - # refractive index (n) and specific attenuation (gamma) - t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz) - delta = .0001 + 0.01 * max(h, 0) # layer thickness - r = earth_radius_km + h - delta # radius of lower edge + t, p, e, n, gamma = self.atmosphere.get_atmospheric_params( + h, rho_s, frequency_MHz, + ) + delta = .0001 + 0.01 * max(h, 0) # layer thickness + r = earth_radius_km + h - delta # radius of lower edge while True: m = (r + delta) * np.sin(beta) - r if m >= 0: - dh = 2 * np.sqrt(2*r*(delta-m)+delta**2-m**2) # horizontal path + dh = 2 * np.sqrt( + 2 * r * (delta - m) + + delta ** 2 - m ** 2, + ) # horizontal path a_acc += dh * gamma break - ds = (r+delta)*np.cos(beta)-np.sqrt((r+delta)**2 * np.cos(beta)**2 - - (2*r*delta + delta**2)) # slope distance - a_acc += ds*gamma - alpha = np.arcsin((r+delta)/r * np.sin(beta)) # angle to vertical + ds = (r + delta) * np.cos(beta) - np.sqrt((r + delta) ** + 2 * np.cos(beta) ** 2 - (2 * r * delta + delta ** 2)) + a_acc += ds * gamma + # angle to vertical + alpha = np.arcsin((r + delta) / r * np.sin(beta)) h -= delta r -= delta - t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz) + t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params( + h, rho_s, frequency_MHz, + ) delta = 0.0001 + 0.01 * max(h, 0) - beta = np.arcsin(n/n_new * np.sin(alpha)) + beta = np.arcsin(n / n_new * np.sin(alpha)) n = n_new - t, p, e, n, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, frequency_MHz) + t, p, e, n, gamma = self.atmosphere.get_atmospheric_params( + h, rho_s, frequency_MHz, + ) delta = .0001 + .01 * max(h, 0) r = earth_radius_km + h while True: - ds = np.sqrt(r**2 * np.cos(beta)**2 + 2*r*delta + delta**2) - r * np.cos(beta) + ds = np.sqrt( + r ** 2 * np.cos(beta) ** 2 + 2 * r * + delta + delta ** 2, + ) - r * np.cos(beta) a_acc += ds * gamma - alpha = np.arcsin(r/(r+delta) * np.sin(beta)) + alpha = np.arcsin(r / (r + delta) * np.sin(beta)) h += delta if h >= 100: break r += delta - t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params(h, rho_s, - frequency_MHz) - beta = np.arcsin(n/n_new * np.sin(alpha)) + t, p, e, n_new, gamma = self.atmosphere.get_atmospheric_params( + h, rho_s, frequency_MHz, + ) + beta = np.arcsin(n / n_new * np.sin(alpha)) n = n_new self.atmospheric_loss.append(a_acc) self.elevation_has_atmospheric_loss.append(apparent_elevation) self.freq_has_atmospheric_loss.append(frequency_MHz) - self.surf_water_dens_has_atmospheric_loss.append(surf_water_vapour_density) + self.surf_water_dens_has_atmospheric_loss.append( + surf_water_vapour_density, + ) return a_acc @@ -141,7 +240,6 @@ def _get_beam_spreading_att(elevation, altitude, earth_to_space) -> np.array: ---------- elevation (np.array) : free space elevation (degrees) altitude (float) : altitude of earth station (m) - sat_params (ParametersFss) : parameters of satellite system Returns ------- @@ -151,173 +249,310 @@ def _get_beam_spreading_att(elevation, altitude, earth_to_space) -> np.array: # calculate scintillation intensity, based on ITU-R P.618-12 altitude_km = altitude / 1000 - numerator = .5411 + .07446 * elevation + altitude_km * (.06272 + .0276 * elevation) \ - + altitude_km ** 2 * .008288 - denominator = (1.728 + .5411 * elevation + .03723 * elevation **2 + - altitude_km * (.1815 + .06272 * elevation + .0138 * elevation ** 2) + - (altitude_km ** 2) * (.01727 + .008288 * elevation))**2 + numerator = .5411 + .07446 * elevation + altitude_km * \ + (.06272 + .0276 * elevation) + altitude_km ** 2 * .008288 + denominator = ( + 1.728 + .5411 * elevation + .03723 * elevation ** 2 + altitude_km * ( + .1815 + .06272 * + elevation + .0138 * elevation ** 2 + ) + (altitude_km ** 2) * (.01727 + .008288 * elevation) + ) ** 2 - attenuation = 10 * np.log10(1 - numerator/denominator) + attenuation = 10 * np.log10(1 - numerator / denominator) if earth_to_space: attenuation = -attenuation return attenuation + @classmethod + def apparent_elevation_angle(cls, elevation_deg: np.array, space_station_alt_m: float) -> np.array: + """Calculate apparent elevation angle according to ITU-R P619, Attachment B + + Parameters + ---------- + elevation_deg : np.array + free-space elevation angle + space_station_alt_m : float + space-station altitude + + Returns + ------- + np.array + apparent elevation angle + """ + elev_angles_rad = np.deg2rad(elevation_deg) + tau_fs1 = 1.728 + 0.5411 * elev_angles_rad + 0.03723 * elev_angles_rad**2 + tau_fs2 = 0.1815 + 0.06272 * elev_angles_rad + 0.01380 * elev_angles_rad**2 + tau_fs3 = 0.01727 + 0.008288 * elev_angles_rad + + # change in elevation angle due to refraction + tau_fs_deg = 1 / ( + tau_fs1 + space_station_alt_m * tau_fs2 + + space_station_alt_m**2 * tau_fs3 + ) + tau_fs = tau_fs_deg / 180. * np.pi + + return np.degrees(elev_angles_rad + tau_fs) + + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper for the get_loss function that satisfies the ABS class interface + + Calculates P.619 path loss between station_a and station_b stations. + + Parameters + ---------- + station_a : StationManager + StationManager container representing station_a + station_b : StationManager + StationManager container representing station_b + params : Parameters + Simulation parameters needed for the propagation class. - def get_loss(self, *args, **kwargs) -> np.array: + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + distance = station_a.get_3d_distance_to(station_b) + frequency = frequency * np.ones(distance.shape) + indoor_stations = np.tile( + station_b.indoor, (station_a.num_stations, 1), + ) + + # Elevation angles seen from the station on Earth. + elevation_angles = {} + if station_a.is_space_station: + elevation_angles["free_space"] = np.transpose( + station_b.get_elevation(station_a), + ) + earth_station_antenna_gain = np.transpose(station_b_gains) + elevation_angles["apparent"] = self.apparent_elevation_angle( + elevation_angles["free_space"], + station_a.height, + ) + elif station_b.is_space_station: + elevation_angles["free_space"] = station_a.get_elevation(station_b) + earth_station_antenna_gain = station_a_gains + elevation_angles["apparent"] = self.apparent_elevation_angle( + elevation_angles["free_space"], + station_b.height, + ) + else: + raise ValueError( + "PropagationP619: At least one station must be an space station", + ) + + # Determine the Earth-space path direction and if the interferer is single or multiple-entry. + # The get_loss interface won't tell us who is the interferer, so we need to get it from the parameters. + is_intra_imt = True if station_a.is_imt_station( + ) and station_b.is_imt_station() else False + if is_intra_imt: + # Intra-IMT + if params.general.imt_link == "UPLINK": + is_earth_to_space_link = True + else: + is_earth_to_space_link = False + # Intra-IMT is always multiple-entry interference + is_single_entry_interf = False + else: + # System-IMT + imt_station, sys_station = ( + station_a, station_b, + ) if station_a.is_imt_station() else (station_b, station_a) + if params.imt.interfered_with: + is_earth_to_space_link = True if imt_station.is_space_station else False + if sys_station.num_stations > 1: + is_single_entry_interf = False + else: + is_single_entry_interf = True + else: + is_earth_to_space_link = False if imt_station.is_space_station else True + is_single_entry_interf = False + + loss = self.get_loss( + distance, + frequency, + indoor_stations, + elevation_angles, + is_earth_to_space_link, + earth_station_antenna_gain, + is_single_entry_interf, + ) + + return loss + + @dispatch(np.ndarray, np.ndarray, np.ndarray, dict, bool, np.ndarray, bool) + def get_loss( + self, + distance: np.array, + frequency: np.array, + indoor_stations: np.array, + elevation: dict, + earth_to_space: bool, + earth_station_antenna_gain: np.array, + single_entry: bool, + ) -> np.array: """ Calculates path loss for earth-space link Parameters ---------- - distance_3D (np.array) : distances between stations [m] + distance (np.array) : 3D distances between stations [m] frequency (np.array) : center frequencies [MHz] indoor_stations (np.array) : array indicating stations that are indoor - elevation (dict) : elevation["apparent"](array): apparent elevation angles (degrees) - elevation["free_space"](array): free-space elevation angles (degrees) - sat_params (ParametersFss) : parameters of satellite system - single_entry (bool): True for single-entry interference, False for multiple-entry (default = False) - number_of_sectors (int): number of sectors in a node (default = 1) + elevation (dict) : dict containing free-space elevation angles (degrees), and aparent elevation angles + earth_to_space (bool): whether the ray direction is earth-to-space. Affects beam spreadding Attenuation. + single_entry (bool): True for single-entry interference, False for + multiple-entry (default = False) + earth_station_antenna_gain (np.array): Earth station atenna gain array. Returns ------- array with path loss values with dimensions of distance_3D """ - d = kwargs["distance_3D"] - f = kwargs["frequency"] - indoor_stations = kwargs["indoor_stations"] - elevation = kwargs["elevation"] - sat_params = kwargs["sat_params"] - earth_to_space = kwargs["earth_to_space"] - earth_station_antenna_gain = kwargs["earth_station_antenna_gain"] - single_entry = kwargs.pop("single_entry", False) - number_of_sectors = kwargs["number_of_sectors"] - - free_space_loss = self.free_space.get_loss(distance_3D=d, frequency=f) - - freq_set = np.unique(f) + free_space_loss = self.free_space.get_free_space_loss( + frequency=frequency, distance=distance, + ) + + freq_set = np.unique(frequency) if len(freq_set) > 1: error_message = "different frequencies not supported in P619" raise ValueError(error_message) - atmospheric_gasses_loss = self._get_atmospheric_gasses_loss(frequency_MHz=freq_set, - apparent_elevation=np.mean(elevation["apparent"]), - sat_params=sat_params) - beam_spreading_attenuation = self._get_beam_spreading_att(elevation["free_space"], - sat_params.imt_altitude, - earth_to_space) + atmospheric_gasses_loss = self._get_atmospheric_gasses_loss( + frequency_MHz=freq_set, + apparent_elevation=np.mean(elevation["apparent"]), + ) + beam_spreading_attenuation = self._get_beam_spreading_att( + elevation["free_space"], + self.earth_station_alt_m, + earth_to_space, + ) diffraction_loss = 0 if single_entry: - elevation_sectors = np.repeat(elevation["free_space"], number_of_sectors) - tropo_scintillation_loss = \ - self.scintillation.get_tropospheric_attenuation(elevation = elevation_sectors, - antenna_gain_dB = earth_station_antenna_gain, - frequency_MHz = freq_set, - sat_params = sat_params) - - loss = (free_space_loss + self.depolarization_loss + - atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss) - loss = np.repeat(loss, number_of_sectors, 1) + tropo_scintillation_loss + tropo_scintillation_loss = self.scintillation.get_tropospheric_attenuation( + elevation=elevation["free_space"], + antenna_gain_dB=earth_station_antenna_gain, + frequency_MHz=freq_set, + earth_station_alt_m=self.earth_station_alt_m, + earth_station_lat_deg=self.earth_station_lat_deg, + season=self.season, + ) + + loss = \ + free_space_loss + self.depolarization_loss + atmospheric_gasses_loss + beam_spreading_attenuation + \ + diffraction_loss + tropo_scintillation_loss else: - clutter_loss = self.clutter.get_loss(frequency=f, distance=d, - elevation=elevation["free_space"], - station_type=StationType.FSS_SS) - building_loss = self.building_entry.get_loss(f, elevation["apparent"]) * indoor_stations - - loss = (free_space_loss + clutter_loss + building_loss + self.polarization_mismatch_loss + - atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss) - loss = np.repeat(loss, number_of_sectors, 1) + clutter_loss = \ + self.clutter.get_loss( + frequency=frequency, + distance=distance, + elevation=elevation["free_space"], + station_type=StationType.FSS_SS, + ) + building_loss = self.building_entry.get_loss( + frequency, elevation["apparent"], + ) * indoor_stations + + loss = ( + free_space_loss + clutter_loss + building_loss + self.polarization_mismatch_loss + + atmospheric_gasses_loss + beam_spreading_attenuation + diffraction_loss + ) return loss + if __name__ == '__main__': - from sharc.parameters.parameters import Parameters + + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt - import os - params = Parameters() + # params = Parameters() - propagation_path = os.getcwd() - sharc_path = os.path.dirname(propagation_path) - param_file = os.path.join(sharc_path, "parameters", "parameters.ini") + # propagation_path = os.getcwd() + # sharc_path = os.path.dirname(propagation_path) + # param_file = os.path.join(sharc_path, "parameters", "parameters.ini") - params.set_file_name(param_file) - params.read_params() + # params.set_file_name(param_file) + # params.read_params() - sat_params = params.fss_ss + # sat_params = params.fss_ss + + space_station_alt_m = 20000.0 + earth_station_alt_m = 1000.0 + earth_station_lat_deg = -15.7801 + earth_station_long_diff_deg = 0.0 + season = "SUMMER" random_number_gen = np.random.RandomState(101) - propagation = PropagationP619(random_number_gen) + propagation = PropagationP619( + random_number_gen=random_number_gen, + space_station_alt_m=space_station_alt_m, + earth_station_alt_m=earth_station_alt_m, + earth_station_lat_deg=earth_station_lat_deg, + earth_station_long_diff_deg=earth_station_long_diff_deg, + season=season, + ) ########################## # Plot atmospheric loss # compare with benchmark from ITU-R P-619 Fig. 3 - frequency_MHz = 30000. - sat_params.imt_altitude = 1000 + frequency_MHz = 2680. + earth_station_alt_m = 1000 - apparent_elevation = range(-1, 90, 2) + apparent_elevation = np.linspace(0, 90, 91) - loss_2_5 = np.zeros(len(apparent_elevation)) - loss_12_5 = np.zeros(len(apparent_elevation)) + loss_false = np.zeros(len(apparent_elevation)) + loss_true = np.zeros(len(apparent_elevation)) print("Plotting atmospheric loss:") for index in range(len(apparent_elevation)): - print("\tApparent Elevation: {} degrees".format(apparent_elevation[index])) - - surf_water_vapour_density = 2.5 - loss_2_5[index] = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz, - apparent_elevation=apparent_elevation[index], - sat_params=sat_params, - surf_water_vapour_density=surf_water_vapour_density) - surf_water_vapour_density = 12.5 - loss_12_5[index] = propagation._get_atmospheric_gasses_loss(frequency_MHz=frequency_MHz, - apparent_elevation=apparent_elevation[index], - sat_params=sat_params, - surf_water_vapour_density=surf_water_vapour_density) + print( + "\tApparent Elevation: {} degrees".format( + apparent_elevation[index], + ), + ) + + surf_water_vapour_density = 14.121645412892681 + table = False + loss_false[index] = propagation._get_atmospheric_gasses_loss( + frequency_MHz=frequency_MHz, + apparent_elevation=apparent_elevation[index], + surf_water_vapour_density=surf_water_vapour_density, + lookupTable=table, + ) + table = True + loss_true[index] = propagation._get_atmospheric_gasses_loss( + frequency_MHz=frequency_MHz, + apparent_elevation=apparent_elevation[index], + surf_water_vapour_density=surf_water_vapour_density, + lookupTable=table, + ) plt.figure() - plt.semilogy(apparent_elevation, loss_2_5, label='2.5 g/m^3') - plt.semilogy(apparent_elevation, loss_12_5, label='12.5 g/m^3') + plt.semilogy(apparent_elevation, loss_false, label='No Table') + plt.semilogy(apparent_elevation, loss_true, label='With Table') plt.grid(True) - - plt.xlabel("apparent elevation (deg)") plt.ylabel("Loss (dB)") plt.title("Atmospheric Gasses Attenuation") plt.legend() - altitude_vec = np.arange(0, 6.1, .5) * 1000 - elevation_vec = np.array([0, .5, 1, 2, 3, 5]) - attenuation = np.empty([len(altitude_vec), len(elevation_vec)]) - - ################################# - # Plot beam spreading attenuation - # compare with benchmark from ITU-R P-619 Fig. 7 - - earth_to_space = False - print("Plotting beam spreading attenuation:") - - plt.figure() - for index in range(len(altitude_vec)): - attenuation[index, :] = propagation._get_beam_spreading_att(elevation_vec, - altitude_vec[index], - earth_to_space) - - handles = plt.plot(altitude_vec / 1000, np.abs(attenuation)) - plt.xlabel("altitude (km)") - plt.ylabel("Attenuation (dB)") - plt.title("Beam Spreading Attenuation") - - for line_handle, elevation in zip(handles, elevation_vec): - line_handle.set_label("{}deg".format(elevation)) - - plt.legend(title="Elevation") - - plt.grid(True) - - - plt.show() + # Save the figures instead of showing them + plt.savefig("atmospheric_gasses_attenuation.png") diff --git a/sharc/propagation/propagation_sat_simple.py b/sharc/propagation/propagation_sat_simple.py index 30c9535cb..c4706fc7d 100644 --- a/sharc/propagation/propagation_sat_simple.py +++ b/sharc/propagation/propagation_sat_simple.py @@ -4,52 +4,140 @@ @author: edgar """ +import numpy as np +from multipledispatch import dispatch from sharc.propagation.propagation import Propagation +from sharc.propagation.propagation_p619 import PropagationP619 from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss from sharc.propagation.propagation_building_entry_loss import PropagationBuildingEntryLoss from sharc.support.enumerations import StationType +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters -import numpy as np class PropagationSatSimple(Propagation): """ Implements the simplified satellite propagation model """ + # pylint: disable=function-redefined + # pylint: disable=arguments-renamed - def __init__(self, random_number_gen: np.random.RandomState): + def __init__(self, random_number_gen: np.random.RandomState, enable_clutter_loss=True): super().__init__(random_number_gen) + self.enable_clutter_loss = enable_clutter_loss self.clutter = PropagationClutterLoss(random_number_gen) self.free_space = PropagationFreeSpace(random_number_gen) - self.building_entry = PropagationBuildingEntryLoss(self.random_number_gen) + self.building_entry = PropagationBuildingEntryLoss( + self.random_number_gen, + ) self.atmospheric_loss = 0.75 + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMi calc_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + distance_3d = station_a.get_3d_distance_to(station_b) + frequency = frequency * np.ones(distance_3d.shape) + indoor_stations = np.tile( + station_b.indoor, (station_a.num_stations, 1), + ) + + # Elevation angles seen from the station on Earth. + elevation_angles = {} + if station_a.is_space_station: + elevation_angles["free_space"] = np.transpose( + station_b.get_elevation(station_a), + ) + elevation_angles["apparent"] = \ + PropagationP619.apparent_elevation_angle( + elevation_angles["free_space"], + station_a.height,) + elif station_b.is_space_station: + elevation_angles["free_space"] = station_a.get_elevation(station_b) + elevation_angles["apparent"] = \ + PropagationP619.apparent_elevation_angle( + elevation_angles["free_space"], + station_b.height,) + else: + raise ValueError( + "PropagationP619: At least one station must be an space station", + ) + + return self.get_loss(distance_3d, frequency, indoor_stations, elevation_angles) + + @dispatch(np.ndarray, np.ndarray, np.ndarray, dict) + def get_loss( + self, + distance: np.array, + frequency: np.array, + indoor_stations: np.array, + elevation: dict, + ) -> np.array: + """Calculates the clutter loss. - def get_loss(self, *args, **kwargs) -> np.array: - d = kwargs["distance_3D"] - f = kwargs["frequency"] - indoor_stations = kwargs["indoor_stations"] - elevation = kwargs["elevation"] - number_of_sectors = kwargs.pop("number_of_sectors", 1) - enable_clutter_loss = kwargs.pop("enable_clutter_loss", True) + Parameters + ---------- + distance : np.array + Distance between the stations + frequency : np.array + Array of frequenciews + indoor_stations : np.array + Bool array indicating if the terrestrial station is indoor or not. + elevation : np.array + Array with elevation angles w.r.t terrestrial station - free_space_loss = self.free_space.get_loss(distance_3D = d, - frequency = f) + Returns + ------- + np.array + Array of clutter losses with the same shape as distance + """ - if enable_clutter_loss: - clutter_loss = np.maximum(0, self.clutter.get_loss(frequency = f, - distance = d, - elevation = elevation["free_space"], - station_type = StationType.FSS_SS)) + free_space_loss = self.free_space.get_free_space_loss( + distance=distance, frequency=frequency, + ) + + if self.enable_clutter_loss: + clutter_loss = np.maximum( + 0, self.clutter.get_loss( + frequency=frequency, + distance=distance, + elevation=elevation["free_space"], + station_type=StationType.FSS_SS, + ), + ) else: clutter_loss = 0 - building_loss = self.building_entry.get_loss(f, elevation["apparent"]) * indoor_stations + building_loss = self.building_entry.get_loss( + frequency, elevation["apparent"], + ) * indoor_stations loss = free_space_loss + clutter_loss + building_loss + self.atmospheric_loss - if number_of_sectors > 1: - loss = np.repeat(loss, number_of_sectors, 1) - return loss diff --git a/sharc/propagation/propagation_ter_simple.py b/sharc/propagation/propagation_ter_simple.py index d10de6b1d..5d4321f98 100644 --- a/sharc/propagation/propagation_ter_simple.py +++ b/sharc/propagation/propagation_ter_simple.py @@ -4,15 +4,20 @@ @author: edgar """ +import numpy as np +from multipledispatch import dispatch from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters from sharc.propagation.propagation_free_space import PropagationFreeSpace from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss from sharc.support.enumerations import StationType -import numpy as np class PropagationTerSimple(Propagation): + # pylint: disable=function-redefined + # pylint: disable=arguments-renamed """ Implements the simplified terrestrial propagation model, which is the basic free space and additional clutter losses @@ -24,30 +29,92 @@ def __init__(self, random_number_gen: np.random.RandomState): self.free_space = PropagationFreeSpace(np.random.RandomState(101)) self.building_loss = 20 - - def get_loss(self, *args, **kwargs) -> np.array: - if "distance_2D" in kwargs: - d = kwargs["distance_2D"] - else: - d = kwargs["distance_3D"] - - f = kwargs["frequency"] - p = kwargs.pop("loc_percentage", "RANDOM") - indoor_stations = kwargs["indoor_stations"] - number_of_sectors = kwargs.pop("number_of_sectors",1) - - free_space_loss = self.free_space.get_loss(distance_2D=d, - frequency=f) - - clutter_loss = self.clutter.get_loss(frequency=f, - distance=d, - loc_percentage=p, - station_type=StationType.FSS_ES) - - building_loss = self.building_loss*indoor_stations + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the get_loss method to fit the Propagation ABC class interface + Calculates the loss between station_a and station_b + + Parameters + ---------- + params : Parameters + Simulation parameters needed for the propagation class + frequency: float + Center frequency + station_a : StationManager + StationManager container representing the system station + station_b : StationManager + StationManager container representing the IMT station + station_a_gains: np.ndarray defaults to None + Not used + station_b_gains: np.ndarray defaults to None + Not used + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + distance = station_a.get_3d_distance_to(station_b) + frequency_array = frequency * np.ones(distance.shape) + indoor_stations = np.tile( + station_b.indoor, (station_a.num_stations, 1), + ) + + return self.get_loss(distance, frequency_array, indoor_stations, -1.0) + + # pylint: disable=arguments-differ + @dispatch(np.ndarray, np.ndarray, np.ndarray, float) + def get_loss( + self, distance: np.ndarray, frequency: np.ndarray, indoor_stations: np.ndarray, + loc_percentage: float, + ) -> np.array: + """Calculates loss with a simple terrestrial model: + * Building loss is set statically in class construction + * Clutter loss is calculated using P.2108 standard + * Free-space loss + + Parameters + ---------- + distance : np.ndarray + distances array between stations + frequency : np.ndarray + frequency + indoor_stations: np.ndarray + array of bool indicating if station n is indoor + loc_percentage : str, optional + Percentage locations range [0, 1[. If a negative number is given + a random percentage is used. + + Returns + ------- + np.array + array of losses with distance dimentions + """ + free_space_loss = self.free_space.get_free_space_loss( + frequency, distance, + ) + if loc_percentage < 0: + loc_percentage = "RANDOM" + + clutter_loss = self.clutter.get_loss( + frequency=frequency, + distance=distance, + loc_percentage=loc_percentage, + station_type=StationType.FSS_ES, + ) + + building_loss = self.building_loss * indoor_stations loss = free_space_loss + building_loss + clutter_loss - loss = np.repeat(loss, number_of_sectors, 1) return loss @@ -60,25 +127,24 @@ def get_loss(self, *args, **kwargs) -> np.array: # Print path loss for TerrestrialSimple and Free Space d = np.linspace(10, 10000, num=10000) - freq = 27000*np.ones(d.shape) - indoor_stations = np.zeros(d.shape, dtype = bool) + freq = 27000 * np.ones(d.shape) + indoor_stations = np.zeros(d.shape, dtype=bool) loc_percentage = 0.5 free_space = PropagationFreeSpace(np.random.RandomState(101)) ter_simple = PropagationTerSimple(np.random.RandomState(101)) - loss_ter = ter_simple.get_loss(distance_2D = d, - frequency = freq, - loc_percentage = loc_percentage, - indoor_stations = indoor_stations) + loss_ter = ter_simple.get_loss(d, freq, indoor_stations, loc_percentage) - loss_fs = free_space.get_loss(distance_2D = d, - frequency = freq) + loss_fs = free_space.get_free_space_loss(freq, d) - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') - plt.semilogx(np.squeeze(d), np.squeeze(loss_fs), label = "free space") - plt.semilogx(np.squeeze(d), np.squeeze(loss_ter), label = "free space + clutter loss") + plt.semilogx(np.squeeze(d), np.squeeze(loss_fs), label="free space") + plt.semilogx( + np.squeeze(d), np.squeeze(loss_ter), + label="free space + clutter loss", + ) plt.title("Free space with additional median clutter loss ($f=27GHz$)") plt.xlabel("distance [m]") diff --git a/sharc/propagation/propagation_tvro.py b/sharc/propagation/propagation_tvro.py index 2be92463e..945740801 100644 --- a/sharc/propagation/propagation_tvro.py +++ b/sharc/propagation/propagation_tvro.py @@ -5,12 +5,16 @@ @author: edgar """ +import numpy as np +import matplotlib.pyplot as plt + from sharc.support.enumerations import StationType from sharc.propagation.propagation import Propagation from sharc.propagation.propagation_free_space import PropagationFreeSpace +from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters -import numpy as np -import matplotlib.pyplot as plt class PropagationTvro(Propagation): """ @@ -20,23 +24,101 @@ class PropagationTvro(Propagation): TODO: calculate the effective environment height for the generic case """ - def __init__(self, - random_number_gen: np.random.RandomState, - environment : str): + def __init__( + self, + random_number_gen: np.random.RandomState, + environment: str, + ): super().__init__(random_number_gen) if environment.upper() == "URBAN": - self.d_k = 0.02 #km + self.d_k = 0.02 # km self.shadowing_std = 6 self.h_a = 20 elif environment.upper() == "SUBURBAN": - self.d_k = 0.025 #km + self.d_k = 0.025 # km self.shadowing_std = 8 self.h_a = 9 self.building_loss = 20 self.free_space_path_loss = PropagationFreeSpace(random_number_gen) - def get_loss(self, *args, **kwargs) -> np.array: + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMi get_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE for IMT-IMT links or Sistem + station for IMT-System links + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()): + distances_2d, distances_3d, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distances_2d = station_a.get_distance_to(station_b) + distances_3d = station_a.get_3d_distance_to(station_b) + + indoor_stations = np.tile(station_a.indoor, (station_b.num_stations, 1)).transpose() + + # Use the right interface whether the link is IMT-IMT or IMT-System + # TODO: Refactor __get_loss and get rid of that if-else. + if station_a.is_imt_station() and station_b.is_imt_station(): + loss = self._get_loss( + distance_3D=distances_3d, + distance_2D=distances_2d, + frequency=frequency * np.ones(distances_2d.shape), + bs_height=station_b.height, + ue_height=station_a.height, + indoor_stations=indoor_stations + ) + else: + imt_station, sys_station = (station_a, station_b) \ + if station_a.is_imt_station() else (station_b, station_a) + loss = self._get_loss( + distance_3D=distances_3d, + distance_2D=distances_2d, + frequency=frequency * np.ones(distances_2d.shape), + bs_height=station_b.height, + imt_sta_type=imt_station.station_type, + imt_x=imt_station.x, + imt_y=imt_station.y, + imt_z=imt_station.height, + es_x=sys_station.x, + es_y=sys_station.y, + es_z=sys_station.height, + indoor_stations=indoor_stations + ) + + return loss + + def _get_loss(self, *args, **kwargs) -> np.array: """ Calculates path loss @@ -55,100 +137,118 @@ def get_loss(self, *args, **kwargs) -> np.array: frequency = kwargs["frequency"] # shadowing is enabled by default shadowing = kwargs.pop("shadowing", True) - number_of_sectors = kwargs.pop("number_of_sectors",1) indoor_stations = kwargs["indoor_stations"] + if "imt_sta_type" in kwargs.keys(): # calculating path loss for the IMT-system link height = kwargs["es_z"] # check if IMT staton is BS or UE imt_sta_type = kwargs["imt_sta_type"] if imt_sta_type is StationType.IMT_BS: - loss = self.get_loss_macrocell(distance_3D, - frequency, - height, - indoor_stations, - shadowing) + loss = self.get_loss_macrocell( + distance_3D, + frequency, + height, + indoor_stations, + shadowing, + ) else: - loss = self.get_loss_microcell(distance_3D, - frequency, - indoor_stations, - shadowing) - pass + loss = self.get_loss_microcell( + distance_3D, + frequency, + indoor_stations, + shadowing, + ) else: # calculating path loss for the IMT-IMT link height = kwargs["ue_height"] - loss = self.get_loss_macrocell(distance_3D, - frequency, - height, - indoor_stations, - shadowing) - if number_of_sectors > 1: - loss = np.repeat(loss, number_of_sectors, 1) - + loss = self.get_loss_macrocell( + distance_3D, + frequency, + height, + indoor_stations, + shadowing, + ) + return loss - def get_loss_microcell(self, - distance_3D : np.array, - frequency : np.array, - indoor_stations : np.array, - shadowing) -> np.array: - pl_los = 102.93 + 20*np.log10(distance_3D/1000) - pl_nlos = 153.5 + 40*np.log10(distance_3D/1000) + def get_loss_microcell( + self, + distance_3D: np.array, + frequency: np.array, + indoor_stations: np.array, + shadowing, + ) -> np.array: + pl_los = 102.93 + 20 * np.log10(distance_3D / 1000) + pl_nlos = 153.5 + 40 * np.log10(distance_3D / 1000) pr_los = self.get_los_probability(distance_3D) - loss = pl_los*pr_los + pl_nlos*(1 - pr_los) - + loss = pl_los * pr_los + pl_nlos * (1 - pr_los) + if shadowing: - shadowing_fading = self.random_number_gen.normal(0, - 3.89, - loss.shape) - loss = loss + shadowing_fading - - loss = loss + self.building_loss*indoor_stations - - free_space_path_loss = self.free_space_path_loss.get_loss(distance_3D = distance_3D, - frequency = frequency) + shadowing_fading = self.random_number_gen.normal( + 0, + 3.89, + loss.shape, + ) + loss = loss + shadowing_fading + + loss = loss + self.building_loss * indoor_stations + + free_space_path_loss = self.free_space_path_loss.get_free_space_loss( + distance=distance_3D, + frequency=frequency, + ) loss = np.maximum(loss, free_space_path_loss) - return loss - - def get_loss_macrocell(self, - distance_3D : np.array, - frequency : np.array, - height : np.array, - indoor_stations : np.array, - shadowing : bool) -> np.array: - - free_space_path_loss = self.free_space_path_loss.get_loss(distance_3D = distance_3D, - frequency = frequency) + return loss - f_fc = .25 + .375*(1 + np.tanh(7.5*(frequency/1000 - .5))) + def get_loss_macrocell( + self, + distance_3D: np.array, + frequency: np.array, + height: np.array, + indoor_stations: np.array, + shadowing: bool, + ) -> np.array: + + free_space_path_loss = self.free_space_path_loss.get_free_space_loss( + distance=distance_3D, + frequency=frequency, + ) + + f_fc = .25 + .375 * (1 + np.tanh(7.5 * (frequency / 1000 - .5))) clutter_loss = 10.25 * f_fc * np.exp(-self.d_k) * \ - (1 - np.tanh(6*(height/self.h_a - .625))) - .33 + (1 - np.tanh(6 * (height[:, np.newaxis] / self.h_a - .625))) - .33 loss = free_space_path_loss.copy() indices = (distance_3D >= 40) & (distance_3D < 10 * self.d_k * 1000) - loss[indices] = loss[indices] + (distance_3D[indices]/1000 - 0.04)/(10*self.d_k - 0.04) * clutter_loss[indices] + loss[indices] = loss[indices] + \ + (distance_3D[indices] / 1000 - 0.04) / \ + (10 * self.d_k - 0.04) * clutter_loss[indices] indices = (distance_3D >= 10 * self.d_k * 1000) loss[indices] = loss[indices] + clutter_loss[indices] - loss = loss + self.building_loss*indoor_stations + loss = loss + self.building_loss * indoor_stations if shadowing: - shadowing_fading = self.random_number_gen.normal(0, - self.shadowing_std, - loss.shape) - loss = loss + shadowing_fading + shadowing_fading = self.random_number_gen.normal( + 0, + self.shadowing_std, + loss.shape, + ) + loss = loss + shadowing_fading loss = np.maximum(loss, free_space_path_loss) return loss - - def get_los_probability(self, - distance : np.array, - distance_transition : float = 70) -> np.array: + def get_los_probability( + self, + distance: np.array, + distance_transition: float = 70, + ) -> np.array: """ Returns the line-of-sight (LOS) probability @@ -161,18 +261,18 @@ def get_los_probability(self, ------- LOS probability as a numpy array with same length as distance """ - p_los = 1/(1 + (1/np.exp(-0.1*(distance - distance_transition)))) + p_los = 1 / (1 + (1 / np.exp(-0.1 * (distance - distance_transition)))) return p_los if __name__ == '__main__': - distance_2D = np.linspace(10, 1000, num=1000)[:,np.newaxis] - frequency = 3600*np.ones(distance_2D.shape) - h_bs = 25*np.ones(len(distance_2D[:,0])) - h_ue = 1.5*np.ones(len(distance_2D[0,:])) + distance_2D = np.linspace(10, 1000, num=1000)[:, np.newaxis] + frequency = 3600 * np.ones(distance_2D.shape) + h_bs = 25 * np.ones(len(distance_2D[:, 0])) + h_ue = 1.5 * np.ones(len(distance_2D[0, :])) h_tvro = 6 - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - indoor_stations = np.zeros(distance_3D.shape, dtype = bool) + distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2) + indoor_stations = np.zeros(distance_3D.shape, dtype=bool) shadowing = False rand_gen = np.random.RandomState(101) @@ -180,74 +280,101 @@ def get_los_probability(self, prop_suburban = PropagationTvro(rand_gen, "SUBURBAN") prop_free_space = PropagationFreeSpace(rand_gen) - loss_urban_bs_ue = prop_urban.get_loss(distance_3D = distance_3D, - frequency = frequency, - indoor_stations = indoor_stations, - shadowing = shadowing, - ue_height = h_ue) - loss_suburban_bs_ue = prop_suburban.get_loss(distance_3D = distance_3D, - frequency = frequency, - indoor_stations = indoor_stations, - shadowing = shadowing, - ue_height = h_ue) - - loss_urban_bs_tvro = prop_urban.get_loss(distance_3D = distance_3D, - frequency = frequency, - indoor_stations = indoor_stations, - shadowing = shadowing, - imt_sta_type = StationType.IMT_BS, - es_z = h_tvro) - loss_suburban_bs_tvro = prop_suburban.get_loss(distance_3D = distance_3D, - frequency = frequency, - indoor_stations = indoor_stations, - shadowing = shadowing, - imt_sta_type = StationType.IMT_BS, - es_z = h_tvro) - - loss_ue_tvro = prop_urban.get_loss(distance_3D = distance_3D, - frequency = frequency, - indoor_stations = indoor_stations, - imt_sta_type = StationType.IMT_UE, - shadowing = shadowing, - es_z = h_tvro) - - loss_fs = prop_free_space.get_loss(distance_3D = distance_3D, - frequency = frequency) - - fig = plt.figure(figsize=(7,5), facecolor='w', edgecolor='k') + loss_urban_bs_ue = prop_urban._get_loss( + distance_3D=distance_3D, + frequency=frequency, + indoor_stations=indoor_stations, + shadowing=shadowing, + ue_height=h_ue, + ) + loss_suburban_bs_ue = prop_suburban._get_loss( + distance_3D=distance_3D, + frequency=frequency, + indoor_stations=indoor_stations, + shadowing=shadowing, + ue_height=h_ue, + ) + + loss_urban_bs_tvro = prop_urban._get_loss( + distance_3D=distance_3D, + frequency=frequency, + indoor_stations=indoor_stations, + shadowing=shadowing, + imt_sta_type=StationType.IMT_BS, + es_z=h_tvro, + ) + loss_suburban_bs_tvro = prop_suburban._get_loss( + distance_3D=distance_3D, + frequency=frequency, + indoor_stations=indoor_stations, + shadowing=shadowing, + imt_sta_type=StationType.IMT_BS, + es_z=h_tvro, + ) + + loss_ue_tvro = prop_urban._get_loss( + distance_3D=distance_3D, + frequency=frequency, + indoor_stations=indoor_stations, + imt_sta_type=StationType.IMT_UE, + shadowing=shadowing, + es_z=h_tvro, + ) + + loss_fs = prop_free_space.get_free_space_loss( + distance=distance_3D, + frequency=frequency, + ) + + fig = plt.figure(figsize=(7, 5), facecolor='w', edgecolor='k') ax = fig.gca() - ax.semilogx(distance_3D, loss_urban_bs_tvro, "-r", label = "urban, BS-to-TVRO", linewidth = 1) - ax.semilogx(distance_3D, loss_suburban_bs_tvro, "--r", label = "suburban, BS-to-TVRO", linewidth = 1) - ax.semilogx(distance_3D, loss_urban_bs_ue, "-b", label = "urban, BS-to-UE", linewidth = 1) - ax.semilogx(distance_3D, loss_suburban_bs_ue, "--b", label = "suburban, BS-to-UE", linewidth = 1) - ax.semilogx(distance_3D, loss_ue_tvro, "-.y", label = "UE-to-TVRO", linewidth = 1) - ax.semilogx(distance_3D, loss_fs, "-g", label = "free space", linewidth = 1.5) + ax.semilogx( + distance_3D, loss_urban_bs_tvro, "-r", + label="urban, BS-to-TVRO", linewidth=1, + ) + ax.semilogx( + distance_3D, loss_suburban_bs_tvro, "--r", + label="suburban, BS-to-TVRO", linewidth=1, + ) + ax.semilogx( + distance_3D, loss_urban_bs_ue, "-b", + label="urban, BS-to-UE", linewidth=1, + ) + ax.semilogx( + distance_3D, loss_suburban_bs_ue, "--b", + label="suburban, BS-to-UE", linewidth=1, + ) + ax.semilogx( + distance_3D, loss_ue_tvro, "-.y", + label="UE-to-TVRO", linewidth=1, + ) + ax.semilogx(distance_3D, loss_fs, "-g", label="free space", linewidth=1.5) plt.title("Path loss (no shadowing)") plt.xlabel("distance [m]") plt.ylabel("path loss [dB]") - plt.xlim((distance_3D[0,0], distance_3D[-1,0])) + plt.xlim((distance_3D[0, 0], distance_3D[-1, 0])) plt.ylim((70, 140)) plt.legend(loc="upper left") plt.tight_layout() plt.grid() - #plt.show() + # plt.show() ########################################################################### p_los = prop_urban.get_los_probability(distance_3D) - fig = plt.figure(figsize=(7,5), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(7, 5), facecolor='w', edgecolor='k') ax = fig.gca() - ax.semilogy(distance_3D, p_los, "-r", linewidth = 1) + ax.semilogy(distance_3D, p_los, "-r", linewidth=1) plt.title("LOS probability") plt.xlabel("distance [m]") plt.ylabel("probability") - plt.xlim((distance_3D[0,0], 200)) + plt.xlim((distance_3D[0, 0], 200)) plt.ylim((1e-6, 1)) plt.tight_layout() plt.grid() - plt.show() \ No newline at end of file + plt.show() diff --git a/sharc/propagation/propagation_uma.py b/sharc/propagation/propagation_uma.py index 97ff361a2..ba83a4014 100644 --- a/sharc/propagation/propagation_uma.py +++ b/sharc/propagation/propagation_uma.py @@ -4,12 +4,18 @@ @author: edgar """ - -from sharc.propagation.propagation import Propagation - +import os +import sys +sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "..")) import numpy as np import matplotlib.pyplot as plt from cycler import cycler +from multipledispatch import dispatch + +from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters + class PropagationUMa(Propagation): """ @@ -17,8 +23,66 @@ class PropagationUMa(Propagation): to 3GPP TR 38.900 v14.2.0. TODO: calculate the effective environment height for the generic case """ + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMa get_loss method + Calculates the loss between station_a and station_b - def get_loss(self, *args, **kwargs) -> np.array: + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()): + distances_2d, distances_3d, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distances_2d = station_a.get_distance_to(station_b) + distances_3d = station_a.get_3d_distance_to(station_b) + + loss = self.get_loss( + distances_3d, + distances_2d, + frequency * np.ones(distances_2d.shape), + station_b.height, + station_a.height, + params.imt.shadowing, + ) + + # the interface expects station_a.num_stations x station_b.num_stations array + return loss + + @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool) + def get_loss( + self, distance_3d: np.array, distance_2d: np.array, frequency: np.array, + bs_height: np.array, ue_height: np.array, shadowing: bool, + ) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing (if shadowing is to be added) @@ -37,44 +101,42 @@ def get_loss(self, *args, **kwargs) -> np.array: array with path loss values with dimensions of distance_2D """ - d_3D = kwargs["distance_3D"] - d_2D = kwargs["distance_2D"] - f = kwargs["frequency"] - h_bs = kwargs["bs_height"] - h_ue = kwargs["ue_height"] - h_e = np.ones(d_2D.shape) - std = kwargs["shadowing"] - - if std: + h_e = np.ones(distance_2d.shape) + if shadowing: shadowing_los = 4 shadowing_nlos = 6 else: shadowing_los = 0 shadowing_nlos = 0 - los_probability = self.get_los_probability(d_2D, h_ue) + los_probability = self.get_los_probability(distance_2d, ue_height) los_condition = self.get_los_condition(los_probability) i_los = np.where(los_condition == True)[:2] i_nlos = np.where(los_condition == False)[:2] - loss = np.empty(d_2D.shape) + loss = np.empty(distance_2d.shape) if len(i_los[0]): - loss_los = self.get_loss_los(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_los) + loss_los = self.get_loss_los( + distance_2d, distance_3d, frequency, bs_height, ue_height, h_e, shadowing_los, + ) loss[i_los] = loss_los[i_los] if len(i_nlos[0]): - loss_nlos = self.get_loss_nlos(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_nlos) + loss_nlos = self.get_loss_nlos( + distance_2d, distance_3d, frequency, bs_height, ue_height, h_e, shadowing_nlos, + ) loss[i_nlos] = loss_nlos[i_nlos] return loss - - def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, - frequency: np.array, - h_bs: np.array, h_ue: np.array, h_e: np.array, - shadowing_std=4): + def get_loss_los( + self, distance_2D: np.array, distance_3D: np.array, + frequency: np.array, + h_bs: np.array, h_ue: np.array, h_e: np.array, + shadowing_std=4, + ): """ Calculates path loss for the LOS (line-of-sight) case. @@ -88,7 +150,9 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, h_bs : array of base stations antenna heights [m] h_ue : array of user equipments antenna heights [m] """ - breakpoint_distance = self.get_breakpoint_distance(frequency, h_bs, h_ue, h_e) + breakpoint_distance = self.get_breakpoint_distance( + frequency, h_bs, h_ue, h_e, + ) # get index where distance if less than breakpoint idl = np.where(distance_2D < breakpoint_distance) @@ -99,25 +163,33 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, loss = np.empty(distance_2D.shape) if len(idl[0]): - loss[idl] = 20*np.log10(distance_3D[idl]) + 20*np.log10(frequency[idl]) - 27.55 + loss[idl] = 20 * np.log10(distance_3D[idl]) + \ + 20 * np.log10(frequency[idl]) - 27.55 if len(idg[0]): - fitting_term = -10*np.log10(breakpoint_distance**2 +(h_bs[:,np.newaxis] - h_ue)**2) - loss[idg] = 40*np.log10(distance_3D[idg]) + 20*np.log10(frequency[idg]) - 27.55 \ + fitting_term = -10 * \ + np.log10( + breakpoint_distance**2 + + (h_bs - h_ue[:, np.newaxis])**2, + ) + loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * np.log10(frequency[idg]) - 27.55 \ + fitting_term[idg] if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_2D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_2D.shape, + ) else: shadowing = 0 return loss + shadowing - - def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array, - frequency: np.array, - h_bs: np.array, h_ue: np.array, h_e: np.array, - shadowing_std=6): + def get_loss_nlos( + self, distance_2D: np.array, distance_3D: np.array, + frequency: np.array, + h_bs: np.array, h_ue: np.array, h_e: np.array, + shadowing_std=6, + ): """ Calculates path loss for the NLOS (non line-of-sight) case. @@ -130,24 +202,26 @@ def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array, frequency : center frequency [MHz] h_bs : array of base stations antenna heights [m] h_ue : array of user equipments antenna heights [m] """ - loss_nlos = -46.46 + 39.08*np.log10(distance_3D) + 20*np.log10(frequency) \ - - 0.6*(h_ue - 1.5) + loss_nlos = -46.46 + 39.08 * np.log10(distance_3D) + 20 * np.log10(frequency) \ + - 0.6 * (h_ue[:, np.newaxis] - 1.5) idl = np.where(distance_2D < 5000) if len(idl[0]): - loss_los = self.get_loss_los(distance_2D, distance_3D, - frequency, h_bs, h_ue, h_e, 0) + loss_los = self.get_loss_los( + distance_2D, distance_3D, + frequency, h_bs, h_ue, h_e, 0, + ) loss_nlos[idl] = np.maximum(loss_los[idl], loss_nlos[idl]) if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_3D.shape, + ) else: shadowing = 0 return loss_nlos + shadowing - - def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.array, h_e: np.array) -> float: """ Calculates the breakpoint distance for the LOS (line-of-sight) case. @@ -164,14 +238,14 @@ def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.arr array of breakpoint distances [m] """ # calculate the effective antenna heights - h_bs_eff = h_bs[:,np.newaxis] - h_e - h_ue_eff = h_ue - h_e + h_bs_eff = h_bs - h_e + h_ue_eff = h_ue[:, np.newaxis] - h_e # calculate the breakpoint distance - breakpoint_distance = 4*h_bs_eff*h_ue_eff*(frequency*1e6)/(3e8) + breakpoint_distance = 4 * h_bs_eff * \ + h_ue_eff * (frequency * 1e6) / (3e8) return breakpoint_distance - def get_los_condition(self, p_los: np.array) -> np.array: """ Evaluates if user equipments are LOS (True) of NLOS (False). @@ -185,10 +259,11 @@ def get_los_condition(self, p_los: np.array) -> np.array: An array with True or False if user equipments are in LOS of NLOS condition, respectively. """ - los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los + los_condition = self.random_number_gen.random_sample( + p_los.shape, + ) < p_los return los_condition - def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array: """ Returns the line-of-sight (LOS) probability @@ -208,12 +283,12 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array idc = np.where(h_ue > 13)[0] if len(idc): - c_prime[:,idc] = np.power((h_ue[idc] - 13)/10, 1.5) + c_prime[:, idc] = np.power((h_ue[idc] - 13) / 10, 1.5) p_los = np.ones(distance_2D.shape) idl = np.where(distance_2D > 18) - p_los[idl] = (18/distance_2D[idl] + np.exp(-distance_2D[idl]/63)*(1-18/distance_2D[idl])) \ - *(1 + 1.25*c_prime[idl]*np.power(distance_2D[idl]/100, 3)*np.exp(-distance_2D[idl]/150)) + p_los[idl] = (18 / distance_2D[idl] + np.exp(-distance_2D[idl] / 63) * (1 - 18 / distance_2D[idl])) \ + * (1 + 1.25 * c_prime[idl] * np.power(distance_2D[idl] / 100, 3) * np.exp(-distance_2D[idl] / 150)) return p_los @@ -222,9 +297,13 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array ########################################################################### # Print LOS probability - distance_2D = np.column_stack((np.linspace(1, 10000, num=10000)[:,np.newaxis], - np.linspace(1, 10000, num=10000)[:,np.newaxis], - np.linspace(1, 10000, num=10000)[:,np.newaxis])) + distance_2D = np.column_stack(( + np.linspace(1, 10000, num=10000)[:, np.newaxis], + np.linspace(1, 10000, num=10000)[ + :, np.newaxis, + ], + np.linspace(1, 10000, num=10000)[:, np.newaxis], + )) h_ue = np.array([1.5, 17, 23]) uma = PropagationUMa(np.random.RandomState(101)) @@ -233,18 +312,18 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array los_probability = uma.get_los_probability(distance_2D, h_ue) - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() - ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) + ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y'])) for h in range(len(h_ue)): name.append("$h_u = {:4.1f}$ $m$".format(h_ue[h])) - ax.loglog(distance_2D[:,h], los_probability[:,h], label=name[h]) + ax.loglog(distance_2D[:, h], los_probability[:, h], label=name[h]) plt.title("UMa - LOS probability") plt.xlabel("distance [m]") plt.ylabel("probability") - plt.xlim((0, distance_2D[-1,0])) + plt.xlim((0, distance_2D[-1, 0])) plt.ylim((0, 1.1)) plt.legend(loc="lower left") plt.tight_layout() @@ -254,30 +333,39 @@ def get_los_probability(self, distance_2D: np.array, h_ue: np.array) -> np.array # Print path loss for UMa-LOS, UMa-NLOS and Free Space from propagation_free_space import PropagationFreeSpace shadowing_std = 0 - distance_2D = np.linspace(1, 10000, num=10000)[:,np.newaxis] - freq = 27000*np.ones(distance_2D.shape) - h_bs = 25*np.ones(len(distance_2D[:,0])) - h_ue = 1.5*np.ones(len(distance_2D[0,:])) - h_e = np.zeros(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - - loss_los = uma.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - loss_nlos = uma.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_loss(distance_2D=distance_2D, frequency=freq) - - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + num_ue = 1 + num_bs = 1000 + distance_2D = np.repeat(np.linspace(1, num_bs, num=num_bs)[np.newaxis, :], num_ue, axis=0) + freq = 27000 * np.ones(distance_2D.shape) + h_bs = 6 * np.ones(num_bs) + h_ue = 1.5 * np.ones(num_ue) + h_e = np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2) + + loss_los = uma.get_loss_los( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + loss_nlos = uma.get_loss_nlos( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_free_space_loss( + distance=distance_2D, + frequency=freq, + ) + + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() - ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) + ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y'])) - ax.semilogx(distance_2D, loss_los, label="UMa LOS") - ax.semilogx(distance_2D, loss_nlos, label="UMa NLOS") - ax.semilogx(distance_2D, loss_fs, label="free space") + ax.semilogx(distance_2D[0, :], loss_los[0, :], label="UMa LOS") + ax.semilogx(distance_2D[0, :], loss_nlos[0, :], label="UMa NLOS") + ax.semilogx(distance_2D[0, :], loss_fs[0, :], label="free space") plt.title("UMa - path loss") plt.xlabel("distance [m]") plt.ylabel("path loss [dB]") - plt.xlim((0, distance_2D[-1,0])) - #plt.ylim((0, 1.1)) + plt.xlim((0, distance_2D[0, -1])) + # plt.ylim((0, 1.1)) plt.legend(loc="upper left") plt.tight_layout() plt.grid() diff --git a/sharc/propagation/propagation_umi.py b/sharc/propagation/propagation_umi.py index 66073dbee..30253c5fb 100644 --- a/sharc/propagation/propagation_umi.py +++ b/sharc/propagation/propagation_umi.py @@ -4,35 +4,104 @@ @author: LeticiaValle_Mac """ +import numpy as np +from multipledispatch import dispatch from sharc.propagation.propagation import Propagation +from sharc.station_manager import StationManager +from sharc.parameters.parameters import Parameters -import numpy as np class PropagationUMi(Propagation): """ - Implements the Urban Micro path loss model (Street Canyon) with LOS + Implements the Urban Micro path loss model (Street Canyon) with LOS probability according to 3GPP TR 38.900 v14.2.0. TODO: calculate the effective environment height for the generic case """ - def __init__(self, - random_number_gen: np.random.RandomState, - los_adjustment_factor: float): + def __init__( + self, + random_number_gen: np.random.RandomState, + los_adjustment_factor: float, + ): super().__init__(random_number_gen) self.los_adjustment_factor = los_adjustment_factor - - - def get_loss(self, *args, **kwargs) -> np.array: + + @dispatch(Parameters, float, StationManager, StationManager, np.ndarray, np.ndarray) + def get_loss( + self, + params: Parameters, + frequency: float, + station_a: StationManager, + station_b: StationManager, + station_a_gains=None, + station_b_gains=None, + ) -> np.array: + """Wrapper function for the PropagationUMi get_loss method + Calculates the loss between station_a and station_b + + Parameters + ---------- + station_a : StationManager + StationManager container representing IMT UE station - Station_type.IMT_UE + station_b : StationManager + StationManager container representing IMT BS stattion + params : Parameters + Simulation parameters needed for the propagation class - Station_type.IMT_BS + + Returns + ------- + np.array + Return an array station_a.num_stations x station_b.num_stations with the path loss + between each station + """ + wrap_around_enabled = False + if params.imt.topology.type == "MACROCELL": + wrap_around_enabled = params.imt.topology.macrocell.wrap_around \ + and params.imt.topology.macrocell.num_clusters == 1 + if params.imt.topology.type == "HOTSPOT": + wrap_around_enabled = params.imt.topology.hotspot.wrap_around \ + and params.imt.topology.hotspot.num_clusters == 1 + + if wrap_around_enabled and (station_a.is_imt_station() and station_b.is_imt_station()): + distance_2d, distance_3d, _, _ = \ + station_a.get_dist_angles_wrap_around(station_b) + else: + distance_2d = station_a.get_distance_to(station_b) + distance_3d = station_a.get_3d_distance_to(station_b) + + loss = self.get_loss( + distance_3d, + distance_2d, + frequency * np.ones(distance_2d.shape), + station_b.height, + station_a.height, + params.imt.shadowing, + ) + + return loss + + # pylint: disable=function-redefined + # pylint: disable=arguments-renamed + @dispatch(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, bool) + def get_loss( + self, + distance_3D: np.array, + distance_2D: np.array, + frequency: np.array, + bs_height: np.array, + ue_height: np.array, + shadowing_flag: bool, + ) -> np.array: """ Calculates path loss for LOS and NLOS cases with respective shadowing (if shadowing is to be added) Parameters ---------- - distance_3D (np.array) : 3D distances between stations - distance_2D (np.array) : 2D distances between stations - frequency (np.array) : center frequencie [MHz] + distance_3D (np.array) : 3D distances between base stations and user equipment + distance_2D (np.array) : 2D distances between base stations and user equipment + frequency (np.array) : center frequencies [MHz] bs_height (np.array) : base station antenna heights ue_height (np.array) : user equipment antenna heights shadowing (bool) : if shadowing should be added or not @@ -40,47 +109,51 @@ def get_loss(self, *args, **kwargs) -> np.array: Returns ------- array with path loss values with dimensions of distance_2D - """ - d_3D = kwargs["distance_3D"] - d_2D = kwargs["distance_2D"] - f = kwargs["frequency"] - h_bs = kwargs["bs_height"] - h_ue = kwargs["ue_height"] - h_e = np.ones(d_2D.shape) - std = kwargs["shadowing"] - - if std: + if shadowing_flag: shadowing_los = 4 shadowing_nlos = 7.82 # option 1 for UMi NLOS - #shadowing_nlos = 8.2 # option 2 for UMi NLOS + # shadowing_nlos = 8.2 # option 2 for UMi NLOS else: shadowing_los = 0 shadowing_nlos = 0 - los_probability = self.get_los_probability(d_2D, self.los_adjustment_factor) + # effective height + h_e = np.ones(distance_2D.shape) + + los_probability = self.get_los_probability( + distance_2D, self.los_adjustment_factor, + ) los_condition = self.get_los_condition(los_probability) i_los = np.where(los_condition == True)[:2] i_nlos = np.where(los_condition == False)[:2] - loss = np.empty(d_2D.shape) + loss = np.empty(distance_2D.shape) if len(i_los[0]): - loss_los = self.get_loss_los(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_los) + loss_los = self.get_loss_los( + distance_2D, distance_3D, frequency, bs_height, ue_height, h_e, shadowing_los, + ) loss[i_los] = loss_los[i_los] if len(i_nlos[0]): - loss_nlos = self.get_loss_nlos(d_2D, d_3D, f, h_bs, h_ue, h_e, shadowing_nlos) + loss_nlos = self.get_loss_nlos( + distance_2D, distance_3D, frequency, bs_height, ue_height, h_e, shadowing_nlos, + ) loss[i_nlos] = loss_nlos[i_nlos] return loss - - def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, - frequency: np.array, - h_bs: np.array, h_ue: np.array, h_e: np.array, - shadowing_std=4): + def get_loss_los( + self, distance_2D: np.array, + distance_3D: np.array, + frequency: np.array, + h_bs: np.array, + h_ue: np.array, + h_e: np.array, + shadowing_std=4, + ): """ Calculates path loss for the LOS (line-of-sight) case. @@ -94,7 +167,9 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, h_bs : array of base stations antenna heights [m] h_ue : array of user equipments antenna heights [m] """ - breakpoint_distance = self.get_breakpoint_distance(frequency, h_bs, h_ue, h_e) + breakpoint_distance = self.get_breakpoint_distance( + frequency, h_bs, h_ue, h_e, + ) # get index where distance if less than breakpoint idl = np.where(distance_2D < breakpoint_distance) @@ -105,25 +180,33 @@ def get_loss_los(self, distance_2D: np.array, distance_3D: np.array, loss = np.empty(distance_2D.shape) if len(idl[0]): - loss[idl] = 21*np.log10(distance_3D[idl]) + 20*np.log10(frequency[idl]) - 27.55 + loss[idl] = 21 * np.log10(distance_3D[idl]) + \ + 20 * np.log10(frequency[idl]) - 27.55 if len(idg[0]): - fitting_term = -9.5*np.log10(breakpoint_distance**2 +(h_bs[:,np.newaxis] - h_ue)**2) - loss[idg] = 40*np.log10(distance_3D[idg]) + 20*np.log10(frequency[idg]) - 27.55 \ + fitting_term = -9.5 * \ + np.log10( + breakpoint_distance**2 + + (h_bs - h_ue[:, np.newaxis])**2, + ) + loss[idg] = 40 * np.log10(distance_3D[idg]) + 20 * np.log10(frequency[idg]) - 27.55 \ + fitting_term[idg] if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_2D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_2D.shape, + ) else: shadowing = 0 return loss + shadowing - - def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array, - frequency: np.array, - h_bs: np.array, h_ue: np.array, h_e: np.array, - shadowing_std=7.82): + def get_loss_nlos( + self, distance_2D: np.array, distance_3D: np.array, + frequency: np.array, + h_bs: np.array, h_ue: np.array, h_e: np.array, + shadowing_std=7.82, + ): """ Calculates path loss for the NLOS (non line-of-sight) case. @@ -138,24 +221,26 @@ def get_loss_nlos(self, distance_2D: np.array, distance_3D: np.array, h_ue : array of user equipments antenna heights [m] """ # option 1 for UMi NLOS - loss_nlos = -37.55 + 35.3*np.log10(distance_3D) + 21.3*np.log10(frequency) \ - - 0.3*(h_ue - 1.5) + loss_nlos = -37.55 + 35.3 * np.log10(distance_3D) + 21.3 * np.log10(frequency) \ + - 0.3 * (h_ue[:, np.newaxis] - 1.5) - loss_los = self.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, 0) + loss_los = self.get_loss_los( + distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, 0, + ) loss_nlos = np.maximum(loss_los, loss_nlos) - + # option 2 for UMi NLOS - #loss_nlos = 31.9*np.log10(distance_3D) + 20*np.log10(frequency*1e-3) + 32.4 + # loss_nlos = 31.9*np.log10(distance_3D) + 20*np.log10(frequency*1e-3) + 32.4 if shadowing_std: - shadowing = self.random_number_gen.normal(0, shadowing_std, distance_3D.shape) + shadowing = self.random_number_gen.normal( + 0, shadowing_std, distance_3D.shape, + ) else: shadowing = 0 return loss_nlos + shadowing - - def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.array, h_e: np.array) -> float: """ Calculates the breakpoint distance for the LOS (line-of-sight) case. @@ -172,14 +257,14 @@ def get_breakpoint_distance(self, frequency: float, h_bs: np.array, h_ue: np.arr array of breakpoint distances [m] """ # calculate the effective antenna heights - h_bs_eff = h_bs[:,np.newaxis] - h_e - h_ue_eff = h_ue - h_e + h_bs_eff = h_bs - h_e + h_ue_eff = h_ue[:, np.newaxis] - h_e # calculate the breakpoint distance - breakpoint_distance = 4*h_bs_eff*h_ue_eff*(frequency*1e6)/(3e8) + breakpoint_distance = 4 * h_bs_eff * \ + h_ue_eff * (frequency * 1e6) / (3e8) return breakpoint_distance - def get_los_condition(self, p_los: np.array) -> np.array: """ Evaluates if user equipments are LOS (True) of NLOS (False). @@ -193,13 +278,16 @@ def get_los_condition(self, p_los: np.array) -> np.array: An array with True or False if user equipments are in LOS of NLOS condition, respectively. """ - los_condition = self.random_number_gen.random_sample(p_los.shape) < p_los + los_condition = self.random_number_gen.random_sample( + p_los.shape, + ) < p_los return los_condition - - def get_los_probability(self, - distance_2D: np.array, - los_adjustment_factor: float) -> np.array: + def get_los_probability( + self, + distance_2D: np.array, + los_adjustment_factor: float, + ) -> np.array: """ Returns the line-of-sight (LOS) probability @@ -207,7 +295,7 @@ def get_los_probability(self, ---------- distance_2D : Two-dimensional array with 2D distance values from base station to user terminal [m] - los_adjustment_factor : adjustment factor to increase/decrease the + los_adjustment_factor : adjustment factor to increase/decrease the LOS probability. Original value is 18 as per 3GPP Returns @@ -217,7 +305,10 @@ def get_los_probability(self, p_los = np.ones(distance_2D.shape) idl = np.where(distance_2D > los_adjustment_factor) - p_los[idl] = (los_adjustment_factor/distance_2D[idl] + np.exp(-distance_2D[idl]/36)*(1-los_adjustment_factor/distance_2D[idl])) + p_los[idl] = ( + los_adjustment_factor / distance_2D[idl] + + np.exp(-distance_2D[idl] / 36) * (1 - los_adjustment_factor / distance_2D[idl]) + ) return p_los @@ -229,31 +320,35 @@ def get_los_probability(self, ########################################################################### # Print LOS probability - #h_ue = np.array([1.5, 17, 23]) - distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis] + # h_ue = np.array([1.5, 17, 23]) + distance_2D = np.linspace(1, 1000, num=1000)[:, np.newaxis] umi = PropagationUMi(np.random.RandomState(101), 18) los_probability = np.empty(distance_2D.shape) name = list() - + los_adjustment_factor = 18 - los_probability_18 = umi.get_los_probability(distance_2D, los_adjustment_factor) + los_probability_18 = umi.get_los_probability( + distance_2D, los_adjustment_factor, + ) los_adjustment_factor = 29 - los_probability_29 = umi.get_los_probability(distance_2D, los_adjustment_factor) + los_probability_29 = umi.get_los_probability( + distance_2D, los_adjustment_factor, + ) - fig = plt.figure(figsize=(6,5), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(6, 5), facecolor='w', edgecolor='k') ax = fig.gca() - ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) + ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y'])) - ax.loglog(distance_2D, 100*los_probability_18, label = r"$\alpha = 18$") - ax.loglog(distance_2D, 100*los_probability_29, label = r"$\alpha = 29$") + ax.loglog(distance_2D, 100 * los_probability_18, label=r"$\alpha = 18$") + ax.loglog(distance_2D, 100 * los_probability_29, label=r"$\alpha = 29$") plt.title("UMi - LOS probability") plt.xlabel("distance between BS and UE [m]") plt.ylabel("probability [%]") - ax.legend(loc = "lower left") - ax.grid(True, which = "both", color="grey", linestyle='-', linewidth=0.2) + ax.legend(loc="lower left") + ax.grid(True, which="both", color="grey", linestyle='-', linewidth=0.2) plt.xlim((10, 300)) plt.ylim((10, 102)) plt.tight_layout() @@ -263,29 +358,38 @@ def get_los_probability(self, # Print path loss for UMi-LOS, UMi-NLOS and Free Space from propagation_free_space import PropagationFreeSpace shadowing_std = 0 - distance_2D = np.linspace(1, 1000, num=1000)[:,np.newaxis] - freq = 24350*np.ones(distance_2D.shape) - h_bs = 6*np.ones(len(distance_2D[:,0])) - h_ue = 1.5*np.ones(len(distance_2D[0,:])) + # 1 ue x 1000 bs + num_ue = 1 + num_bs = 1000 + distance_2D = np.repeat(np.linspace(1, num_bs, num=num_bs)[np.newaxis, :], num_ue, axis=0) + freq = 24350 * np.ones(distance_2D.shape) + h_bs = 6 * np.ones(num_bs) + h_ue = 1.5 * np.ones(num_ue) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - - loss_los = umi.get_loss_los(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - loss_nlos = umi.get_loss_nlos(distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std) - loss_fs = PropagationFreeSpace(np.random.RandomState(101)).get_loss(distance_2D=distance_2D, frequency=freq) - - fig = plt.figure(figsize=(8,6), facecolor='w', edgecolor='k') + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[np.newaxis, :])**2) + + loss_los = umi.get_loss_los( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + loss_nlos = umi.get_loss_nlos( + distance_2D, distance_3D, freq, h_bs, h_ue, h_e, shadowing_std, + ) + loss_fs = PropagationFreeSpace( + np.random.RandomState(101,), + ).get_free_space_loss(distance=distance_2D, frequency=freq) + + fig = plt.figure(figsize=(8, 6), facecolor='w', edgecolor='k') ax = fig.gca() - ax.set_prop_cycle( cycler('color', ['r', 'g', 'b', 'y']) ) + ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y'])) - ax.semilogx(distance_2D, loss_los, label="UMi LOS") - ax.semilogx(distance_2D, loss_nlos, label="UMi NLOS") - ax.semilogx(distance_2D, loss_fs, label="free space") + ax.semilogx(distance_2D[0, :], loss_los[0, :], label="UMi LOS") + ax.semilogx(distance_2D[0, :], loss_nlos[0, :], label="UMi NLOS") + ax.semilogx(distance_2D[0, :], loss_fs[0, :], label="free space") plt.title("UMi - path loss") plt.xlabel("distance [m]") plt.ylabel("path loss [dB]") - plt.xlim((0, distance_2D[-1,0])) + plt.xlim((0, distance_2D[0, -1])) plt.ylim((60, 200)) plt.legend(loc="upper left") plt.tight_layout() diff --git a/sharc/propagation/scintillation.py b/sharc/propagation/scintillation.py index 7d5cb4efc..37c2e9a70 100644 --- a/sharc/propagation/scintillation.py +++ b/sharc/propagation/scintillation.py @@ -5,11 +5,11 @@ @author: Andre Noll Barreto """ -from sharc.propagation.propagation import Propagation from sharc.propagation.atmosphere import ReferenceAtmosphere import numpy as np + class Scintillation(): """ Implements the scintillation attenuation according to ITU-R P619 @@ -21,8 +21,7 @@ def __init__(self, random_number_gen: np.random.RandomState): self.atmosphere = ReferenceAtmosphere() - - def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array: + def get_tropospheric_attenuation(self, *args, **kwargs) -> np.array: """ Calculates tropospheric scintillation based on ITU-R P.619, Appendix D @@ -36,10 +35,18 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array: default = "random" wet_refractivity (float) : wet term of the radio refractivity - optional if not given, then it is calculated from sat_params - sat_params (ParametersFss) : satellite channel parameters - optional - needed only if wet_refractivity is not given - + satellite channel parameters: + optional needed only if wet_refractivity is not given + space_station_alt_m : float + The space statio altitude in meters. + Optional needed only if wet_refractivity is not given + earth_station_alt_m : float + The Earth station altitude in meters. + Optional needed only if wet_refractivity is not given + earth_station_lat_deg : float + The Earth station latitude in degrees. + Optional needed only if wet_refractivity is not given Returns ------- attenuation (np.array): attenuation (dB) with dimensions equal to "elevation" @@ -48,44 +55,53 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array: f_GHz = kwargs["frequency_MHz"] / 1000. elevation_rad = kwargs["elevation"] / 180. * np.pi antenna_gain_dB = kwargs["antenna_gain_dB"] - if not np.isscalar(antenna_gain_dB): - antenna_gain_dB = antenna_gain_dB.flatten() time_ratio = kwargs.pop("time_ratio", "random") wet_refractivity = kwargs.pop("wet_refractivity", False) if not wet_refractivity: - sat_params = kwargs["sat_params"] + for p in ["earth_station_alt_m", "earth_station_lat_deg", "season"]: + if p not in kwargs: + raise ValueError( + f"Scintillation: parameter {p} is mandatory if wet_refractivity is set.", + ) temperature, \ - pressure, \ - water_vapour_density = self.atmosphere.get_reference_atmosphere_p835(sat_params.imt_lat_deg, - sat_params.imt_altitude, - sat_params.season) + pressure, \ + water_vapour_density = self.atmosphere.get_reference_atmosphere_p835( + kwargs["earth_station_lat_deg"], + kwargs["earth_station_lat_deg"], + kwargs["season"], + ) # calculate saturation water vapour pressure according to ITU-R P.453-12 # water coefficients (ice disregarded) - coef_a = 6.1121 - coef_b = 18.678 - coef_c = 257.14 - coef_d = 234.5 - EF_water = 1 + 1e-4 * (7.2 + pressure * (.032 + 5.9e-6 * temperature ** 2)) vapour_pressure = water_vapour_density * temperature / 216.7 # eq 10 in P453 - wet_refractivity = (72 * vapour_pressure / temperature - + 3.75e5 * vapour_pressure / temperature ** 2) + wet_refractivity = ( + 72 * vapour_pressure / temperature + + 3.75e5 * vapour_pressure / temperature ** 2 + ) sigma_ref = 3.6e-3 + 1e-4 * wet_refractivity h_l = 1000 - path_length = 2 * h_l / (np.sqrt(np.sin(elevation_rad) ** 2 + 2.35e-4) + np.sin(elevation_rad)) - eff_antenna_diameter = .3 * 10 ** (.05 * antenna_gain_dB) / (np.pi * f_GHz) + path_length = 2 * h_l / \ + (np.sqrt(np.sin(elevation_rad) ** 2 + 2.35e-4) + np.sin(elevation_rad)) + eff_antenna_diameter = .3 * \ + 10 ** (.05 * antenna_gain_dB) / (np.pi * f_GHz) x = 1.22 * eff_antenna_diameter ** 2 * (f_GHz / path_length) - antenna_averaging_factor = np.sqrt(3.86 * (x ** 2 + 1) ** (11 / 12) * - np.sin(11 / 6 * np.arctan(1 / x)) - 7.08 * x ** 5 / 6) - scintillation_intensity = (sigma_ref * f_GHz ** (7 / 12) * antenna_averaging_factor - / np.sin(elevation_rad) ** 1.2) + antenna_averaging_factor = np.sqrt( + 3.86 * (x ** 2 + 1) ** (11 / 12) * + np.sin(11 / 6 * np.arctan(1 / x)) - 7.08 * x ** 5 / 6, + ) + scintillation_intensity = ( + sigma_ref * f_GHz ** (7 / 12) * antenna_averaging_factor / + np.sin(elevation_rad) ** 1.2 + ) if isinstance(time_ratio, str) and time_ratio.lower() == "random": - time_ratio = self.random_number_gen.rand(len(elevation_rad)) + time_ratio = self.random_number_gen.rand( + elevation_rad.size, + ).reshape(elevation_rad.shape) # tropospheric scintillation attenuation not exceeded for time_percentage percent time time_percentage = time_ratio * 100. @@ -94,31 +110,40 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array: if np.isscalar(scintillation_intensity): if not np.isscalar(time_percentage): num_el = time_percentage.size - scintillation_intensity = np.ones(num_el) * scintillation_intensity + scintillation_intensity = np.ones( + num_el, + ) * scintillation_intensity else: num_el = scintillation_intensity.size if np.isscalar(time_percentage): time_percentage = np.ones(num_el) * time_percentage - attenuation = np.empty(num_el) + attenuation = np.empty(elevation_rad.shape) - a_ste = (2.672 - 1.258 * np.log10(time_percentage) - - .0835 * np.log10(time_percentage) ** 2 - - .0597 * np.log10(time_percentage) ** 3) + a_ste = ( + 2.672 - 1.258 * np.log10(time_percentage) - + .0835 * np.log10(time_percentage) ** 2 - + .0597 * np.log10(time_percentage) ** 3 + ) - a_stf = (3. - 1.71 * np.log10(100 - time_percentage) + - .072 * np.log10(100 - time_percentage) ** 2 - - .061 * np.log10(100 - time_percentage) ** 3) + a_stf = ( + 3. - 1.71 * np.log10(100 - time_percentage) + + .072 * np.log10(100 - time_percentage) ** 2 - + .061 * np.log10(100 - time_percentage) ** 3 + ) - gain_indices = np.where(time_percentage <= 50.)[0] - if gain_indices.size: - attenuation[gain_indices] = - scintillation_intensity[gain_indices] * a_ste[gain_indices] + gain_indices = np.where(time_percentage <= 50.) + if gain_indices: + attenuation[gain_indices] = - \ + scintillation_intensity[gain_indices] * a_ste[gain_indices] fade_indices = np.where(time_percentage > 50.)[0] if fade_indices.size: - attenuation[fade_indices] = scintillation_intensity[fade_indices] * a_stf[fade_indices] + attenuation[fade_indices] = scintillation_intensity[fade_indices] * \ + a_stf[fade_indices] return attenuation + if __name__ == '__main__': from matplotlib import pyplot as plt @@ -138,21 +163,32 @@ def get_tropospheric_attenuation (self, *args, **kwargs) -> np.array: plt.figure() for elevation in elevation_vec: - attenuation = propagation.get_tropospheric_attenuation(elevation=elevation, - frequency_MHz=frequency_MHz, - antenna_gain_dB=antenna_gain, - time_ratio=1 - (percentage_fading_exceeded / 100), - wet_refractivity=wet_refractivity) - plt.semilogx(percentage_fading_exceeded, attenuation, label="{} deg".format(elevation)) + attenuation = propagation.get_tropospheric_attenuation( + elevation=elevation, + frequency_MHz=frequency_MHz, + antenna_gain_dB=antenna_gain, + time_ratio=1 - + (percentage_fading_exceeded / 100), + wet_refractivity=wet_refractivity, + ) + plt.semilogx( + percentage_fading_exceeded, attenuation, + label="{} deg".format(elevation), + ) percentage_gain_exceeded = 10 ** np.arange(-2, 1.1, .1) for elevation in elevation_vec: - attenuation = propagation.get_tropospheric_attenuation(elevation=elevation, - frequency_MHz=frequency_MHz, - antenna_gain_dB=antenna_gain, - time_ratio=percentage_gain_exceeded / 100, - wet_refractivity=wet_refractivity) - plt.loglog(percentage_gain_exceeded, np.abs(attenuation), ':', label="{} deg".format(elevation)) + attenuation = propagation.get_tropospheric_attenuation( + elevation=elevation, + frequency_MHz=frequency_MHz, + antenna_gain_dB=antenna_gain, + time_ratio=percentage_gain_exceeded / 100, + wet_refractivity=wet_refractivity, + ) + plt.loglog( + percentage_gain_exceeded, np.abs(attenuation), + ':', label="{} deg".format(elevation), + ) plt.legend(title='elevation') plt.grid(True) diff --git a/sharc/results.py b/sharc/results.py index 210f99ec5..803a53c61 100644 --- a/sharc/results.py +++ b/sharc/results.py @@ -5,430 +5,271 @@ @author: edgar """ -from sharc.plot import Plot - -import numpy as np +import glob import os import datetime - +import re +import pathlib +import pandas as pd from shutil import copy + +class SampleList(list): + """ + This class only exists so that no list property can be confused with a SampleList + """ + + pass + + class Results(object): + """Handle the output of the simulator""" + + # This should always be true for 1st samples flush + overwrite_sample_files = True + + def __init__(self): + # Transmit power density [dBm/Hz] + self.imt_ul_tx_power_density = SampleList() + self.imt_ul_tx_power = SampleList() + # SINR [dB] + self.imt_ul_sinr_ext = SampleList() + # SINR [dB] + self.imt_ul_sinr = SampleList() + # SNR [dB] + self.imt_ul_snr = SampleList() + self.imt_ul_inr = SampleList() + # Throughput [bits/s/Hz] + self.imt_ul_tput_ext = SampleList() + # Throughput [bits/s/Hz] + self.imt_ul_tput = SampleList() + + self.imt_path_loss = SampleList() + self.imt_coupling_loss = SampleList() + # Antenna gain [dBi] + self.imt_bs_antenna_gain = SampleList() + # Antenna gain [dBi] + self.imt_ue_antenna_gain = SampleList() + + # Antenna gain [dBi] + self.system_imt_antenna_gain = SampleList() + # Antenna gain [dBi] + self.imt_system_antenna_gain = SampleList() + # Path Loss [dB] + self.imt_system_path_loss = SampleList() + # Building entry loss [dB] + self.imt_system_build_entry_loss = SampleList() + # System diffraction loss [dB] + self.imt_system_diffraction_loss = SampleList() - def __init__(self, parameters_filename: str, overwrite_output: bool): - self.imt_ul_tx_power_density = list() - self.imt_ul_tx_power = list() - self.imt_ul_sinr_ext = list() - self.imt_ul_sinr = list() - self.imt_ul_snr = list() - self.imt_ul_inr = list() - self.imt_ul_tput_ext = list() - self.imt_ul_tput = list() - - self.imt_path_loss = list() - self.imt_coupling_loss = list() - self.imt_bs_antenna_gain = list() - self.imt_ue_antenna_gain = list() - - self.system_imt_antenna_gain = list() - self.imt_system_antenna_gain = list() - self.imt_system_path_loss = list() - self.imt_system_build_entry_loss = list() - self.imt_system_diffraction_loss = list() - - self.imt_dl_tx_power_density = list() - self.imt_dl_tx_power = list() - self.imt_dl_sinr_ext = list() - self.imt_dl_sinr = list() - self.imt_dl_snr = list() - self.imt_dl_inr = list() - self.imt_dl_tput_ext = list() - self.imt_dl_tput = list() - - self.system_ul_coupling_loss = list() - self.system_ul_interf_power = list() - - self.system_dl_coupling_loss = list() - self.system_dl_interf_power = list() - - self.system_inr = list() - self.system_pfd = list() - self.system_rx_interf = list() + self.imt_dl_tx_power_density = SampleList() + # Transmit power [dBm] + self.imt_dl_tx_power = SampleList() + # SINR [dB] + self.imt_dl_sinr_ext = SampleList() + # SINR [dB] + self.imt_dl_sinr = SampleList() + # SNR [dB] + self.imt_dl_snr = SampleList() + # I/N [dB] + self.imt_dl_inr = SampleList() + # Throughput [bits/s/Hz] + self.imt_dl_tput_ext = SampleList() + # Throughput [bits/s/Hz] + self.imt_dl_tput = SampleList() + + self.system_ul_coupling_loss = SampleList() + self.system_ul_interf_power = SampleList() + # Interference Power [dBm] + + self.system_dl_coupling_loss = SampleList() + self.system_dl_interf_power = SampleList() + # Interference Power [dBm] + + self.system_inr = SampleList() + self.system_pfd = SampleList() + self.system_rx_interf = SampleList() + + self.__sharc_dir = pathlib.Path(__file__).parent.resolve() + + def prepare_to_write( + self, + parameters_filename: str, + overwrite_output: bool, + output_dir="output", + output_dir_prefix="output", + ): + self.output_dir_parent = output_dir if not overwrite_output: today = datetime.date.today() results_number = 1 - results_dir_head = 'output_' + today.isoformat() + '_' + "{:02n}" - self.create_dir(results_number,results_dir_head) + results_dir_head = ( + output_dir_prefix + "_" + today.isoformat() + "_" + "{:02n}" + ) + self.create_dir(results_number, results_dir_head) copy(parameters_filename, self.output_directory) else: - self.output_directory = 'output' + self.output_directory = self.__sharc_dir / self.output_dir_parent + try: + os.makedirs(self.output_directory) + except FileExistsError: + pass + + return self + + def create_dir(self, results_number: int, dir_head: str): + """Creates the output directory if it doesn't exist. + + Parameters + ---------- + results_number : int + Increment used in directory name + dir_head : str + Directory name prefix + + Returns + ------- + str + output directory name + """ + + dir_head_complete = ( + self.__sharc_dir / self.output_dir_parent / dir_head.format(results_number) + ) - def create_dir(self,results_number,dir_head): - - dir_head_complete = dir_head.format(results_number) - try: os.makedirs(dir_head_complete) self.output_directory = dir_head_complete - except FileExistsError as e: + except FileExistsError: self.create_dir(results_number + 1, dir_head) + def get_relevant_attributes(self): + """ + Returns the attributes that are used for storing samples + """ + self_dict = self.__dict__ - def generate_plot_list(self, n_bins): - self.plot_list = list() - if len(self.system_imt_antenna_gain) > 0: - values, base = np.histogram(self.system_imt_antenna_gain, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Antenna gain [dBi]" - y_label = "Probability of antenna gain < $X$" - title = "[SYS] CDF of system antenna gain towards IMT stations" - file_name = title - #x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_system_antenna_gain) > 0: - values, base = np.histogram(self.imt_system_antenna_gain, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Antenna gain [dBi]" - y_label = "Probability of antenna gain < $X$" - title = "[IMT] CDF of IMT station antenna gain towards system" - file_name = title - #x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_system_path_loss) > 0: - values, base = np.histogram(self.imt_system_path_loss, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Path Loss [dB]" - y_label = "Probability of path loss < $X$" - title = "[SYS] CDF of IMT to system path loss" - file_name = title - #x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_system_build_entry_loss) > 0: - values, base = np.histogram(self.imt_system_build_entry_loss, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Building entry loss [dB]" - y_label = "Probability of loss < $X$" - title = "[SYS] CDF of IMT to system building entry loss" - file_name = title - #x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_system_diffraction_loss) > 0: - values, base = np.histogram(self.imt_system_diffraction_loss, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Building entry loss [dB]" - y_label = "Probability of loss < $X$" - title = "[SYS] CDF of IMT to system diffraction loss" - file_name = title - #x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_bs_antenna_gain) > 0: - values, base = np.histogram(self.imt_bs_antenna_gain, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Antenna gain [dBi]" - y_label = "Probability of antenna gain < $X$" - title = "[IMT] CDF of BS antenna gain towards the UE" - file_name = title - x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ue_antenna_gain) > 0: - values, base = np.histogram(self.imt_ue_antenna_gain, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Antenna gain [dBi]" - y_label = "Probability of antenna gain < $X$" - title = "[IMT] CDF of UE antenna gain towards the BS" - file_name = title - x_limits = (0, 25) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ul_tx_power_density) > 0: - values, base = np.histogram(self.imt_ul_tx_power_density, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Transmit power density [dBm/Hz]" - y_label = "Probability of transmit power density < $X$" - title = "[IMT] CDF of UE transmit power density" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_ul_tx_power) > 0: - values, base = np.histogram(self.imt_ul_tx_power, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Transmit power [dBm]" - y_label = "Probability of transmit power < $X$" - title = "[IMT] CDF of UE transmit power" - file_name = title - x_limits = (-40, 30) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ul_sinr_ext) > 0: - values, base = np.histogram(self.imt_ul_sinr_ext, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "SINR [dB]" - y_label = "Probability of SINR < $X$" - title = "[IMT] CDF of UL SINR with external interference" - file_name = title - x_limits = (-15, 20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ul_sinr) > 0: - values, base = np.histogram(self.imt_ul_sinr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "SINR [dB]" - y_label = "Probability of SINR < $X$" - title = "[IMT] CDF of UL SINR" - file_name = title - x_limits = (-15, 20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ul_snr) > 0: - values, base = np.histogram(self.imt_ul_snr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of UL SNR" - x_label = "SNR [dB]" - y_label = "Probability of SNR < $X$" - file_name = title - x_limits = (-15, 20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_ul_inr) > 0: - values, base = np.histogram(self.imt_ul_inr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of UL interference-to-noise ratio" - x_label = "$I/N$ [dB]" - y_label = "Probability of $I/N$ < $X$" - file_name = title - #x_limits = (-15, 20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_ul_tput_ext) > 0: - values, base = np.histogram(self.imt_ul_tput_ext, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of UL throughput with external interference" - x_label = "Throughput [bits/s/Hz]" - y_label = "Probability of UL throughput < $X$" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_ul_tput) > 0: - values, base = np.histogram(self.imt_ul_tput, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of UL throughput" - x_label = "Throughput [bits/s/Hz]" - y_label = "Probability of UL throughput < $X$" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_path_loss) > 0: - values, base = np.histogram(self.imt_path_loss, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of path loss" - x_label = "Path loss [dB]" - y_label = "Probability of path loss < $X$" - file_name = title - x_limits = (40, 150) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_coupling_loss) > 0: - values, base = np.histogram(self.imt_coupling_loss, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of coupling loss" - x_label = "Coupling loss [dB]" - y_label = "Probability of coupling loss < $X$" - file_name = title - x_limits = (30, 120) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_dl_tx_power) > 0: - values, base = np.histogram(self.imt_dl_tx_power, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "Transmit power [dBm]" - y_label = "Probability of transmit power < $X$" - title = "[IMT] CDF of DL transmit power" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_dl_sinr_ext) > 0: - values, base = np.histogram(self.imt_dl_sinr_ext, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "SINR [dB]" - y_label = "Probability of SINR < $X$" - title = "[IMT] CDF of DL SINR with external interference" - file_name = title - x_limits = (-20, 80) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_dl_sinr) > 0: - values, base = np.histogram(self.imt_dl_sinr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - x_label = "SINR [dB]" - y_label = "Probability of SINR < $X$" - title = "[IMT] CDF of DL SINR" - file_name = title - x_limits = (-20, 80) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_dl_snr) > 0: - values, base = np.histogram(self.imt_dl_snr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of DL SNR" - x_label = "SNR [dB]" - y_label = "Probability of SNR < $X$" - file_name = title - x_limits = (-20, 80) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.imt_dl_inr) > 0: - values, base = np.histogram(self.imt_dl_inr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of DL interference-to-noise ratio" - x_label = "$I/N$ [dB]" - y_label = "Probability of $I/N$ < $X$" - file_name = title - #x_limits = (-15, 20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_dl_tput_ext) > 0: - values, base = np.histogram(self.imt_dl_tput_ext, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of DL throughput with external interference" - x_label = "Throughput [bits/s/Hz]" - y_label = "Probability of throughput < $X$" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.imt_dl_tput) > 0: - values, base = np.histogram(self.imt_dl_tput, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[IMT] CDF of DL throughput" - x_label = "Throughput [bits/s/Hz]" - y_label = "Probability of throughput < $X$" - file_name = title - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, y_lim=y_limits)) - if len(self.system_inr) > 0: - values, base = np.histogram(self.system_inr, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[SYS] CDF of system INR" - x_label = "INR [dB]" - y_label = "Probability of INR < $X$" - file_name = title - x_limits = (-80, 30) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - ################################################################### - # now we plot INR samples - x = np.arange(len(self.system_inr)) - y = np.array(self.system_inr) - title = "[SYS] INR samples" - x_label = "Number of samples" - y_label = "INR [dB]" - file_name = title - x_limits = (0, 800) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name)) - if len(self.system_pfd) > 0: - values, base = np.histogram(self.system_pfd, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[SYS] CDF of system PFD" - x_label = "PFD [dBm/m^2]" - y_label = "Probability of INR < $X$" - file_name = title -# x_limits = (-80, -20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.system_ul_interf_power) > 0: - values, base = np.histogram(self.system_ul_interf_power, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[SYS] CDF of system interference power from IMT UL" - x_label = "Interference Power [dBm]" - y_label = "Probability of Power < $X$" - file_name = title - #x_limits = (-80, -20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) - if len(self.system_dl_interf_power) > 0: - values, base = np.histogram(self.system_dl_interf_power, bins=n_bins) - cumulative = np.cumsum(values) - x = base[:-1] - y = cumulative / cumulative[-1] - title = "[SYS] CDF of system interference power from IMT DL" - x_label = "Interference Power [dBm]" - y_label = "Probability of Power < $X$" - file_name = title - #x_limits = (-80, -20) - y_limits = (0, 1) - self.plot_list.append(Plot(x, y, x_label, y_label, title, file_name, x_lim=x_limits, y_lim=y_limits)) + results_relevant_attr_names = list( + filter(lambda x: isinstance(getattr(self, x), SampleList), self_dict) + ) + + return results_relevant_attr_names def write_files(self, snapshot_number: int): - n_bins = 200 - file_extension = ".txt" - header_text = "Results collected after " + str(snapshot_number) + " snapshots." - self.generate_plot_list(n_bins) + """Writes the sample data to the output file + + Parameters + ---------- + snapshot_number : int + Current snapshot number + """ + results_relevant_attr_names = self.get_relevant_attributes() + for attr_name in results_relevant_attr_names: + file_path = os.path.join( + self.output_directory, + attr_name + ".csv", + ) + samples = getattr(self, attr_name) + if len(samples) == 0: + continue + df = pd.DataFrame({"samples": samples}) + if self.overwrite_sample_files: + df.to_csv(file_path, mode="w", index=False) + else: + df.to_csv(file_path, mode="a", index=False, header=False) + setattr(self, attr_name, SampleList()) + + if self.overwrite_sample_files: + self.overwrite_sample_files = False + + @staticmethod + def load_many_from_dir(root_dir: str, *, only_latest=True) -> list["Results"]: + output_dirs = list(glob.glob(f"{root_dir}/output_*")) + + if len(output_dirs) == 0: + print("[WARNING]: Results.load_many_from_dir did not find any results") + + if only_latest: + output_dirs = Results.get_most_recent_outputs_for_each_prefix(output_dirs) + + all_res = [] + for output_dir in output_dirs: + res = Results() + res.load_from_dir(output_dir) + all_res.append(res) + + return all_res + + def load_from_dir(self, abs_path: str) -> "Results": + self.output_directory = abs_path + + self_dict = self.__dict__ + results_relevant_attr_names = filter( + lambda x: isinstance(getattr(self, x), SampleList), self_dict + ) + + for attr_name in results_relevant_attr_names: + file_path = os.path.join(abs_path, f"{attr_name}.csv") + if os.path.exists(file_path): + try: + # Try reading the .csv file using pandas with different delimiters + try: + data = pd.read_csv(file_path, delimiter=",") + except pd.errors.ParserError: + data = pd.read_csv(file_path, delimiter=";") + + # Ensure the data has exactly one column + if data.shape[1] != 1: + raise Exception( + f"The file with samples of {attr_name} should have a single column.", + ) + + # Remove rows that do not contain valid numeric values + data = data.apply(pd.to_numeric, errors="coerce").dropna() + + # Ignore if there is no data + if data.empty: + continue + # Check if there is enough data to load results from. + + setattr(self, attr_name, SampleList(data.to_numpy()[:, 0])) + + except Exception as e: + print(e) + raise Exception( + f"Error processing the sample file ({attr_name}.csv) for {attr_name}: {e}" + ) + + return self + + @staticmethod + def get_most_recent_outputs_for_each_prefix(dirnames: list[str]) -> list[str]: + """ + Input: + A list of output directories. + Returns: + A list containing the most recent output dirname for each output_prefix. + Note that if full paths are provided, full paths are returned + """ + res = {} + + for dirname in dirnames: + prefix, date, id = Results.get_prefix_date_and_id(dirname) + res.setdefault(prefix, {"date": date, "id": id, "dirname": dirname}) + if date > res[prefix]["date"]: + res[prefix]["date"] = date + res[prefix]["id"] = id + res[prefix]["dirname"] = dirname + if date == res[prefix]["date"] and id > res[prefix]["id"]: + res[prefix]["id"] = id + res[prefix]["dirname"] = dirname - for plot in self.plot_list: - np.savetxt(os.path.join(self.output_directory, plot.file_name + file_extension), - np.transpose([plot.x, plot.y]), - fmt="%.5f", delimiter="\t", header=header_text)#, - #newline=os.linesep) + return list(map(lambda x: x["dirname"], res.values())) + @staticmethod + def get_prefix_date_and_id(dirname: str) -> (str, str, str): + mtch = re.search("(.*)(20[2-9][0-9]-[0-1][0-9]-[0-3][0-9])_([0-9]{2})", dirname) + prefix, date, id = mtch.group(1), mtch.group(2), mtch.group(3) + return prefix, date, id diff --git a/sharc/run_multiple_campaigns.py b/sharc/run_multiple_campaigns.py new file mode 100644 index 000000000..a3611fe77 --- /dev/null +++ b/sharc/run_multiple_campaigns.py @@ -0,0 +1,33 @@ +import subprocess +import os +import sys + + +def run_campaign(campaign_name): + # Get the current working directory + workfolder = os.path.dirname(os.path.abspath(__file__)) + + # Path to the main_cli.py script + main_cli_path = os.path.join(workfolder, "main_cli.py") + + # Campaign directory + campaign_folder = os.path.join( + workfolder, "campaigns", campaign_name, "input", + ) + + # List of parameter files + parameter_files = [ + os.path.join(campaign_folder, f) for f in os.listdir( + campaign_folder, + ) if f.endswith('.yaml') + ] + + # Run the command for each parameter file + for param_file in parameter_files: + command = [sys.executable, main_cli_path, "-p", param_file] + subprocess.run(command) + + +if __name__ == "__main__": + # Example usage + run_campaign("imt_hibs_ras_2600_MHz") diff --git a/sharc/run_multiple_campaigns_mut_thread.py b/sharc/run_multiple_campaigns_mut_thread.py new file mode 100644 index 000000000..ade03a5fb --- /dev/null +++ b/sharc/run_multiple_campaigns_mut_thread.py @@ -0,0 +1,43 @@ +import subprocess +import os +import sys +from concurrent.futures import ThreadPoolExecutor + + +def run_command(param_file, main_cli_path): + command = [sys.executable, main_cli_path, "-p", param_file] + subprocess.run(command) + + +def run_campaign(campaign_name): + # Path to the working directory + workfolder = os.path.dirname(os.path.abspath(__file__)) + main_cli_path = os.path.join(workfolder, "main_cli.py") + + # Campaign directory + campaign_folder = os.path.join( + workfolder, "campaigns", campaign_name, "input", + ) + + # List of parameter files + parameter_files = [ + os.path.join(campaign_folder, f) for f in os.listdir( + campaign_folder, + ) if f.endswith('.yaml') + ] + + # Number of threads (adjust as needed) + num_threads = min(len(parameter_files), os.cpu_count()) + + # Run the commands in parallel + with ThreadPoolExecutor(max_workers=num_threads) as executor: + executor.map( + run_command, parameter_files, [ + main_cli_path, + ] * len(parameter_files), + ) + + +if __name__ == "__main__": + # Example usage + run_campaign("imt_hibs_ras_2600_MHz") diff --git a/sharc/sharc_definitions.py b/sharc/sharc_definitions.py new file mode 100644 index 000000000..de399002a --- /dev/null +++ b/sharc/sharc_definitions.py @@ -0,0 +1,10 @@ +"""Commom SHARC definitions""" +SHARC_IMPLEMENTED_SYSTEMS = [ + "EESS_SS", + "METSAT_SS", + "FSS_SS", + "FSS_ES", + "FS", + "RAS", + "SINGLE_EARTH_STATION", +] diff --git a/sharc/simulation.py b/sharc/simulation.py index 69ec221e3..1a4b09648 100644 --- a/sharc/simulation.py +++ b/sharc/simulation.py @@ -12,12 +12,10 @@ import math import sys import matplotlib.pyplot as plt -from matplotlib.patches import Wedge from sharc.support.enumerations import StationType from sharc.topology.topology_factory import TopologyFactory from sharc.parameters.parameters import Parameters -from sharc.propagation.propagation import Propagation from sharc.station_manager import StationManager from sharc.results import Results from sharc.propagation.propagation_factory import PropagationFactory @@ -32,8 +30,12 @@ def __init__(self, parameters: Parameters, parameter_file: str): self.parameters = parameters self.parameters_filename = parameter_file - if self.parameters.general.system == "EESS_PASSIVE": - self.param_system = self.parameters.eess_passive + if self.parameters.general.system == "METSAT_SS": + self.param_system = self.parameters.metsat_ss + elif self.parameters.general.system == "EESS_SS": + self.param_system = self.parameters.eess_ss + elif self.parameters.general.system == "SINGLE_EARTH_STATION": + self.param_system = self.parameters.single_earth_station elif self.parameters.general.system == "FSS_SS": self.param_system = self.parameters.fss_ss elif self.parameters.general.system == "FSS_ES": @@ -47,13 +49,19 @@ def __init__(self, parameters: Parameters, parameter_file: str): elif self.parameters.general.system == "RAS": self.param_system = self.parameters.ras else: - sys.stderr.write("ERROR\nInvalid system: " + self.parameters.general.system) + sys.stderr.write( + "ERROR\nInvalid system: " + + self.parameters.general.system, + ) sys.exit(1) - - self.wrap_around_enabled = self.parameters.imt.wrap_around and \ - (self.parameters.imt.topology == 'MACROCELL' \ - or self.parameters.imt.topology == 'HOTSPOT') and \ - self.parameters.imt.num_clusters == 1 + + self.wrap_around_enabled = False + if self.parameters.imt.topology.type == "MACROCELL": + self.wrap_around_enabled = self.parameters.imt.topology.macrocell.wrap_around \ + and self.parameters.imt.topology.macrocell.num_clusters == 1 + if self.parameters.imt.topology.type == "HOTSPOT": + self.wrap_around_enabled = self.parameters.imt.topology.hotspot.wrap_around \ + and self.parameters.imt.topology.hotspot.num_clusters == 1 self.co_channel = self.parameters.general.enable_cochannel self.adjacent_channel = self.parameters.general.enable_adjacent_channel @@ -105,18 +113,27 @@ def __init__(self, parameters: Parameters, parameter_file: str): if self.overlapping_bandwidth < 0: self.overlapping_bandwidth = 0 - if (self.overlapping_bandwidth == self.param_system.bandwidth and - not self.parameters.imt.interfered_with) or \ - (self.overlapping_bandwidth == self.parameters.imt.bandwidth and - self.parameters.imt.interfered_with): + if (self.overlapping_bandwidth == self.param_system.bandwidth and not self.parameters.imt.interfered_with) or \ + (self.overlapping_bandwidth == self.parameters.imt.bandwidth and self.parameters.imt.interfered_with): self.adjacent_channel = False + if not self.co_channel and not self.adjacent_channel: + raise ValueError("Both co_channel and adjacent_channel can't be false") + random_number_gen = np.random.RandomState(self.parameters.general.seed) - self.propagation_imt = PropagationFactory.create_propagation(self.parameters.imt.channel_model, self.parameters, - random_number_gen) - self.propagation_system = PropagationFactory.create_propagation(self.param_system.channel_model, self.parameters, - random_number_gen) + self.propagation_imt = PropagationFactory.create_propagation( + self.parameters.imt.channel_model, + self.parameters, + self.parameters.imt, + random_number_gen, + ) + self.propagation_system = PropagationFactory.create_propagation( + self.param_system.channel_model, + self.parameters, + self.param_system, + random_number_gen, + ) def add_observer_list(self, observers: list): for o in observers: @@ -129,12 +146,16 @@ def initialize(self, *args, **kwargs): self.topology.calculate_coordinates() num_bs = self.topology.num_base_stations - num_ue = num_bs*self.parameters.imt.ue_k*self.parameters.imt.ue_k_m - - self.bs_power_gain = 10*math.log10(self.parameters.antenna_imt.bs_n_rows* - self.parameters.antenna_imt.bs_n_columns) - self.ue_power_gain = 10*math.log10(self.parameters.antenna_imt.ue_n_rows* - self.parameters.antenna_imt.ue_n_columns) + num_ue = num_bs * self.parameters.imt.ue.k * self.parameters.imt.ue.k_m + + self.bs_power_gain = 10 * math.log10( + self.parameters.imt.bs.antenna.n_rows * + self.parameters.imt.bs.antenna.n_columns, + ) + self.ue_power_gain = 10 * math.log10( + self.parameters.imt.ue.antenna.n_rows * + self.parameters.imt.ue.antenna.n_columns, + ) self.imt_bs_antenna_gain = list() self.imt_ue_antenna_gain = list() self.path_loss_imt = np.empty([num_bs, num_ue]) @@ -143,7 +164,7 @@ def initialize(self, *args, **kwargs): self.bs_to_ue_phi = np.empty([num_bs, num_ue]) self.bs_to_ue_theta = np.empty([num_bs, num_ue]) - self.bs_to_ue_beam_rbs = -1.0*np.ones(num_ue, dtype=int) + self.bs_to_ue_beam_rbs = -1.0 * np.ones(num_ue, dtype=int) self.ue = np.empty(num_ue) self.bs = np.empty(num_bs) @@ -152,18 +173,27 @@ def initialize(self, *args, **kwargs): # this attribute indicates the list of UE's that are connected to each # base station. The position the the list indicates the resource block # group that is allocated to the given UE - self.link = dict([(bs,list()) for bs in range(num_bs)]) + self.link = dict([(bs, list()) for bs in range(num_bs)]) # calculates the number of RB per BS - self.num_rb_per_bs = math.trunc((1-self.parameters.imt.guard_band_ratio)* \ - self.parameters.imt.bandwidth /self.parameters.imt.rb_bandwidth) + self.num_rb_per_bs = math.trunc( + (1 - self.parameters.imt.guard_band_ratio) * + self.parameters.imt.bandwidth / self.parameters.imt.rb_bandwidth, + ) # calculates the number of RB per UE on a given BS - self.num_rb_per_ue = math.trunc(self.num_rb_per_bs/self.parameters.imt.ue_k) - - self.results = Results(self.parameters_filename, self.parameters.general.overwrite_output) - - if self.parameters.general.system == 'RAS': - self.polarization_loss = 0.0 + self.num_rb_per_ue = math.trunc( + self.num_rb_per_bs / self.parameters.imt.ue.k, + ) + + self.results = Results().prepare_to_write( + self.parameters_filename, + self.parameters.general.overwrite_output, + self.parameters.general.output_dir, + self.parameters.general.output_dir_prefix, + ) + + if hasattr(self.param_system, "polarization_loss"): + self.polarization_loss = self.param_system.polarization_loss else: self.polarization_loss = 3.0 @@ -174,130 +204,166 @@ def finalize(self, *args, **kwargs): snapshot_number = kwargs["snapshot_number"] self.results.write_files(snapshot_number) - def calculate_coupling_loss(self, - station_a: StationManager, - station_b: StationManager, - propagation: Propagation, - c_channel = True) -> np.array: + def calculate_coupling_loss_system_imt( + self, + system_station: StationManager, + imt_station: StationManager, + is_co_channel=True, + ) -> np.array: """ - Calculates the path coupling loss from each station_a to all station_b. - Result is returned as a numpy array with dimensions num_a x num_b - TODO: calculate coupling loss between activa stations only + Calculates the coupling loss (path loss + antenna gains + other losses) between + a system station and an IMT station. + + Returns an numpy array with system_station.size X imt_station.size with coupling loss + values. + + Parameters + ---------- + system_station : StationManager + A StationManager object with system stations + imt_station : StationManager + A StationManager object with IMT stations + is_co_channel : bool, optional + Whether the interference analysis is co-channel or not, by default True + + Returns + ------- + np.array + Returns an numpy array with system_station.size X imt_station.size with coupling loss + values. """ - if station_a.station_type is StationType.EESS_PASSIVE or \ - station_a.station_type is StationType.FSS_SS or \ - station_a.station_type is StationType.HAPS or \ - station_a.station_type is StationType.RNS: - elevation_angles = station_b.get_elevation_angle(station_a, self.param_system) - elif station_a.station_type is StationType.IMT_BS and \ - station_b.station_type is StationType.IMT_UE and \ - self.parameters.imt.topology == "INDOOR": - elevation_angles = np.transpose(station_b.get_elevation(station_a)) - elif station_a.station_type is StationType.FSS_ES or \ - station_a.station_type is StationType.RAS: - elevation_angles = station_b.get_elevation(station_a) - else: - elevation_angles = None - - if station_a.station_type is StationType.EESS_PASSIVE or \ - station_a.station_type is StationType.FSS_SS or \ - station_a.station_type is StationType.FSS_ES or \ - station_a.station_type is StationType.HAPS or \ - station_a.station_type is StationType.FS or \ - station_a.station_type is StationType.RNS or \ - station_a.station_type is StationType.RAS: - # Calculate distance from transmitters to receivers. The result is a - # num_station_a x num_station_b - d_2D = station_a.get_distance_to(station_b) - d_3D = station_a.get_3d_distance_to(station_b) - - if self.parameters.imt.interfered_with: - freq = self.param_system.frequency - else: - freq = self.parameters.imt.frequency - - if station_b.station_type is StationType.IMT_UE: - # define antenna gains - gain_a = self.calculate_gains(station_a, station_b) - gain_b = np.transpose(self.calculate_gains(station_b, station_a, c_channel)) - sectors_in_node = 1 - additional_loss = self.parameters.imt.ue_ohmic_loss \ - + self.parameters.imt.ue_body_loss \ - + self.polarization_loss - else: - # define antenna gains - gain_a = np.repeat(self.calculate_gains(station_a, station_b), self.parameters.imt.ue_k, 1) - gain_b = np.transpose(self.calculate_gains(station_b, station_a, c_channel)) - sectors_in_node = self.parameters.imt.ue_k - additional_loss = self.parameters.imt.bs_ohmic_loss \ - + self.polarization_loss - - if self.parameters.imt.interfered_with: - earth_to_space = False - single_entry = True - else: - earth_to_space = True - single_entry = False - - if station_a.station_type is StationType.EESS_PASSIVE or \ - station_a.station_type is StationType.FSS_SS or \ - station_a.station_type is StationType.HAPS or \ - station_a.station_type is StationType.RNS: - path_loss = propagation.get_loss(distance_3D=d_3D, - frequency=freq*np.ones(d_3D.shape), - indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)), - elevation=elevation_angles, sat_params = self.param_system, - earth_to_space = earth_to_space, earth_station_antenna_gain=gain_b, - single_entry=single_entry, number_of_sectors=sectors_in_node) - else: - path_loss = propagation.get_loss(distance_3D=d_3D, - frequency=freq*np.ones(d_3D.shape), - indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)), - elevation=elevation_angles, es_params=self.param_system, - tx_gain = gain_a, rx_gain = gain_b, number_of_sectors=sectors_in_node, - imt_sta_type=station_b.station_type, - imt_x=station_b.x, - imt_y=station_b.y, - imt_z=station_b.height, - es_x=station_a.x, - es_y=station_a.y, - es_z=station_a.height) - if self.param_system.channel_model == "HDFSS": - self.imt_system_build_entry_loss = path_loss[1] - self.imt_system_diffraction_loss = path_loss[2] - path_loss = path_loss[0] - - self.system_imt_antenna_gain = gain_a - self.imt_system_antenna_gain = gain_b - self.imt_system_path_loss = path_loss - # IMT <-> IMT + # Set the frequency and other parameters for the propagation model + if self.parameters.imt.interfered_with: + freq = self.param_system.frequency else: - d_2D = self.bs_to_ue_d_2D - d_3D = self.bs_to_ue_d_3D freq = self.parameters.imt.frequency - - path_loss = propagation.get_loss(distance_3D=d_3D, - distance_2D=d_2D, - frequency=self.parameters.imt.frequency*np.ones(d_2D.shape), - indoor_stations=np.tile(station_b.indoor, (station_a.num_stations, 1)), - bs_height=station_a.height, - ue_height=station_b.height, - elevation=elevation_angles, - shadowing=self.parameters.imt.shadowing) + + # Calculate the antenna gains of the IMT station with respect to the system's station + if imt_station.station_type is StationType.IMT_UE: + # define antenna gains + gain_sys_to_imt = self.calculate_gains(system_station, imt_station) + gain_imt_to_sys = np.transpose( + self.calculate_gains( + imt_station, system_station, is_co_channel, + ), + ) + additional_loss = self.parameters.imt.ue.ohmic_loss \ + + self.parameters.imt.ue.body_loss \ + + self.polarization_loss + elif imt_station.station_type is StationType.IMT_BS: # define antenna gains - gain_a = self.calculate_gains(station_a, station_b) - gain_b = np.transpose(self.calculate_gains(station_b, station_a)) + # repeat for each BS beam + gain_sys_to_imt = np.repeat( + self.calculate_gains(system_station, imt_station), + self.parameters.imt.ue.k, 1, + ) + gain_imt_to_sys = np.transpose( + self.calculate_gains( + imt_station, system_station, is_co_channel, + ), + ) + additional_loss = self.parameters.imt.bs.ohmic_loss \ + + self.polarization_loss + else: + # should never reach this line + return ValueError(f"Invalid IMT StationType! {imt_station.station_type}") + + # Calculate the path loss based on the propagation model + path_loss = self.propagation_system.get_loss( + self.parameters, + freq, + system_station, + imt_station, + gain_sys_to_imt, + gain_imt_to_sys, + ) + # Store antenna gains and path loss samples + if self.param_system.channel_model == "HDFSS": + self.imt_system_build_entry_loss = path_loss[1] + self.imt_system_diffraction_loss = path_loss[2] + path_loss = path_loss[0] + + if imt_station.station_type is StationType.IMT_UE: + self.imt_system_path_loss = path_loss + else: + # Repeat for each BS beam + self.imt_system_path_loss = np.repeat( + path_loss, self.parameters.imt.ue.k, 1, + ) - # collect IMT BS and UE antenna gain samples - self.path_loss_imt = path_loss - self.imt_bs_antenna_gain = gain_a - self.imt_ue_antenna_gain = gain_b - additional_loss = self.parameters.imt.bs_ohmic_loss \ - + self.parameters.imt.ue_ohmic_loss \ - + self.parameters.imt.ue_body_loss + self.system_imt_antenna_gain = gain_sys_to_imt + self.imt_system_antenna_gain = gain_imt_to_sys # calculate coupling loss - coupling_loss = np.squeeze(path_loss - gain_a - gain_b) + additional_loss + coupling_loss = np.squeeze( + self.imt_system_path_loss - self.system_imt_antenna_gain - + self.imt_system_antenna_gain, + ) + additional_loss + + return coupling_loss + + def calculate_intra_imt_coupling_loss( + self, + imt_ue_station: StationManager, + imt_bs_station: StationManager, + ) -> np.array: + """ + Calculates the coupling loss (path loss + antenna gains + other losses) between + a IMT stations (UE and BS). + + Returns an numpy array with imt_bs_station.size X imt_ue_station.size with coupling loss + values. + + Parameters + ---------- + system_station : StationManager + A StationManager object representins IMT_UE stations + imt_station : StationManager + A StationManager object representins IMT_BS stations + is_co_channel : bool, optional + Whether the interference analysis is co-channel or not, by default True. + This parameter is ignored. It's keeped to maintein method interface. + + Returns + ------- + np.array + Returns an numpy array with imt_bs_station.size X imt_ue_station.size with coupling loss + values. + """ + # Calculate the antenna gains + + ant_gain_bs_to_ue = self.calculate_gains( + imt_bs_station, imt_ue_station, + ) + ant_gain_ue_to_bs = self.calculate_gains( + imt_ue_station, imt_bs_station, + ) + + # Calculate the path loss between IMT stations. Primarly used for UL power control. + + # Note on the array dimentions for coupling loss calculations: + # The function get_loss returns an array station_a x station_b + path_loss = self.propagation_imt.get_loss( + self.parameters, + self.parameters.imt.frequency, + imt_ue_station, + imt_bs_station, + ant_gain_ue_to_bs, + ant_gain_bs_to_ue, + ) + + # Collect IMT BS and UE antenna gain samples + self.path_loss_imt = np.transpose(path_loss) + self.imt_bs_antenna_gain = ant_gain_bs_to_ue + self.imt_ue_antenna_gain = np.transpose(ant_gain_ue_to_bs) + additional_loss = self.parameters.imt.bs.ohmic_loss \ + + self.parameters.imt.ue.ohmic_loss \ + + self.parameters.imt.ue.body_loss + + # calculate coupling loss + coupling_loss = np.squeeze( + self.path_loss_imt - self.imt_bs_antenna_gain - self.imt_ue_antenna_gain, + ) + additional_loss return coupling_loss @@ -307,10 +373,14 @@ def connect_ue_to_bs(self): user equipments are distributed and pointed to a certain base station according to the decisions taken at TG 5/1 meeting """ - num_ue_per_bs = self.parameters.imt.ue_k*self.parameters.imt.ue_k_m + num_ue_per_bs = self.parameters.imt.ue.k * self.parameters.imt.ue.k_m bs_active = np.where(self.bs.active)[0] for bs in bs_active: - ue_list = [i for i in range(bs*num_ue_per_bs, bs*num_ue_per_bs + num_ue_per_bs)] + ue_list = [ + i for i in range( + bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs, + ) + ] self.link[bs] = ue_list def select_ue(self, random_number_gen: np.random.RandomState): @@ -324,27 +394,34 @@ def select_ue(self, random_number_gen: np.random.RandomState): else: self.bs_to_ue_d_2D = self.bs.get_distance_to(self.ue) self.bs_to_ue_d_3D = self.bs.get_3d_distance_to(self.ue) - self.bs_to_ue_phi, self.bs_to_ue_theta = self.bs.get_pointing_vector_to(self.ue) + self.bs_to_ue_phi, self.bs_to_ue_theta = self.bs.get_pointing_vector_to( + self.ue, + ) bs_active = np.where(self.bs.active)[0] for bs in bs_active: # select K UE's among the ones that are connected to BS random_number_gen.shuffle(self.link[bs]) - K = self.parameters.imt.ue_k + K = self.parameters.imt.ue.k del self.link[bs][K:] # Activate the selected UE's and create beams if self.bs.active[bs]: self.ue.active[self.link[bs]] = np.ones(K, dtype=bool) for ue in self.link[bs]: # add beam to BS antennas - self.bs.antenna[bs].add_beam(self.bs_to_ue_phi[bs,ue], - self.bs_to_ue_theta[bs,ue]) + self.bs.antenna[bs].add_beam( + self.bs_to_ue_phi[bs, ue], + self.bs_to_ue_theta[bs, ue], + ) # add beam to UE antennas - self.ue.antenna[ue].add_beam(self.bs_to_ue_phi[bs,ue] - 180, - 180 - self.bs_to_ue_theta[bs,ue]) + self.ue.antenna[ue].add_beam( + self.bs_to_ue_phi[bs, ue] - 180, + 180 - self.bs_to_ue_theta[bs, ue], + ) # set beam resource block group - self.bs_to_ue_beam_rbs[ue] = len(self.bs.antenna[bs].beams_list) - 1 - + self.bs_to_ue_beam_rbs[ue] = len( + self.bs.antenna[bs].beams_list, + ) - 1 def scheduler(self): """ @@ -354,13 +431,17 @@ def scheduler(self): bs_active = np.where(self.bs.active)[0] for bs in bs_active: ue = self.link[bs] - self.bs.bandwidth[bs] = self.num_rb_per_ue*self.parameters.imt.rb_bandwidth - self.ue.bandwidth[ue] = self.num_rb_per_ue*self.parameters.imt.rb_bandwidth - - def calculate_gains(self, - station_1: StationManager, - station_2: StationManager, - c_channel = True) -> np.array: + self.bs.bandwidth[bs] = self.num_rb_per_ue * \ + self.parameters.imt.rb_bandwidth + self.ue.bandwidth[ue] = self.num_rb_per_ue * \ + self.parameters.imt.rb_bandwidth + + def calculate_gains( + self, + station_1: StationManager, + station_2: StationManager, + c_channel=True, + ) -> np.array: """ Calculates the gains of antennas in station_1 in the direction of station_2 @@ -369,99 +450,100 @@ def calculate_gains(self, station_2_active = np.where(station_2.active)[0] # Initialize variables (phi, theta, beams_idx) - if(station_1.station_type is StationType.IMT_BS): - if(station_2.station_type is StationType.IMT_UE): + if (station_1.station_type is StationType.IMT_BS): + if (station_2.station_type is StationType.IMT_UE): phi = self.bs_to_ue_phi theta = self.bs_to_ue_theta beams_idx = self.bs_to_ue_beam_rbs[station_2_active] - elif(station_2.station_type is StationType.EESS_PASSIVE or \ - station_2.station_type is StationType.FSS_SS or \ - station_2.station_type is StationType.FSS_ES or \ - station_2.station_type is StationType.HAPS or \ - station_2.station_type is StationType.FS or \ - station_2.station_type is StationType.RNS or \ - station_2.station_type is StationType.RAS): + elif not station_2.is_imt_station(): phi, theta = station_1.get_pointing_vector_to(station_2) - phi = np.repeat(phi,self.parameters.imt.ue_k,0) - theta = np.repeat(theta,self.parameters.imt.ue_k,0) - beams_idx = np.tile(np.arange(self.parameters.imt.ue_k),self.bs.num_stations) + phi = np.repeat(phi, self.parameters.imt.ue.k, 0) + theta = np.repeat(theta, self.parameters.imt.ue.k, 0) + beams_idx = np.tile( + np.arange(self.parameters.imt.ue.k), self.bs.num_stations, + ) - elif(station_1.station_type is StationType.IMT_UE): + elif (station_1.station_type is StationType.IMT_UE): phi, theta = station_1.get_pointing_vector_to(station_2) - beams_idx = np.zeros(len(station_2_active),dtype=int) - - elif(station_1.station_type is StationType.EESS_PASSIVE or \ - station_1.station_type is StationType.FSS_SS or \ - station_1.station_type is StationType.FSS_ES or \ - station_1.station_type is StationType.HAPS or \ - station_1.station_type is StationType.FS or \ - station_1.station_type is StationType.RNS or \ - station_1.station_type is StationType.RAS): + beams_idx = np.zeros(len(station_2_active), dtype=int) + + elif not station_1.is_imt_station(): phi, theta = station_1.get_pointing_vector_to(station_2) - beams_idx = np.zeros(len(station_2_active),dtype=int) + beams_idx = np.zeros(len(station_2_active), dtype=int) # Calculate gains gains = np.zeros(phi.shape) - if (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.EESS_PASSIVE) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FSS_SS) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FSS_ES) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.HAPS) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.FS) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.RNS) or \ - (station_1.station_type is StationType.IMT_BS and station_2.station_type is StationType.RAS): + if station_1.station_type is StationType.IMT_BS and not station_2.is_imt_station(): for k in station_1_active: - for b in range(k*self.parameters.imt.ue_k,(k+1)*self.parameters.imt.ue_k): - gains[b,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[b,station_2_active], - theta_vec=theta[b,station_2_active], - beams_l=np.array([beams_idx[b]]), - co_channel=c_channel) - - elif (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.EESS_PASSIVE) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FSS_SS) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FSS_ES) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.HAPS) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.FS) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.RNS) or \ - (station_1.station_type is StationType.IMT_UE and station_2.station_type is StationType.RAS): - for k in station_1_active: - gains[k,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[k,station_2_active], - theta_vec=theta[k,station_2_active], - beams_l=beams_idx, - co_channel=c_channel) + for b in range(k * self.parameters.imt.ue.k, (k + 1) * self.parameters.imt.ue.k): + gains[b, station_2_active] = station_1.antenna[k].calculate_gain( + phi_vec=phi[b, station_2_active], + theta_vec=theta[ + b, + station_2_active, + ], + beams_l=np.array( + [beams_idx[b]], + ), + co_channel=c_channel, + ) + + elif station_1.station_type is StationType.IMT_UE and not station_2.is_imt_station(): + for k in station_1_active: + gains[k, station_2_active] = station_1.antenna[k].calculate_gain( + phi_vec=phi[k, station_2_active], + theta_vec=theta[ + k, + station_2_active, + ], + beams_l=beams_idx, + co_channel=c_channel, + ) elif station_1.station_type is StationType.RNS: - gains[0,station_2_active] = station_1.antenna[0].calculate_gain(phi_vec = phi[0,station_2_active], - theta_vec = theta[0,station_2_active]) + gains[0, station_2_active] = station_1.antenna[0].calculate_gain( + phi_vec=phi[0, station_2_active], + theta_vec=theta[0, station_2_active], + ) - elif station_1.station_type is StationType.EESS_PASSIVE or \ - station_1.station_type is StationType.FSS_SS or \ - station_1.station_type is StationType.FSS_ES or \ - station_1.station_type is StationType.HAPS or \ - station_1.station_type is StationType.FS or \ - station_1.station_type is StationType.RAS: + elif not station_1.is_imt_station(): off_axis_angle = station_1.get_off_axis_angle(station_2) distance = station_1.get_distance_to(station_2) - theta = np.degrees(np.arctan((station_1.height - station_2.height)/distance)) + station_1.elevation - gains[0,station_2_active] = station_1.antenna[0].calculate_gain(off_axis_angle_vec=off_axis_angle[0,station_2_active], - theta_vec=theta[0,station_2_active]) - else: # for IMT <-> IMT + theta = np.degrees( + np.arctan2( + (station_1.height - station_2.height), distance, + ), + ) + station_1.elevation + gains[0, station_2_active] = \ + station_1.antenna[0].calculate_gain( + off_axis_angle_vec=off_axis_angle[0, station_2_active], + theta_vec=theta[0, station_2_active], + ) + else: # for IMT <-> IMT for k in station_1_active: - gains[k,station_2_active] = station_1.antenna[k].calculate_gain(phi_vec=phi[k,station_2_active], - theta_vec=theta[k,station_2_active], - beams_l=beams_idx) - + gains[k, station_2_active] = station_1.antenna[k].calculate_gain( + phi_vec=phi[k, station_2_active], + theta_vec=theta[ + k, + station_2_active, + ], + beams_l=beams_idx, + ) return gains - def calculate_imt_tput(self, - sinr: np.array, - sinr_min: float, - sinr_max: float, - attenuation_factor: float) -> np.array: + def calculate_imt_tput( + self, + sinr: np.array, + sinr_min: float, + sinr_max: float, + attenuation_factor: float, + ) -> np.array: tput_min = 0 - tput_max = attenuation_factor*math.log2(1+math.pow(10, 0.1*sinr_max)) + tput_max = attenuation_factor * \ + math.log2(1 + math.pow(10, 0.1 * sinr_max)) - tput = attenuation_factor*np.log2(1+np.power(10, 0.1*sinr)) + tput = attenuation_factor * np.log2(1 + np.power(10, 0.1 * sinr)) id_min = np.where(sinr < sinr_min)[0] id_max = np.where(sinr > sinr_max)[0] @@ -500,10 +582,10 @@ def calculate_bw_weights(self, bw_imt: float, bw_sys: float, ue_k: int) -> np.ar bw_per_rbg = bw_imt / ue_k # number of resource block groups that will have weight equal to 1 - rb_ones = math.floor( bw_sys / bw_per_rbg ) + rb_ones = math.floor(bw_sys / bw_per_rbg) # weight of the rbg that will generate partial interference - rb_partial = np.mod( bw_sys, bw_per_rbg ) / bw_per_rbg + rb_partial = np.mod(bw_sys, bw_per_rbg) / bw_per_rbg # assign value to weight array weights[:rb_ones] = 1 @@ -512,24 +594,33 @@ def calculate_bw_weights(self, bw_imt: float, bw_sys: float, ue_k: int) -> np.ar return weights def plot_scenario(self): - fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k') ax = fig.gca() # Plot network topology self.topology.plot(ax) # Plot user equipments - ax.scatter(self.ue.x, self.ue.y, color='r', edgecolor="w", linewidth=0.5, label="UE") + ax.scatter( + self.ue.x, self.ue.y, color='r', + edgecolor="w", linewidth=0.5, label="UE", + ) # wedge = Wedge((0, 0), 300, 0, 360, 290, color='b', alpha=0.2, fill=True) # ax.add_artist(wedge) - + # Plot UE's azimuth d = 0.1 * self.topology.cell_radius for i in range(len(self.ue.x)): - plt.plot([self.ue.x[i], self.ue.x[i] + d*math.cos(math.radians(self.ue.azimuth[i]))], - [self.ue.y[i], self.ue.y[i] + d*math.sin(math.radians(self.ue.azimuth[i]))], - 'r-') + plt.plot( + [self.ue.x[i], self.ue.x[i] + d * + math.cos(math.radians(self.ue.azimuth[i]))], + [ + self.ue.y[i], self.ue.y[i] + d * + math.sin(math.radians(self.ue.azimuth[i])), + ], + 'r-', + ) plt.axis('image') plt.title("Simulation scenario") @@ -540,14 +631,17 @@ def plot_scenario(self): plt.show() if self.parameters.imt.topology == "INDOOR": - fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') + fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k') ax = fig.gca() # Plot network topology - self.topology.plot(ax,top_view=False) + self.topology.plot(ax, top_view=False) # Plot user equipments - ax.scatter(self.ue.x, self.ue.height, color='r', edgecolor="w", linewidth=0.5, label="UE") + ax.scatter( + self.ue.x, self.ue.height, color='r', + edgecolor="w", linewidth=0.5, label="UE", + ) plt.title("Simulation scenario: side view") plt.xlabel("x-coordinate [m]") @@ -555,7 +649,7 @@ def plot_scenario(self): plt.legend(loc="upper left", scatterpoints=1) plt.tight_layout() plt.show() - + # sys.exit(0) @abstractmethod @@ -563,7 +657,6 @@ def snapshot(self, *args, **kwargs): """ Performs a single snapshot. """ - pass @abstractmethod def power_control(self): @@ -576,4 +669,3 @@ def collect_results(self, *args, **kwargs): """ Collects results. """ - pass diff --git a/sharc/simulation_downlink.py b/sharc/simulation_downlink.py index 30094e369..2c6fb26e4 100644 --- a/sharc/simulation_downlink.py +++ b/sharc/simulation_downlink.py @@ -11,9 +11,7 @@ from sharc.simulation import Simulation from sharc.parameters.parameters import Parameters from sharc.station_factory import StationFactory -from sharc.support.enumerations import StationType - -from sharc.propagation.propagation_factory import PropagationFactory +from sharc.parameters.constants import BOLTZMANN_CONSTANT class SimulationDownlink(Simulation): @@ -30,7 +28,7 @@ def snapshot(self, *args, **kwargs): seed = kwargs["seed"] random_number_gen = np.random.RandomState(seed) - + # In case of hotspots, base stations coordinates have to be calculated # on every snapshot. Anyway, let topology decide whether to calculate # or not @@ -38,27 +36,33 @@ def snapshot(self, *args, **kwargs): # Create the base stations (remember that it takes into account the # network load factor) - self.bs = StationFactory.generate_imt_base_stations(self.parameters.imt, - self.parameters.antenna_imt, - self.topology, random_number_gen) + self.bs = StationFactory.generate_imt_base_stations( + self.parameters.imt, + self.parameters.imt.bs.antenna, + self.topology, random_number_gen, + ) # Create the other system (FSS, HAPS, etc...) - self.system = StationFactory.generate_system(self.parameters, self.topology, random_number_gen) + self.system = StationFactory.generate_system( + self.parameters, self.topology, random_number_gen, + ) # Create IMT user equipments - self.ue = StationFactory.generate_imt_ue(self.parameters.imt, - self.parameters.antenna_imt, - self.topology, random_number_gen) + self.ue = StationFactory.generate_imt_ue( + self.parameters.imt, + self.parameters.imt.ue.antenna, + self.topology, random_number_gen, + ) + + # self.plot_scenario() - #self.plot_scenario() - self.connect_ue_to_bs() self.select_ue(random_number_gen) # Calculate coupling loss after beams are created - self.coupling_loss_imt = self.calculate_coupling_loss(self.bs, - self.ue, - self.propagation_imt) + self.coupling_loss_imt = self.calculate_intra_imt_coupling_loss( + self.ue, self.bs, + ) self.scheduler() self.power_control() @@ -67,13 +71,11 @@ def snapshot(self, *args, **kwargs): # interference into IMT self.calculate_sinr() self.calculate_sinr_ext() - pass else: # Execute this piece of code if IMT generates interference into # the other system self.calculate_sinr() self.calculate_external_interference() - pass self.collect_results(write_to_file, snapshot_number) @@ -86,19 +88,22 @@ def power_control(self): """ # Currently, the maximum transmit power of the base station is equaly # divided among the selected UEs - total_power = self.parameters.imt.bs_conducted_power \ - + self.bs_power_gain - tx_power = total_power - 10 * math.log10(self.parameters.imt.ue_k) + total_power = self.parameters.imt.bs.conducted_power \ + + self.bs_power_gain + tx_power = total_power - 10 * math.log10(self.parameters.imt.ue.k) # calculate transmit powers to have a structure such as # {bs_1: [pwr_1, pwr_2,...], ...}, where bs_1 is the base station id, # pwr_1 is the transmit power from bs_1 to ue_1, pwr_2 is the transmit # power from bs_1 to ue_2, etc bs_active = np.where(self.bs.active)[0] - self.bs.tx_power = dict([(bs, tx_power*np.ones(self.parameters.imt.ue_k)) for bs in bs_active]) + self.bs.tx_power = dict( + [(bs, tx_power * np.ones(self.parameters.imt.ue.k)) + for bs in bs_active], + ) # Update the spectral mask if self.adjacent_channel: - self.bs.spectral_mask.set_mask(power = total_power) + self.bs.spectral_mask.set_mask(p_tx=total_power) def calculate_sinr(self): """ @@ -107,26 +112,33 @@ def calculate_sinr(self): bs_active = np.where(self.bs.active)[0] for bs in bs_active: ue = self.link[bs] - self.ue.rx_power[ue] = self.bs.tx_power[bs] - self.coupling_loss_imt[bs,ue] + self.ue.rx_power[ue] = self.bs.tx_power[bs] - \ + self.coupling_loss_imt[bs, ue] # create a list with base stations that generate interference in ue_list bs_interf = [b for b in bs_active if b not in [bs]] # calculate intra system interference for bi in bs_interf: - interference = self.bs.tx_power[bi] - self.coupling_loss_imt[bi,ue] + interference = self.bs.tx_power[bi] - \ + self.coupling_loss_imt[bi, ue] - self.ue.rx_interference[ue] = 10*np.log10( \ - np.power(10, 0.1*self.ue.rx_interference[ue]) + np.power(10, 0.1*interference)) + self.ue.rx_interference[ue] = 10 * np.log10( + np.power( + 10, 0.1 * self.ue.rx_interference[ue]) + np.power(10, 0.1 * interference), + ) + # Thermal noise in dBm self.ue.thermal_noise = \ - 10*math.log10(self.parameters.imt.BOLTZMANN_CONSTANT*self.parameters.imt.noise_temperature*1e3) + \ - 10*np.log10(self.ue.bandwidth * 1e6) + \ + 10 * math.log10(BOLTZMANN_CONSTANT * self.parameters.imt.noise_temperature * 1e3) + \ + 10 * np.log10(self.ue.bandwidth * 1e6) + \ self.ue.noise_figure self.ue.total_interference = \ - 10*np.log10(np.power(10, 0.1*self.ue.rx_interference) + \ - np.power(10, 0.1*self.ue.thermal_noise)) + 10 * np.log10( + np.power(10, 0.1 * self.ue.rx_interference) + + np.power(10, 0.1 * self.ue.thermal_noise), + ) self.ue.sinr = self.ue.rx_power - self.ue.total_interference self.ue.snr = self.ue.rx_power - self.ue.thermal_noise @@ -136,38 +148,48 @@ def calculate_sinr_ext(self): Calculates the downlink SINR and INR for each UE taking into account the interference that is generated by the other system into IMT system. """ - self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system, - self.ue, - self.propagation_system, - c_channel = self.co_channel) + self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt( + self.system, + self.ue, + self.co_channel, + ) # applying a bandwidth scaling factor since UE transmits on a portion # of the satellite's bandwidth # calculate interference only to active UE's ue = np.where(self.ue.active)[0] - tx_power_sys = self.param_system.tx_power_density + 10*np.log10(self.ue.bandwidth[ue]*1e6) + 30 - self.ue.ext_interference[ue] = tx_power_sys - self.coupling_loss_imt_system[ue] + tx_power_sys = self.param_system.tx_power_density + \ + 10 * np.log10(self.ue.bandwidth[ue] * 1e6) + 30 + self.ue.ext_interference[ue] = tx_power_sys - \ + self.coupling_loss_imt_system[ue] self.ue.sinr_ext[ue] = self.ue.rx_power[ue] \ - - (10*np.log10(np.power(10, 0.1*self.ue.total_interference[ue]) + np.power(10, 0.1*self.ue.ext_interference[ue]))) - self.ue.inr[ue] = self.ue.ext_interference[ue] - self.ue.thermal_noise[ue] + - (10 * np.log10( + np.power(10, 0.1 * self.ue.total_interference[ue]) + np.power( + 10, 0.1 * self.ue.ext_interference[ue], + ), + )) + self.ue.inr[ue] = self.ue.ext_interference[ue] - \ + self.ue.thermal_noise[ue] def calculate_external_interference(self): """ Calculates interference that IMT system generates on other system """ - if self.co_channel: - self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system, - self.bs, - self.propagation_system) - + self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt( + self.system, + self.bs, + is_co_channel=True, + ) if self.adjacent_channel: - self.coupling_loss_imt_system_adjacent = self.calculate_coupling_loss(self.system, - self.bs, - self.propagation_system, - c_channel=False) + self.coupling_loss_imt_system_adjacent = \ + self.calculate_coupling_loss_system_imt( + self.system, + self.bs, + is_co_channel=False, + ) # applying a bandwidth scaling factor since UE transmits on a portion # of the interfered systems bandwidth @@ -177,96 +199,160 @@ def calculate_external_interference(self): bs_active = np.where(self.bs.active)[0] for bs in bs_active: - active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)] + active_beams = [ + i for i in range( + bs * + self.parameters.imt.ue.k, (bs + 1) * + self.parameters.imt.ue.k, + ) + ] if self.co_channel: if self.overlapping_bandwidth: acs = 0 - weights = self.calculate_bw_weights(self.parameters.imt.bandwidth, - self.param_system.bandwidth, - self.parameters.imt.ue_k) + weights = self.calculate_bw_weights( + self.parameters.imt.bandwidth, + self.param_system.bandwidth, + self.parameters.imt.ue.k, + ) else: acs = self.param_system.adjacent_ch_selectivity - weights = np.ones(self.parameters.imt.ue_k) + weights = np.ones(self.parameters.imt.ue.k) - interference = self.bs.tx_power[bs] - self.coupling_loss_imt_system[active_beams] - rx_interference += np.sum(weights*np.power(10, 0.1*interference)) / 10**(acs/10.) + interference = self.bs.tx_power[bs] - \ + self.coupling_loss_imt_system[active_beams] + rx_interference += np.sum( + weights * np.power( + 10, + 0.1 * interference, + ), + ) / 10**(acs / 10.) if self.adjacent_channel: - # The unwanted emission is calculated in terms of TRP (after - # antenna). In SHARC implementation, ohmic losses are already - # included in coupling loss. Then, care has to be taken; + # The unwanted emission is calculated in terms of TRP (after + # antenna). In SHARC implementation, ohmic losses are already + # included in coupling loss. Then, care has to be taken; # otherwise ohmic loss will be included twice. oob_power = self.bs.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth) \ - + self.parameters.imt.bs_ohmic_loss + + self.parameters.imt.bs.ohmic_loss oob_interference = oob_power \ - - self.coupling_loss_imt_system_adjacent[active_beams[0]] \ - + 10*np.log10((self.param_system.bandwidth - self.overlapping_bandwidth)/ - self.param_system.bandwidth) - - rx_interference += math.pow(10, 0.1*oob_interference) + - self.coupling_loss_imt_system_adjacent[active_beams[0]] \ + + 10 * np.log10( + (self.param_system.bandwidth - self.overlapping_bandwidth) / + self.param_system.bandwidth, + ) - self.system.rx_interference = 10*np.log10(rx_interference) + rx_interference += math.pow(10, 0.1 * oob_interference) + + # Total received interference - dBW + self.system.rx_interference = 10 * np.log10(rx_interference) # calculate N self.system.thermal_noise = \ - 10*math.log10(self.param_system.BOLTZMANN_CONSTANT* \ - self.system.noise_temperature*1e3) + \ - 10*math.log10(self.param_system.bandwidth * 1e6) + 10 * math.log10(BOLTZMANN_CONSTANT * self.system.noise_temperature * 1e3) + \ + 10 * math.log10(self.param_system.bandwidth * 1e6) - # calculate INR at the system - self.system.inr = np.array([self.system.rx_interference - self.system.thermal_noise]) + # Calculate INR at the system - dBm/MHz + self.system.inr = np.array( + [self.system.rx_interference - self.system.thermal_noise], + ) # Calculate PFD at the system - if self.system.station_type is StationType.RAS: - self.system.pfd = 10*np.log10(10**(self.system.rx_interference/10)/self.system.antenna[0].effective_area) + # TODO: generalize this a bit more if needed + if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1: + self.system.pfd = 10 * \ + np.log10( + 10**(self.system.rx_interference / 10) / + self.system.antenna[0].effective_area, + ) def collect_results(self, write_to_file: bool, snapshot_number: int): if not self.parameters.imt.interfered_with and np.any(self.bs.active): self.results.system_inr.extend(self.system.inr.tolist()) - self.results.system_dl_interf_power.extend([self.system.rx_interference]) - if self.system.station_type is StationType.RAS: + self.results.system_dl_interf_power.extend( + [self.system.rx_interference], + ) + # TODO: generalize this a bit more if needed (same conditional as above) + if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1: self.results.system_pfd.extend([self.system.pfd]) bs_active = np.where(self.bs.active)[0] for bs in bs_active: ue = self.link[bs] - self.results.imt_path_loss.extend(self.path_loss_imt[bs,ue]) - self.results.imt_coupling_loss.extend(self.coupling_loss_imt[bs,ue]) - - self.results.imt_bs_antenna_gain.extend(self.imt_bs_antenna_gain[bs,ue]) - self.results.imt_ue_antenna_gain.extend(self.imt_ue_antenna_gain[bs,ue]) - - tput = self.calculate_imt_tput(self.ue.sinr[ue], - self.parameters.imt.dl_sinr_min, - self.parameters.imt.dl_sinr_max, - self.parameters.imt.dl_attenuation_factor) + self.results.imt_path_loss.extend(self.path_loss_imt[bs, ue]) + self.results.imt_coupling_loss.extend( + self.coupling_loss_imt[bs, ue], + ) + + self.results.imt_bs_antenna_gain.extend( + self.imt_bs_antenna_gain[bs, ue], + ) + self.results.imt_ue_antenna_gain.extend( + self.imt_ue_antenna_gain[bs, ue], + ) + + tput = self.calculate_imt_tput( + self.ue.sinr[ue], + self.parameters.imt.downlink.sinr_min, + self.parameters.imt.downlink.sinr_max, + self.parameters.imt.downlink.attenuation_factor, + ) self.results.imt_dl_tput.extend(tput.tolist()) if self.parameters.imt.interfered_with: - tput_ext = self.calculate_imt_tput(self.ue.sinr_ext[ue], - self.parameters.imt.dl_sinr_min, - self.parameters.imt.dl_sinr_max, - self.parameters.imt.dl_attenuation_factor) + tput_ext = self.calculate_imt_tput( + self.ue.sinr_ext[ue], + self.parameters.imt.downlink.sinr_min, + self.parameters.imt.downlink.sinr_max, + self.parameters.imt.downlink.attenuation_factor, + ) self.results.imt_dl_tput_ext.extend(tput_ext.tolist()) - self.results.imt_dl_sinr_ext.extend(self.ue.sinr_ext[ue].tolist()) + self.results.imt_dl_sinr_ext.extend( + self.ue.sinr_ext[ue].tolist(), + ) self.results.imt_dl_inr.extend(self.ue.inr[ue].tolist()) - self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,ue]) - self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,ue]) - self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,ue]) + self.results.system_imt_antenna_gain.extend( + self.system_imt_antenna_gain[0, ue], + ) + self.results.imt_system_antenna_gain.extend( + self.imt_system_antenna_gain[0, ue], + ) + self.results.imt_system_path_loss.extend( + self.imt_system_path_loss[0, ue], + ) if self.param_system.channel_model == "HDFSS": - self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[0,ue]) - self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[0,ue]) + self.results.imt_system_build_entry_loss.extend( + self.imt_system_build_entry_loss[0, ue], + ) + self.results.imt_system_diffraction_loss.extend( + self.imt_system_diffraction_loss[0, ue], + ) else: - active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)] - self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,active_beams]) - self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,active_beams]) - self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,active_beams]) + active_beams = [ + i for i in range( + bs * + self.parameters.imt.ue.k, (bs + 1) * + self.parameters.imt.ue.k, + ) + ] + self.results.system_imt_antenna_gain.extend( + self.system_imt_antenna_gain[0, active_beams], + ) + self.results.imt_system_antenna_gain.extend( + self.imt_system_antenna_gain[0, active_beams], + ) + self.results.imt_system_path_loss.extend( + self.imt_system_path_loss[0, active_beams], + ) if self.param_system.channel_model == "HDFSS": - self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,bs]) - self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,bs]) + self.results.imt_system_build_entry_loss.extend( + self.imt_system_build_entry_loss[:, bs], + ) + self.results.imt_system_diffraction_loss.extend( + self.imt_system_diffraction_loss[:, bs], + ) self.results.imt_dl_tx_power.extend(self.bs.tx_power[bs].tolist()) @@ -276,4 +362,3 @@ def collect_results(self, write_to_file: bool, snapshot_number: int): if write_to_file: self.results.write_files(snapshot_number) self.notify_observers(source=__name__, results=self.results) - diff --git a/sharc/simulation_uplink.py b/sharc/simulation_uplink.py index 723b63d04..fde631245 100644 --- a/sharc/simulation_uplink.py +++ b/sharc/simulation_uplink.py @@ -11,9 +11,8 @@ from sharc.simulation import Simulation from sharc.parameters.parameters import Parameters from sharc.station_factory import StationFactory -from sharc.support.enumerations import StationType +from sharc.parameters.constants import BOLTZMANN_CONSTANT -from sharc.propagation.propagation_factory import PropagationFactory class SimulationUplink(Simulation): """ @@ -37,26 +36,33 @@ def snapshot(self, *args, **kwargs): # Create the base stations (remember that it takes into account the # network load factor) - self.bs = StationFactory.generate_imt_base_stations(self.parameters.imt, - self.parameters.antenna_imt, - self.topology, random_number_gen) + self.bs = StationFactory.generate_imt_base_stations( + self.parameters.imt, + self.parameters.imt.bs.antenna, + self.topology, random_number_gen, + ) # Create the other system (FSS, HAPS, etc...) - self.system = StationFactory.generate_system(self.parameters, self.topology, random_number_gen) + self.system = StationFactory.generate_system( + self.parameters, self.topology, random_number_gen, + ) # Create IMT user equipments - self.ue = StationFactory.generate_imt_ue(self.parameters.imt, - self.parameters.antenna_imt, - self.topology, random_number_gen) - #self.plot_scenario() + self.ue = StationFactory.generate_imt_ue( + self.parameters.imt, + self.parameters.imt.ue.antenna, + self.topology, random_number_gen, + ) + # self.plot_scenario() self.connect_ue_to_bs() self.select_ue(random_number_gen) # Calculate coupling loss after beams are created - self.coupling_loss_imt = self.calculate_coupling_loss(self.bs, - self.ue, - self.propagation_imt) + self.coupling_loss_imt = self.calculate_intra_imt_coupling_loss( + self.ue, + self.bs, + ) self.scheduler() self.power_control() @@ -65,44 +71,41 @@ def snapshot(self, *args, **kwargs): # interference into IMT self.calculate_sinr() self.calculate_sinr_ext() - #self.add_external_interference() - #self.recalculate_sinr() - #self.calculate_imt_degradation() - pass else: # Execute this piece of code if IMT generates interference into # the other system self.calculate_sinr() self.calculate_external_interference() - #self.calculate_external_degradation() - pass self.collect_results(write_to_file, snapshot_number) - def power_control(self): """ Apply uplink power control algorithm """ - if self.parameters.imt.ue_tx_power_control == "OFF": + if self.parameters.imt.ue.tx_power_control == "OFF": ue_active = np.where(self.ue.active)[0] - self.ue.tx_power[ue_active] = self.parameters.imt.ue_p_cmax * np.ones(len(ue_active)) + self.ue.tx_power[ue_active] = self.parameters.imt.ue.p_cmax * \ + np.ones(len(ue_active)) else: bs_active = np.where(self.bs.active)[0] for bs in bs_active: ue = self.link[bs] - p_cmax = self.parameters.imt.ue_p_cmax + p_cmax = self.parameters.imt.ue.p_cmax m_pusch = self.num_rb_per_ue - p_o_pusch = self.parameters.imt.ue_p_o_pusch - alpha = self.parameters.imt.ue_alpha - ue_power_dynamic_range = self.parameters.imt.ue_power_dynamic_range - cl = self.coupling_loss_imt[bs,ue] - self.ue.tx_power[ue] = np.minimum(p_cmax, 10*np.log10(m_pusch) + p_o_pusch + alpha*cl) + p_o_pusch = self.parameters.imt.ue.p_o_pusch + alpha = self.parameters.imt.ue.alpha + ue_power_dynamic_range = self.parameters.imt.ue.power_dynamic_range + cl = self.coupling_loss_imt[bs, ue] + self.ue.tx_power[ue] = np.minimum( + p_cmax, 10 * np.log10(m_pusch) + p_o_pusch + alpha * cl, + ) # apply the power dymanic range - self.ue.tx_power[ue] = np.maximum(self.ue.tx_power[ue], p_cmax - ue_power_dynamic_range) - if self.adjacent_channel: - self.ue_power_diff = self.parameters.imt.ue_p_cmax - self.ue.tx_power - + self.ue.tx_power[ue] = np.maximum( + self.ue.tx_power[ue], p_cmax - ue_power_dynamic_range, + ) + if self.adjacent_channel: + self.ue_power_diff = self.parameters.imt.ue.p_cmax - self.ue.tx_power def calculate_sinr(self): """ @@ -113,68 +116,93 @@ def calculate_sinr(self): for bs in bs_active: ue = self.link[bs] - self.bs.rx_power[bs] = self.ue.tx_power[ue] - self.coupling_loss_imt[bs,ue] + self.bs.rx_power[bs] = self.ue.tx_power[ue] - \ + self.coupling_loss_imt[bs, ue] # create a list of BSs that serve the interfering UEs bs_interf = [b for b in bs_active if b not in [bs]] # calculate intra system interference for bi in bs_interf: ui = self.link[bi] - interference = self.ue.tx_power[ui] - self.coupling_loss_imt[bs,ui] - self.bs.rx_interference[bs] = 10*np.log10( \ - np.power(10, 0.1*self.bs.rx_interference[bs]) - + np.power(10, 0.1*interference)) + interference = self.ue.tx_power[ui] - \ + self.coupling_loss_imt[bs, ui] + self.bs.rx_interference[bs] = 10 * np.log10( + np.power(10, 0.1 * self.bs.rx_interference[bs]) + + np.power(10, 0.1 * interference), + ) # calculate N + # thermal noise in dBm self.bs.thermal_noise[bs] = \ - 10*np.log10(self.parameters.imt.BOLTZMANN_CONSTANT*self.parameters.imt.noise_temperature*1e3) + \ - 10*np.log10(self.bs.bandwidth[bs] * 1e6) + \ + 10 * np.log10(BOLTZMANN_CONSTANT * self.parameters.imt.noise_temperature * 1e3) + \ + 10 * np.log10(self.bs.bandwidth[bs] * 1e6) + \ self.bs.noise_figure[bs] # calculate I+N self.bs.total_interference[bs] = \ - 10*np.log10(np.power(10, 0.1*self.bs.rx_interference[bs]) + \ - np.power(10, 0.1*self.bs.thermal_noise[bs])) + 10 * np.log10( + np.power(10, 0.1 * self.bs.rx_interference[bs]) + + np.power(10, 0.1 * self.bs.thermal_noise[bs]), + ) # calculate SNR and SINR - self.bs.sinr[bs] = self.bs.rx_power[bs] - self.bs.total_interference[bs] + self.bs.sinr[bs] = self.bs.rx_power[bs] - \ + self.bs.total_interference[bs] self.bs.snr[bs] = self.bs.rx_power[bs] - self.bs.thermal_noise[bs] - def calculate_sinr_ext(self): """ Calculates the downlink SINR for each UE taking into account the interference that is generated by the other system into IMT system. """ - self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system, - self.bs, - self.propagation_system) + self.coupling_loss_imt_system = \ + self.calculate_coupling_loss_system_imt( + self.system, + self.bs, + ) bs_active = np.where(self.bs.active)[0] - tx_power = self.param_system.tx_power_density + 10*np.log10(self.bs.bandwidth*1e6) + 30 + tx_power = self.param_system.tx_power_density + \ + 10 * np.log10(self.bs.bandwidth * 1e6) + 30 for bs in bs_active: - active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)] - self.bs.ext_interference[bs] = tx_power[bs] - self.coupling_loss_imt_system[active_beams] + active_beams = [ + i for i in range( + bs * self.parameters.imt.ue.k, (bs + 1) * self.parameters.imt.ue.k, + ) + ] + self.bs.ext_interference[bs] = tx_power[bs] - \ + self.coupling_loss_imt_system[active_beams] self.bs.sinr_ext[bs] = self.bs.rx_power[bs] \ - - (10*np.log10(np.power(10, 0.1*self.bs.total_interference[bs]) + np.power(10, 0.1*self.bs.ext_interference[bs]))) - self.bs.inr[bs] = self.bs.ext_interference[bs] - self.bs.thermal_noise[bs] - + - ( + 10 * np.log10( + np.power(10, 0.1 * self.bs.total_interference[bs]) + np.power( + 10, 0.1 * self.bs.ext_interference[bs], + ), + ) + ) + self.bs.inr[bs] = self.bs.ext_interference[bs] - \ + self.bs.thermal_noise[bs] def calculate_external_interference(self): """ - Calculates interference that IMT system generates on other system + Calculat + es interference that IMT system generates on other system """ - if self.co_channel: - self.coupling_loss_imt_system = self.calculate_coupling_loss(self.system, - self.ue, - self.propagation_system) + if self.co_channel: + self.coupling_loss_imt_system = self.calculate_coupling_loss_system_imt( + self.system, + self.ue, + is_co_channel=True, + ) if self.adjacent_channel: - self.coupling_loss_imt_system_adjacent = self.calculate_coupling_loss(self.system, - self.ue, - self.propagation_system, - c_channel=False) + self.coupling_loss_imt_system_adjacent = \ + self.calculate_coupling_loss_system_imt( + self.system, + self.ue, + is_co_channel=False, + ) # applying a bandwidth scaling factor since UE transmits on a portion # of the satellite's bandwidth @@ -188,99 +216,164 @@ def calculate_external_interference(self): if self.co_channel: if self.overlapping_bandwidth: acs = 0 - weights = self.calculate_bw_weights(self.parameters.imt.bandwidth, - self.param_system.bandwidth, - self.parameters.imt.ue_k) + weights = self.calculate_bw_weights( + self.parameters.imt.bandwidth, + self.param_system.bandwidth, + self.parameters.imt.ue.k, + ) else: acs = self.param_system.adjacent_ch_selectivity - weights = np.ones(self.parameters.imt.ue_k) + weights = np.ones(self.parameters.imt.ue.k) - interference_ue = self.ue.tx_power[ue] - self.coupling_loss_imt_system[ue] - rx_interference += np.sum(weights*np.power(10, 0.1*interference_ue)) / 10**(acs/10.) + interference_ue = self.ue.tx_power[ue] - \ + self.coupling_loss_imt_system[ue] + rx_interference += np.sum( + weights * np.power( + 10, + 0.1 * interference_ue, + ), + ) / 10**(acs / 10.) if self.adjacent_channel: - # The unwanted emission is calculated in terms of TRP (after - # antenna). In SHARC implementation, ohmic losses are already - # included in coupling loss. Then, care has to be taken; - # otherwise ohmic loss will be included twice. - oob_power = self.ue.spectral_mask.power_calc(self.param_system.frequency,self.system.bandwidth)\ - - self.ue_power_diff[ue] \ - + self.parameters.imt.ue_ohmic_loss + # The unwanted emission is calculated in terms of TRP (after + # antenna). In SHARC implementation, ohmic losses are already + # included in coupling loss. Then, care has to be taken; + # otherwise ohmic loss will be included twice. + oob_power = self.ue.spectral_mask.power_calc(self.param_system.frequency, self.system.bandwidth)\ + - self.ue_power_diff[ue] \ + + self.parameters.imt.ue.ohmic_loss oob_interference_array = oob_power - self.coupling_loss_imt_system_adjacent[ue] \ - + 10*np.log10((self.param_system.bandwidth - self.overlapping_bandwidth)/ - self.param_system.bandwidth) - rx_interference += np.sum(np.power(10,0.1*oob_interference_array)) - - self.system.rx_interference = 10*np.log10(rx_interference) + + 10 * np.log10( + (self.param_system.bandwidth - self.overlapping_bandwidth) / + self.param_system.bandwidth, + ) + rx_interference += np.sum( + np.power( + 10, + 0.1 * oob_interference_array, + ), + ) + + self.system.rx_interference = 10 * np.log10(rx_interference) # calculate N self.system.thermal_noise = \ - 10*np.log10(self.param_system.BOLTZMANN_CONSTANT* \ - self.system.noise_temperature*1e3) + \ - 10*math.log10(self.param_system.bandwidth * 1e6) + 10 * np.log10( + BOLTZMANN_CONSTANT * + self.system.noise_temperature * 1e3, + ) + \ + 10 * math.log10(self.param_system.bandwidth * 1e6) # calculate INR at the system - self.system.inr = np.array([self.system.rx_interference - self.system.thermal_noise]) + self.system.inr = np.array( + [self.system.rx_interference - self.system.thermal_noise], + ) # Calculate PFD at the system - if self.system.station_type is StationType.RAS: - self.system.pfd = 10*np.log10(10**(self.system.rx_interference/10)/self.system.antenna[0].effective_area) - + # TODO: generalize this a bit more if needed + if hasattr(self.system.antenna[0], "effective_area") and self.system.num_stations == 1: + self.system.pfd = 10 * \ + np.log10( + 10**(self.system.rx_interference / 10) / + self.system.antenna[0].effective_area, + ) def collect_results(self, write_to_file: bool, snapshot_number: int): if not self.parameters.imt.interfered_with and np.any(self.bs.active): self.results.system_inr.extend(self.system.inr.tolist()) - self.results.system_ul_interf_power.extend([self.system.rx_interference]) - if self.system.station_type is StationType.RAS: + self.results.system_ul_interf_power.extend( + [self.system.rx_interference], + ) + # 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]) bs_active = np.where(self.bs.active)[0] for bs in bs_active: ue = self.link[bs] - self.results.imt_path_loss.extend(self.path_loss_imt[bs,ue]) - self.results.imt_coupling_loss.extend(self.coupling_loss_imt[bs,ue]) - - self.results.imt_bs_antenna_gain.extend(self.imt_bs_antenna_gain[bs,ue]) - self.results.imt_ue_antenna_gain.extend(self.imt_ue_antenna_gain[bs,ue]) - - tput = self.calculate_imt_tput(self.bs.sinr[bs], - self.parameters.imt.ul_sinr_min, - self.parameters.imt.ul_sinr_max, - self.parameters.imt.ul_attenuation_factor) + self.results.imt_path_loss.extend(self.path_loss_imt[bs, ue]) + self.results.imt_coupling_loss.extend( + self.coupling_loss_imt[bs, ue], + ) + + self.results.imt_bs_antenna_gain.extend( + self.imt_bs_antenna_gain[bs, ue], + ) + self.results.imt_ue_antenna_gain.extend( + self.imt_ue_antenna_gain[bs, ue], + ) + + tput = self.calculate_imt_tput( + self.bs.sinr[bs], + self.parameters.imt.uplink.sinr_min, + self.parameters.imt.uplink.sinr_max, + self.parameters.imt.uplink.attenuation_factor, + ) self.results.imt_ul_tput.extend(tput.tolist()) if self.parameters.imt.interfered_with: - tput_ext = self.calculate_imt_tput(self.bs.sinr_ext[bs], - self.parameters.imt.ul_sinr_min, - self.parameters.imt.ul_sinr_max, - self.parameters.imt.ul_attenuation_factor) + tput_ext = self.calculate_imt_tput( + self.bs.sinr_ext[bs], + self.parameters.imt.uplink.sinr_min, + self.parameters.imt.uplink.sinr_max, + self.parameters.imt.uplink.attenuation_factor, + ) self.results.imt_ul_tput_ext.extend(tput_ext.tolist()) - self.results.imt_ul_sinr_ext.extend(self.bs.sinr_ext[bs].tolist()) + self.results.imt_ul_sinr_ext.extend( + self.bs.sinr_ext[bs].tolist(), + ) self.results.imt_ul_inr.extend(self.bs.inr[bs].tolist()) - active_beams = [i for i in range(bs*self.parameters.imt.ue_k, (bs+1)*self.parameters.imt.ue_k)] - self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,active_beams]) - self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,active_beams]) - self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,active_beams]) + active_beams = [ + i for i in range( + bs * self.parameters.imt.ue.k, (bs + 1) * self.parameters.imt.ue.k, + ) + ] + self.results.system_imt_antenna_gain.extend( + self.system_imt_antenna_gain[0, active_beams], + ) + self.results.imt_system_antenna_gain.extend( + self.imt_system_antenna_gain[0, active_beams], + ) + self.results.imt_system_path_loss.extend( + self.imt_system_path_loss[0, active_beams], + ) if self.param_system.channel_model == "HDFSS": - self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,bs]) - self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,bs]) + self.results.imt_system_build_entry_loss.extend( + self.imt_system_build_entry_loss[:, bs], + ) + self.results.imt_system_diffraction_loss.extend( + self.imt_system_diffraction_loss[:, bs], + ) else: - self.results.system_imt_antenna_gain.extend(self.system_imt_antenna_gain[0,ue]) - self.results.imt_system_antenna_gain.extend(self.imt_system_antenna_gain[0,ue]) - self.results.imt_system_path_loss.extend(self.imt_system_path_loss[0,ue]) + self.results.system_imt_antenna_gain.extend( + self.system_imt_antenna_gain[0, ue], + ) + self.results.imt_system_antenna_gain.extend( + self.imt_system_antenna_gain[0, ue], + ) + self.results.imt_system_path_loss.extend( + self.imt_system_path_loss[0, ue], + ) if self.param_system.channel_model == "HDFSS": - self.results.imt_system_build_entry_loss.extend(self.imt_system_build_entry_loss[:,ue]) - self.results.imt_system_diffraction_loss.extend(self.imt_system_diffraction_loss[:,ue]) + self.results.imt_system_build_entry_loss.extend( + self.imt_system_build_entry_loss[:, ue], + ) + self.results.imt_system_diffraction_loss.extend( + self.imt_system_diffraction_loss[:, ue], + ) self.results.imt_ul_tx_power.extend(self.ue.tx_power[ue].tolist()) - imt_ul_tx_power_density = 10*np.log10(np.power(10, 0.1*self.ue.tx_power[ue])/(self.num_rb_per_ue*self.parameters.imt.rb_bandwidth*1e6)) - self.results.imt_ul_tx_power_density.extend(imt_ul_tx_power_density.tolist()) + imt_ul_tx_power_density = 10 * np.log10( + np.power(10, 0.1 * self.ue.tx_power[ue]) / ( + self.num_rb_per_ue * self.parameters.imt.rb_bandwidth * 1e6 + ), + ) + self.results.imt_ul_tx_power_density.extend( + imt_ul_tx_power_density.tolist(), + ) self.results.imt_ul_sinr.extend(self.bs.sinr[bs].tolist()) self.results.imt_ul_snr.extend(self.bs.snr[bs].tolist()) if write_to_file: self.results.write_files(snapshot_number) self.notify_observers(source=__name__, results=self.results) - - - diff --git a/sharc/station.py b/sharc/station.py index e9ba8a06a..4e327f027 100644 --- a/sharc/station.py +++ b/sharc/station.py @@ -7,8 +7,9 @@ from sharc.support.enumerations import StationType + class Station(object): - + def __init__(self): self.id = -1 self.x = 0 @@ -33,29 +34,30 @@ def __init__(self): self.sinr_ext = 0 self.inr = 0 self.station_type = StationType.NONE - - + def __eq__(self, other): if isinstance(other, self.__class__): - equal = (self.id == other.id and + equal = ( + self.id == other.id and self.x == other.x and self.y == other.y and - self.height == other.height) + self.height == other.height + ) return equal else: return NotImplemented - def __ne__(self, other): if isinstance(other, self.__class__): - not_equal = (self.id != other.id or + not_equal = ( + self.id != other.id or self.x != other.x or self.y != other.y or - self.height != other.height) + self.height != other.height + ) return not_equal else: return NotImplemented - - + def __hash__(self): return hash(self.id, self.x, self.y, self.height) diff --git a/sharc/station_factory.py b/sharc/station_factory.py index 2e58dd7db..c1906a6ae 100644 --- a/sharc/station_factory.py +++ b/sharc/station_factory.py @@ -5,128 +5,187 @@ @author: edgar """ -import numpy as np -import sys import math +import sys +from warnings import warn + +import numpy as np -from sharc.support.enumerations import StationType -from sharc.parameters.parameters import Parameters -from sharc.parameters.parameters_imt import ParametersImt -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt -from sharc.parameters.parameters_eess_passive import ParametersEessPassive -from sharc.parameters.parameters_fs import ParametersFs -from sharc.parameters.parameters_fss_ss import ParametersFssSs -from sharc.parameters.parameters_fss_es import ParametersFssEs -from sharc.parameters.parameters_haps import ParametersHaps -from sharc.parameters.parameters_rns import ParametersRns -from sharc.parameters.parameters_ras import ParametersRas -from sharc.station_manager import StationManager -from sharc.mask.spectral_mask_imt import SpectralMaskImt from sharc.antenna.antenna import Antenna -from sharc.antenna.antenna_fss_ss import AntennaFssSs -from sharc.antenna.antenna_omni import AntennaOmni +from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt from sharc.antenna.antenna_f699 import AntennaF699 from sharc.antenna.antenna_f1891 import AntennaF1891 +from sharc.antenna.antenna_fss_ss import AntennaFssSs from sharc.antenna.antenna_m1466 import AntennaM1466 +from sharc.antenna.antenna_modified_s465 import AntennaModifiedS465 +from sharc.antenna.antenna_omni import AntennaOmni from sharc.antenna.antenna_rs1813 import AntennaRS1813 from sharc.antenna.antenna_rs1861_9a import AntennaRS1861_9A from sharc.antenna.antenna_rs1861_9b import AntennaRS1861_9B from sharc.antenna.antenna_rs1861_9c import AntennaRS1861_9C +from sharc.antenna.antenna_rs2043 import AntennaRS2043 from sharc.antenna.antenna_s465 import AntennaS465 -from sharc.antenna.antenna_modified_s465 import AntennaModifiedS465 from sharc.antenna.antenna_s580 import AntennaS580 from sharc.antenna.antenna_s672 import AntennaS672 from sharc.antenna.antenna_s1528 import AntennaS1528 from sharc.antenna.antenna_s1855 import AntennaS1855 from sharc.antenna.antenna_sa509 import AntennaSA509 -from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt +from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp +from sharc.mask.spectral_mask_imt import SpectralMaskImt +from sharc.parameters.constants import EARTH_RADIUS, SPEED_OF_LIGHT +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_imt import ParametersImt +from sharc.parameters.parameters import Parameters +from sharc.parameters.parameters_eess_ss import ParametersEessSS +from sharc.parameters.parameters_fs import ParametersFs +from sharc.parameters.parameters_fss_es import ParametersFssEs +from sharc.parameters.parameters_fss_ss import ParametersFssSs +from sharc.parameters.parameters_haps import ParametersHaps +from sharc.parameters.parameters_metsat_ss import ParametersMetSatSS +from sharc.parameters.parameters_ras import ParametersRas +from sharc.parameters.parameters_rns import ParametersRns +from sharc.parameters.parameters_single_earth_station import \ + ParametersSingleEarthStation +from sharc.parameters.parameters_space_station import ParametersSpaceStation +from sharc.station_manager import StationManager +from sharc.support.enumerations import StationType from sharc.topology.topology import Topology from sharc.topology.topology_macrocell import TopologyMacrocell -from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp class StationFactory(object): @staticmethod - def generate_imt_base_stations(param: ParametersImt, - param_ant: ParametersAntennaImt, - topology: Topology, - random_number_gen: np.random.RandomState): - par = param_ant.get_antenna_parameters(StationType.IMT_BS) + def generate_imt_base_stations( + param: ParametersImt, + param_ant_bs: ParametersAntennaImt, + topology: Topology, + random_number_gen: np.random.RandomState, + ): + param_ant = param_ant_bs.get_antenna_parameters() num_bs = topology.num_base_stations imt_base_stations = StationManager(num_bs) imt_base_stations.station_type = StationType.IMT_BS - # now we set the coordinates - imt_base_stations.x = topology.x - imt_base_stations.y = topology.y - imt_base_stations.azimuth = topology.azimuth - imt_base_stations.elevation = -par.downtilt*np.ones(num_bs) - if param.topology == 'INDOOR': - imt_base_stations.height = topology.height + 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.height = topology.space_station_z * \ + np.ones(num_bs) + imt_base_stations.elevation = topology.elevation + imt_base_stations.is_space_station = True else: - imt_base_stations.height = param.bs_height*np.ones(num_bs) - - imt_base_stations.active = random_number_gen.rand(num_bs) < param.bs_load_probability - imt_base_stations.tx_power = param.bs_conducted_power*np.ones(num_bs) - imt_base_stations.rx_power = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.rx_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.ext_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.total_interference = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - - imt_base_stations.snr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.sinr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.sinr_ext = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) - imt_base_stations.inr = dict([(bs, -500 * np.ones(param.ue_k)) for bs in range(num_bs)]) + imt_base_stations.x = topology.x + imt_base_stations.y = topology.y + imt_base_stations.elevation = -param_ant.downtilt * np.ones(num_bs) + if param.topology.type == 'INDOOR': + imt_base_stations.height = topology.height + else: + imt_base_stations.height = param.bs.height * np.ones(num_bs) - imt_base_stations.antenna = np.empty(num_bs, dtype=AntennaBeamformingImt) + imt_base_stations.azimuth = topology.azimuth + imt_base_stations.active = random_number_gen.rand( + num_bs, + ) < param.bs.load_probability + imt_base_stations.tx_power = param.bs.conducted_power * np.ones(num_bs) + imt_base_stations.rx_power = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.rx_interference = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.ext_interference = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.total_interference = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + + imt_base_stations.snr = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.sinr = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.sinr_ext = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + imt_base_stations.inr = dict( + [(bs, -500 * np.ones(param.ue.k)) for bs in range(num_bs)], + ) + + imt_base_stations.antenna = np.empty( + num_bs, dtype=AntennaBeamformingImt, + ) for i in range(num_bs): imt_base_stations.antenna[i] = \ - AntennaBeamformingImt(par, imt_base_stations.azimuth[i],\ - imt_base_stations.elevation[i]) + AntennaBeamformingImt( + param_ant, imt_base_stations.azimuth[i], + imt_base_stations.elevation[i],) - #imt_base_stations.antenna = [AntennaOmni(0) for bs in range(num_bs)] - imt_base_stations.bandwidth = param.bandwidth*np.ones(num_bs) - imt_base_stations.center_freq = param.frequency*np.ones(num_bs) - imt_base_stations.noise_figure = param.bs_noise_figure*np.ones(num_bs) - imt_base_stations.thermal_noise = -500*np.ones(num_bs) + # imt_base_stations.antenna = [AntennaOmni(0) for bs in range(num_bs)] + imt_base_stations.bandwidth = param.bandwidth * np.ones(num_bs) + imt_base_stations.center_freq = param.frequency * np.ones(num_bs) + imt_base_stations.noise_figure = param.bs.noise_figure * \ + np.ones(num_bs) + imt_base_stations.thermal_noise = -500 * np.ones(num_bs) if param.spectral_mask == "IMT-2020": - imt_base_stations.spectral_mask = SpectralMaskImt(StationType.IMT_BS, - param.frequency, - param.bandwidth, - param.spurious_emissions, - scenario = param.topology) + imt_base_stations.spectral_mask = SpectralMaskImt( + StationType.IMT_BS, + param.frequency, + param.bandwidth, + param.spurious_emissions, + scenario=param.topology.type, + ) elif param.spectral_mask == "3GPP E-UTRA": - imt_base_stations.spectral_mask = SpectralMask3Gpp(StationType.IMT_BS, - param.frequency, - param.bandwidth, - param.spurious_emissions) - - if param.topology == 'MACROCELL' or param.topology == 'HOTSPOT': - imt_base_stations.intesite_dist = param.intersite_distance + imt_base_stations.spectral_mask = SpectralMask3Gpp( + StationType.IMT_BS, + param.frequency, + param.bandwidth, + param.spurious_emissions, + ) + + if param.topology.type == 'MACROCELL': + imt_base_stations.intersite_dist = param.topology.macrocell.intersite_distance + elif param.topology.type == 'HOTSPOT': + imt_base_stations.intersite_dist = param.topology.hotspot.intersite_distance return imt_base_stations @staticmethod - def generate_imt_ue(param: ParametersImt, - param_ant: ParametersAntennaImt, - topology: Topology, - random_number_gen: np.random.RandomState)-> StationManager: - - if param.topology == "INDOOR": - return StationFactory.generate_imt_ue_indoor(param, param_ant, random_number_gen, topology) + def generate_imt_ue( + param: ParametersImt, + ue_param_ant: ParametersAntennaImt, + topology: Topology, + random_number_gen: np.random.RandomState, + ) -> StationManager: + + if param.topology.type == "INDOOR": + return StationFactory.generate_imt_ue_indoor(param, ue_param_ant, random_number_gen, topology) else: - return StationFactory.generate_imt_ue_outdoor(param, param_ant, random_number_gen, topology) + return StationFactory.generate_imt_ue_outdoor(param, ue_param_ant, random_number_gen, topology) + @staticmethod + def generate_ras_station( + param: ParametersRas, + random_number_gen: np.random.RandomState, + topology: Topology, + ) -> StationManager: + return StationFactory.generate_single_earth_station( + param, random_number_gen, + StationType.RAS, topology + ) @staticmethod - def generate_imt_ue_outdoor(param: ParametersImt, - param_ant: ParametersAntennaImt, - random_number_gen: np.random.RandomState, - topology: Topology) -> StationManager: + def generate_imt_ue_outdoor( + param: ParametersImt, + ue_param_ant: ParametersAntennaImt, + random_number_gen: np.random.RandomState, + topology: Topology, + ) -> StationManager: num_bs = topology.num_base_stations - num_ue_per_bs = param.ue_k*param.ue_k_m + num_ue_per_bs = param.ue.k * param.ue.k_m num_ue = num_bs * num_ue_per_bs @@ -135,35 +194,56 @@ def generate_imt_ue_outdoor(param: ParametersImt, ue_x = list() ue_y = list() + # TODO: Sanitaze the azimuth_range parameter + azimuth_range = param.ue.azimuth_range + if (not isinstance(azimuth_range, tuple)) or len(azimuth_range) != 2: + raise ValueError("Invalid type or length for parameter azimuth_range") # Calculate UE pointing - azimuth_range = (-60, 60) - azimuth = (azimuth_range[1] - azimuth_range[0])*random_number_gen.random_sample(num_ue) + azimuth_range[0] + azimuth = (azimuth_range[1] - azimuth_range[0]) * \ + random_number_gen.random_sample(num_ue) + azimuth_range[0] # Remove the randomness from azimuth and you will have a perfect pointing elevation_range = (-90, 90) - elevation = (elevation_range[1] - elevation_range[0])*random_number_gen.random_sample(num_ue) + \ - elevation_range[0] + elevation = (elevation_range[1] - elevation_range[0]) * random_number_gen.random_sample(num_ue) + \ + elevation_range[0] + + if param.ue.distribution_type.upper() == "UNIFORM" or \ + param.ue.distribution_type.upper() == "CELL" or \ + param.ue.distribution_type.upper() == "UNIFORM_IN_CELL": + + deterministic_cell = False + central_cell = False + + if param.ue.distribution_type.upper() == "UNIFORM_IN_CELL" or \ + param.ue.distribution_type.upper() == "CELL": + deterministic_cell = True - if param.ue_distribution_type.upper() == "UNIFORM": + if param.ue.distribution_type.upper() == "CELL": + central_cell = True if not (type(topology) is TopologyMacrocell): - sys.stderr.write("ERROR\nUniform UE distribution is currently supported only with Macrocell topology") + sys.stderr.write( + "ERROR\nUniform UE distribution is currently supported only with Macrocell topology", + ) sys.exit(1) - [ue_x, ue_y, theta, distance] = StationFactory.get_random_position(num_ue, topology, random_number_gen, - param.minimum_separation_distance_bs_ue, - deterministic_cell=True) - psi = np.degrees(np.arctan((param.bs_height - param.ue_height) / distance)) + [ue_x, ue_y, theta, distance] = StationFactory.get_random_position( + num_ue, topology, random_number_gen, + param.minimum_separation_distance_bs_ue, + deterministic_cell=True, + ) + psi = np.degrees( + np.arctan((param.bs.height - param.ue.height) / distance), + ) - imt_ue.azimuth = (azimuth + theta + np.pi/2) + imt_ue.azimuth = (azimuth + theta + np.pi / 2) imt_ue.elevation = elevation + psi - - elif param.ue_distribution_type.upper() == "ANGLE_AND_DISTANCE": + elif param.ue.distribution_type.upper() == "ANGLE_AND_DISTANCE": # The Rayleigh and Normal distribution parameters (mean, scale and cutoff) # were agreed in TG 5/1 meeting (May 2017). - if param.ue_distribution_distance.upper() == "RAYLEIGH": + if param.ue.distribution_distance.upper() == "RAYLEIGH": # For the distance between UE and BS, it is desired that 99% of UE's # are located inside the [soft] cell edge, i.e. Prob(d -angle_cutoff))[0][:num_ue] + angle_n = random_number_gen.normal( + angle_mean, angle_scale, int(N * num_ue), + ) + + angle_cutoff = np.max(azimuth_range) + idx = np.where((angle_n < angle_cutoff) & ( + angle_n > -angle_cutoff + ))[0][:num_ue] angle = angle_n[idx] - elif param.ue_distribution_azimuth.upper() == "UNIFORM": + elif param.ue.distribution_azimuth.upper() == "UNIFORM": azimuth_range = (-60, 60) angle = (azimuth_range[1] - azimuth_range[0]) * random_number_gen.random_sample(num_ue) \ - + azimuth_range[0] + + azimuth_range[0] else: - sys.stderr.write("ERROR\nInvalid UE azimuth distribution: " + param.ue_distribution_distance) + sys.stderr.write( + "ERROR\nInvalid UE azimuth distribution: " + param.ue.distribution_distance, + ) sys.exit(1) for bs in range(num_bs): - idx = [i for i in range(bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs)] + idx = [ + i for i in range( + bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs, + ) + ] # theta is the horizontal angle of the UE wrt the serving BS theta = topology.azimuth[bs] + angle[idx] # calculate UE position in x-y coordinates @@ -214,62 +307,79 @@ def generate_imt_ue_outdoor(param: ParametersImt, # calculate elevation angle # psi is the vertical angle of the UE wrt the serving BS - distance = np.sqrt((topology.x[bs] - x) ** 2 + (topology.y[bs] - y) ** 2) - psi = np.degrees(np.arctan((param.bs_height - param.ue_height) / distance)) + distance = np.sqrt( + (topology.x[bs] - x) ** 2 + (topology.y[bs] - y) ** 2, + ) + psi = np.degrees( + np.arctan((param.bs.height - param.ue.height) / distance), + ) imt_ue.elevation[idx] = elevation[idx] + psi else: - sys.stderr.write("ERROR\nInvalid UE distribution type: " + param.ue_distribution_type) + sys.stderr.write( + "ERROR\nInvalid UE distribution type: " + param.ue.distribution_type, + ) sys.exit(1) imt_ue.x = np.array(ue_x) imt_ue.y = np.array(ue_y) imt_ue.active = np.zeros(num_ue, dtype=bool) - imt_ue.height = param.ue_height*np.ones(num_ue) - imt_ue.indoor = random_number_gen.random_sample(num_ue) <= (param.ue_indoor_percent/100) - imt_ue.rx_interference = -500*np.ones(num_ue) - imt_ue.ext_interference = -500*np.ones(num_ue) + imt_ue.height = param.ue.height * np.ones(num_ue) + imt_ue.indoor = random_number_gen.random_sample( + num_ue, + ) <= (param.ue.indoor_percent / 100) + imt_ue.rx_interference = -500 * np.ones(num_ue) + imt_ue.ext_interference = -500 * np.ones(num_ue) # TODO: this piece of code works only for uplink - par = param_ant.get_antenna_parameters(StationType.IMT_UE) + par = ue_param_ant.get_antenna_parameters() for i in range(num_ue): - imt_ue.antenna[i] = AntennaBeamformingImt(par, imt_ue.azimuth[i], - imt_ue.elevation[i]) + imt_ue.antenna[i] = AntennaBeamformingImt( + par, imt_ue.azimuth[i], + imt_ue.elevation[i], + ) - #imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)] - imt_ue.bandwidth = param.bandwidth*np.ones(num_ue) - imt_ue.center_freq = param.frequency*np.ones(num_ue) - imt_ue.noise_figure = param.ue_noise_figure*np.ones(num_ue) + # imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)] + imt_ue.bandwidth = param.bandwidth * np.ones(num_ue) + imt_ue.center_freq = param.frequency * np.ones(num_ue) + imt_ue.noise_figure = param.ue.noise_figure * np.ones(num_ue) if param.spectral_mask == "IMT-2020": - imt_ue.spectral_mask = SpectralMaskImt(StationType.IMT_UE, - param.frequency, - param.bandwidth, - param.spurious_emissions, - scenario = "OUTDOOR") + imt_ue.spectral_mask = SpectralMaskImt( + StationType.IMT_UE, + param.frequency, + param.bandwidth, + param.spurious_emissions, + scenario="OUTDOOR", + ) elif param.spectral_mask == "3GPP E-UTRA": - imt_ue.spectral_mask = SpectralMask3Gpp(StationType.IMT_UE, - param.frequency, - param.bandwidth, - param.spurious_emissions) + imt_ue.spectral_mask = SpectralMask3Gpp( + StationType.IMT_UE, + param.frequency, + param.bandwidth, + param.spurious_emissions, + ) imt_ue.spectral_mask.set_mask() - if param.topology == 'MACROCELL' or param.topology == 'HOTSPOT': - imt_ue.intersite_dist = param.intersite_distance + if param.topology.type == 'MACROCELL': + imt_ue.intersite_dist = param.topology.macrocell.intersite_distance + elif param.topology.type == 'HOTSPOT': + imt_ue.intersite_dist = param.topology.hotspot.intersite_distance return imt_ue - @staticmethod - def generate_imt_ue_indoor(param: ParametersImt, - param_ant: ParametersAntennaImt, - random_number_gen: np.random.RandomState, - topology: Topology) -> StationManager: + def generate_imt_ue_indoor( + param: ParametersImt, + ue_param_ant: ParametersAntennaImt, + random_number_gen: np.random.RandomState, + topology: Topology, + ) -> StationManager: num_bs = topology.num_base_stations - num_ue_per_bs = param.ue_k*param.ue_k_m - num_ue = num_bs*num_ue_per_bs + num_ue_per_bs = param.ue.k * param.ue.k_m + num_ue = num_bs * num_ue_per_bs imt_ue = StationManager(num_ue) imt_ue.station_type = StationType.IMT_UE @@ -282,17 +392,27 @@ def generate_imt_ue_indoor(param: ParametersImt, # Calculate UE pointing azimuth_range = (-60, 60) - azimuth = (azimuth_range[1] - azimuth_range[0])*random_number_gen.random_sample(num_ue) + azimuth_range[0] + azimuth = (azimuth_range[1] - azimuth_range[0]) * \ + random_number_gen.random_sample(num_ue) + azimuth_range[0] # Remove the randomness from azimuth and you will have a perfect pointing - #azimuth = np.zeros(num_ue) + # azimuth = np.zeros(num_ue) elevation_range = (-90, 90) - elevation = (elevation_range[1] - elevation_range[0])*random_number_gen.random_sample(num_ue) + elevation_range[0] + elevation = (elevation_range[1] - elevation_range[0]) * \ + random_number_gen.random_sample(num_ue) + elevation_range[0] - delta_x = (topology.b_w/math.sqrt(topology.ue_indoor_percent) - topology.b_w)/2 - delta_y = (topology.b_d/math.sqrt(topology.ue_indoor_percent) - topology.b_d)/2 + delta_x = ( + topology.b_w / math.sqrt(topology.ue_indoor_percent) - topology.b_w + ) / 2 + delta_y = ( + topology.b_d / math.sqrt(topology.ue_indoor_percent) - topology.b_d + ) / 2 for bs in range(num_bs): - idx = [i for i in range(bs*num_ue_per_bs, bs*num_ue_per_bs + num_ue_per_bs)] + idx = [ + i for i in range( + bs * num_ue_per_bs, bs * num_ue_per_bs + num_ue_per_bs, + ) + ] # Right most cell of first floor if bs % topology.num_cells == 0 and bs < topology.total_bs_level: x_min = topology.x[bs] - topology.cell_radius - delta_x @@ -308,43 +428,56 @@ def generate_imt_ue_indoor(param: ParametersImt, # First floor if bs < topology.total_bs_level: - y_min = topology.y[bs] - topology.b_d/2 - delta_y - y_max = topology.y[bs] + topology.b_d/2 + delta_y + y_min = topology.y[bs] - topology.b_d / 2 - delta_y + y_max = topology.y[bs] + topology.b_d / 2 + delta_y # Higher floors else: - y_min = topology.y[bs] - topology.b_d/2 - y_max = topology.y[bs] + topology.b_d/2 - - x = (x_max - x_min)*random_number_gen.random_sample(num_ue_per_bs) + x_min - y = (y_max - y_min)*random_number_gen.random_sample(num_ue_per_bs) + y_min - z = [topology.height[bs] - topology.b_h + param.ue_height for k in range(num_ue_per_bs)] + y_min = topology.y[bs] - topology.b_d / 2 + y_max = topology.y[bs] + topology.b_d / 2 + + x = (x_max - x_min) * \ + random_number_gen.random_sample(num_ue_per_bs) + x_min + y = (y_max - y_min) * \ + random_number_gen.random_sample(num_ue_per_bs) + y_min + z = [ + topology.height[bs] - topology.b_h + + param.ue.height for k in range(num_ue_per_bs) + ] ue_x.extend(x) ue_y.extend(y) ue_z.extend(z) # theta is the horizontal angle of the UE wrt the serving BS - theta = np.degrees(np.arctan2(y - topology.y[bs], x - topology.x[bs])) + theta = np.degrees( + np.arctan2( + y - topology.y[bs], x - topology.x[bs], + ), + ) # calculate UE azimuth wrt serving BS - imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180)%360 + imt_ue.azimuth[idx] = (azimuth[idx] + theta + 180) % 360 # calculate elevation angle # psi is the vertical angle of the UE wrt the serving BS - distance = np.sqrt((topology.x[bs] - x)**2 + (topology.y[bs] - y)**2) - psi = np.degrees(np.arctan((param.bs_height - param.ue_height)/distance)) + distance = np.sqrt( + (topology.x[bs] - x)**2 + (topology.y[bs] - y)**2, + ) + psi = np.degrees( + np.arctan((param.bs.height - param.ue.height) / distance), + ) imt_ue.elevation[idx] = elevation[idx] + psi # check if UE is indoor if bs % topology.num_cells == 0: out = (x < topology.x[bs] - topology.cell_radius) | \ - (y > topology.y[bs] + topology.b_d/2) | \ - (y < topology.y[bs] - topology.b_d/2) + (y > topology.y[bs] + topology.b_d / 2) | \ + (y < topology.y[bs] - topology.b_d / 2) elif bs % topology.num_cells == topology.num_cells - 1: out = (x > topology.x[bs] + topology.cell_radius) | \ - (y > topology.y[bs] + topology.b_d/2) | \ - (y < topology.y[bs] - topology.b_d/2) + (y > topology.y[bs] + topology.b_d / 2) | \ + (y < topology.y[bs] - topology.b_d / 2) else: - out = (y > topology.y[bs] + topology.b_d/2) | \ - (y < topology.y[bs] - topology.b_d/2) + out = (y > topology.y[bs] + topology.b_d / 2) | \ + (y < topology.y[bs] - topology.b_d / 2) imt_ue.indoor[idx] = ~ out imt_ue.x = np.array(ue_x) @@ -352,102 +485,140 @@ def generate_imt_ue_indoor(param: ParametersImt, imt_ue.height = np.array(ue_z) imt_ue.active = np.zeros(num_ue, dtype=bool) - imt_ue.rx_interference = -500*np.ones(num_ue) - imt_ue.ext_interference = -500*np.ones(num_ue) + imt_ue.rx_interference = -500 * np.ones(num_ue) + imt_ue.ext_interference = -500 * np.ones(num_ue) # TODO: this piece of code works only for uplink - par = param_ant.get_antenna_parameters(StationType.IMT_UE) + par = ue_param_ant.get_antenna_parameters() for i in range(num_ue): - imt_ue.antenna[i] = AntennaBeamformingImt(par, imt_ue.azimuth[i], - imt_ue.elevation[i]) + imt_ue.antenna[i] = AntennaBeamformingImt( + par, imt_ue.azimuth[i], + imt_ue.elevation[i], + ) - #imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)] - imt_ue.bandwidth = param.bandwidth*np.ones(num_ue) - imt_ue.center_freq = param.frequency*np.ones(num_ue) - imt_ue.noise_figure = param.ue_noise_figure*np.ones(num_ue) + # imt_ue.antenna = [AntennaOmni(0) for bs in range(num_ue)] + imt_ue.bandwidth = param.bandwidth * np.ones(num_ue) + imt_ue.center_freq = param.frequency * np.ones(num_ue) + imt_ue.noise_figure = param.ue.noise_figure * np.ones(num_ue) if param.spectral_mask == "IMT-2020": - imt_ue.spectral_mask = SpectralMaskImt(StationType.IMT_UE, - param.frequency, - param.bandwidth, - param.spurious_emissions, - scenario = "INDOOR") + imt_ue.spectral_mask = SpectralMaskImt( + StationType.IMT_UE, + param.frequency, + param.bandwidth, + param.spurious_emissions, + scenario="INDOOR", + ) elif param.spectral_mask == "3GPP E-UTRA": - imt_ue.spectral_mask = SpectralMask3Gpp(StationType.IMT_UE, - param.frequency, - param.bandwidth, - param.spurious_emissions) + imt_ue.spectral_mask = SpectralMask3Gpp( + StationType.IMT_UE, + param.frequency, + param.bandwidth, + param.spurious_emissions, + ) imt_ue.spectral_mask.set_mask() return imt_ue - @staticmethod - def generate_system(parameters: Parameters, topology: Topology, random_number_gen: np.random.RandomState ): - if parameters.general.system == "EESS_PASSIVE": - return StationFactory.generate_eess_passive_sensor(parameters.eess_passive) - if parameters.general.system == "FSS_ES": + def generate_system(parameters: Parameters, topology: Topology, random_number_gen: np.random.RandomState): + if parameters.imt.topology.type == 'MACROCELL': + intersite_dist = parameters.imt.topology.macrocell.intersite_distance + elif parameters.imt.topology.type == 'HOTSPOT': + intersite_dist = parameters.imt.topology.hotspot.intersite_distance + + if parameters.general.system == "METSAT_SS": + return StationFactory.generate_metsat_ss(parameters.metsat_ss) + elif parameters.general.system == "EESS_SS": + return StationFactory.generate_eess_space_station(parameters.eess_ss) + elif parameters.general.system == "FSS_ES": return StationFactory.generate_fss_earth_station(parameters.fss_es, random_number_gen, topology) + elif parameters.general.system == "SINGLE_EARTH_STATION": + return StationFactory.generate_single_earth_station(parameters.single_earth_station, random_number_gen, + StationType.SINGLE_EARTH_STATION, topology) + elif parameters.general.system == "RAS": + return StationFactory.generate_ras_station( + parameters.ras, random_number_gen, + topology + ) elif parameters.general.system == "FSS_SS": return StationFactory.generate_fss_space_station(parameters.fss_ss) elif parameters.general.system == "FS": return StationFactory.generate_fs_station(parameters.fs) elif parameters.general.system == "HAPS": - return StationFactory.generate_haps(parameters.haps, parameters.imt.intersite_distance, random_number_gen) + return StationFactory.generate_haps(parameters.haps, intersite_dist, random_number_gen) elif parameters.general.system == "RNS": return StationFactory.generate_rns(parameters.rns, random_number_gen) - elif parameters.general.system == "RAS": - return StationFactory.generate_ras_station(parameters.ras) else: - sys.stderr.write("ERROR\nInvalid system: " + parameters.general.system) + sys.stderr.write( + "ERROR\nInvalid system: " + + parameters.general.system, + ) sys.exit(1) - @staticmethod def generate_fss_space_station(param: ParametersFssSs): fss_space_station = StationManager(1) fss_space_station.station_type = StationType.FSS_SS + fss_space_station.is_space_station = True # now we set the coordinates according to # ITU-R P619-1, Attachment A # calculate distances to the centre of the Earth - dist_sat_centre_earth_km = (param.EARTH_RADIUS + param.altitude)/1000 - dist_imt_centre_earth_km = (param.EARTH_RADIUS + param.imt_altitude)/1000 + dist_sat_centre_earth_km = (EARTH_RADIUS + param.altitude) / 1000 + dist_imt_centre_earth_km = ( + EARTH_RADIUS + param.earth_station_alt_m + ) / 1000 # calculate Cartesian coordinates of satellite, with origin at centre of the Earth sat_lat_rad = param.lat_deg * np.pi / 180. - imt_long_diff_rad = param.imt_long_diff_deg * np.pi / 180. - x1 = dist_sat_centre_earth_km * np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad) - y1 = dist_sat_centre_earth_km * np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad) + imt_long_diff_rad = param.earth_station_long_diff_deg * np.pi / 180. + x1 = dist_sat_centre_earth_km * \ + np.cos(sat_lat_rad) * np.cos(imt_long_diff_rad) + y1 = dist_sat_centre_earth_km * \ + np.cos(sat_lat_rad) * np.sin(imt_long_diff_rad) z1 = dist_sat_centre_earth_km * np.sin(sat_lat_rad) # rotate axis and calculate coordinates with origin at IMT system - imt_lat_rad = param.imt_lat_deg * np.pi / 180. - fss_space_station.x = np.array([x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)]) * 1000 + imt_lat_rad = param.earth_station_lat_deg * np.pi / 180. + fss_space_station.x = np.array( + [x1 * np.sin(imt_lat_rad) - z1 * np.cos(imt_lat_rad)], + ) * 1000 fss_space_station.y = np.array([y1]) * 1000 - fss_space_station.height = np.array([(z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad) - - dist_imt_centre_earth_km) * 1000]) + fss_space_station.height = np.array([ + ( + z1 * np.sin(imt_lat_rad) + x1 * np.cos(imt_lat_rad) - + dist_imt_centre_earth_km + ) * 1000, + ]) fss_space_station.azimuth = param.azimuth fss_space_station.elevation = param.elevation fss_space_station.active = np.array([True]) - fss_space_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30]) + fss_space_station.tx_power = np.array( + [param.tx_power_density + 10 * + math.log10(param.bandwidth * 1e6) + 30], + ) fss_space_station.rx_interference = -500 if param.antenna_pattern == "OMNI": - fss_space_station.antenna = np.array([AntennaOmni(param.antenna_gain)]) + fss_space_station.antenna = np.array( + [AntennaOmni(param.antenna_gain)], + ) elif param.antenna_pattern == "ITU-R S.672": fss_space_station.antenna = np.array([AntennaS672(param)]) elif param.antenna_pattern == "ITU-R S.1528": - fss_space_station.antenna = np.array([AntennaS1528(param)]) + fss_space_station.antenna = np.array([AntennaS1528(param.antenna_s1528)]) elif param.antenna_pattern == "FSS_SS": fss_space_station.antenna = np.array([AntennaFssSs(param)]) else: - sys.stderr.write("ERROR\nInvalid FSS SS antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid FSS SS antenna pattern: " + param.antenna_pattern, + ) sys.exit(1) fss_space_station.bandwidth = param.bandwidth @@ -457,11 +628,14 @@ def generate_fss_space_station(param: ParametersFssSs): return fss_space_station - @staticmethod - def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.random.RandomState, *args): """ + @deprecated + + Since this creates a Single Earth Station, you should use StationFactory.generate_single_earth_station instead. + This will be deleted in the future. + ---------------------------------- Generates FSS Earth Station. Arguments: @@ -469,7 +643,13 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran random_number_gen: np.random.RandomState topology (optional): Topology """ - if len(args): topology = args[0] + warn( + "This is deprecated, use StationFactory.generate_single_earth_station() instead; date=2024-10-11", + DeprecationWarning, stacklevel=2, + ) + + if len(args): + topology = args[0] fss_earth_station = StationManager(1) fss_earth_station.station_type = StationType.FSS_ES @@ -478,31 +658,46 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran fss_earth_station.x = np.array([param.x]) fss_earth_station.y = np.array([param.y]) elif param.location.upper() == "CELL": - x, y, dummy1, dummy2 = StationFactory.get_random_position(1, topology, random_number_gen, - param.min_dist_to_bs, True) + x, y, _, _ = StationFactory.get_random_position( + 1, topology, random_number_gen, + param.min_dist_to_bs, True, + ) fss_earth_station.x = np.array(x) fss_earth_station.y = np.array(y) elif param.location.upper() == "NETWORK": - x, y, dummy1, dummy2 = StationFactory.get_random_position(1, topology, random_number_gen, - param.min_dist_to_bs, False) + x, y, _, _ = StationFactory.get_random_position( + 1, topology, random_number_gen, + param.min_dist_to_bs, False, + ) fss_earth_station.x = np.array(x) fss_earth_station.y = np.array(y) elif param.location.upper() == "UNIFORM_DIST": # FSS ES is randomly (uniform) created inside a circle of radius # equal to param.max_dist_to_bs if param.min_dist_to_bs < 0: - sys.stderr.write("ERROR\nInvalid minimum distance from FSS ES to BS: {}".format(param.min_dist_to_bs)) + sys.stderr.write( + "ERROR\nInvalid minimum distance from FSS ES to BS: {}".format( + param.min_dist_to_bs, + ), + ) sys.exit(1) - while(True): - dist_x = random_number_gen.uniform(-param.max_dist_to_bs, param.max_dist_to_bs) - dist_y = random_number_gen.uniform(-param.max_dist_to_bs, param.max_dist_to_bs) + while (True): + dist_x = random_number_gen.uniform( + -param.max_dist_to_bs, param.max_dist_to_bs, + ) + dist_y = random_number_gen.uniform( + -param.max_dist_to_bs, param.max_dist_to_bs, + ) radius = np.sqrt(dist_x**2 + dist_y**2) if (radius > param.min_dist_to_bs) & (radius < param.max_dist_to_bs): break fss_earth_station.x[0] = dist_x fss_earth_station.y[0] = dist_y else: - sys.stderr.write("ERROR\nFSS-ES location type {} not supported".format(param.location)) + sys.stderr.write( + "ERROR\nFSS-ES location type {} not supported".format( + param.location), + ) sys.exit(1) fss_earth_station.height = np.array([param.height]) @@ -512,15 +707,22 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran else: fss_earth_station.azimuth = float(param.azimuth) - elevation = random_number_gen.uniform(param.elevation_min, param.elevation_max) + elevation = random_number_gen.uniform( + param.elevation_min, param.elevation_max, + ) fss_earth_station.elevation = np.array([elevation]) fss_earth_station.active = np.array([True]) - fss_earth_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30]) + fss_earth_station.tx_power = np.array( + [param.tx_power_density + 10 * + math.log10(param.bandwidth * 1e6) + 30], + ) fss_earth_station.rx_interference = -500 if param.antenna_pattern.upper() == "OMNI": - fss_earth_station.antenna = np.array([AntennaOmni(param.antenna_gain)]) + fss_earth_station.antenna = np.array( + [AntennaOmni(param.antenna_gain)], + ) elif param.antenna_pattern.upper() == "ITU-R S.1855": fss_earth_station.antenna = np.array([AntennaS1855(param)]) elif param.antenna_pattern.upper() == "ITU-R S.465": @@ -530,7 +732,9 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran elif param.antenna_pattern.upper() == "ITU-R S.580": fss_earth_station.antenna = np.array([AntennaS580(param)]) else: - sys.stderr.write("ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern, + ) sys.exit(1) fss_earth_station.noise_temperature = param.noise_temperature @@ -541,9 +745,169 @@ def generate_fss_earth_station(param: ParametersFssEs, random_number_gen: np.ran return fss_earth_station + @staticmethod + def generate_single_earth_station( + param: ParametersSingleEarthStation, random_number_gen: np.random.RandomState, + station_type=StationType.SINGLE_EARTH_STATION, topology=None, + ): + """ + Generates a Single Earth Station. + + Arguments: + param: ParametersSingleEarthStation + random_number_gen: np.random.RandomState + topology (optional): Topology + """ + single_earth_station = StationManager(1) + single_earth_station.station_type = StationType.SINGLE_EARTH_STATION + + match param.geometry.location.type: + case "FIXED": + single_earth_station.x = np.array( + [param.geometry.location.fixed.x], + ) + single_earth_station.y = np.array( + [param.geometry.location.fixed.y], + ) + case "CELL": + x, y, _, _ = StationFactory.get_random_position( + 1, topology, random_number_gen, + param.geometry.location.cell.min_dist_to_bs, True, + ) + single_earth_station.x = np.array(x) + single_earth_station.y = np.array(y) + case "NETWORK": + x, y, _, _ = StationFactory.get_random_position( + 1, topology, random_number_gen, + param.geometry.location.network.min_dist_to_bs, False, + ) + single_earth_station.x = np.array(x) + single_earth_station.y = np.array(y) + case "UNIFORM_DIST": + # ES is randomly (uniform) created inside a circle of radius + # equal to param.max_dist_to_bs + if param.geometry.location.uniform_dist.min_dist_to_bs < 0: + sys.stderr.write( + "ERROR\nInvalid minimum distance from Single ES to BS: {}".format( + param.geometry.location.uniform_dist.min_dist_to_bs, + ), + ) + sys.exit(1) + while (True): + dist_x = random_number_gen.uniform( + -param.geometry.location.uniform_dist.max_dist_to_bs, + param.geometry.location.uniform_dist.max_dist_to_bs, + ) + dist_y = random_number_gen.uniform( + -param.geometry.location.uniform_dist.max_dist_to_bs, + param.geometry.location.uniform_dist.max_dist_to_bs, + ) + radius = np.sqrt(dist_x**2 + dist_y**2) + if (radius > param.geometry.location.uniform_dist.min_dist_to_bs) & \ + (radius < param.geometry.location.uniform_dist.max_dist_to_bs): + break + single_earth_station.x[0] = dist_x + single_earth_station.y[0] = dist_y + case _: + sys.stderr.write( + "ERROR\nSingle-ES location type {} not supported".format( + param.geometry.location.type), + ) + sys.exit(1) + + single_earth_station.height = np.array([param.geometry.height]) + + if param.geometry.azimuth.type == "UNIFORM_DIST": + if param.geometry.azimuth.uniform_dist.min < -180: + sys.stderr.write( + "ERROR\nInvalid minimum azimuth: {} < -180".format( + param.geometry.azimuth.uniform_dist.min), + ) + sys.exit(1) + if param.geometry.azimuth.uniform_dist.max > 180: + sys.stderr.write( + "ERROR\nInvalid maximum azimuth: {} > 180".format( + param.geometry.azimuth.uniform_dist.max, + ), + ) + sys.exit(1) + single_earth_station.azimuth = np.array([ + random_number_gen.uniform( + param.geometry.azimuth.uniform_dist.min, param.geometry.azimuth.uniform_dist.max, + ), + ]) + else: + single_earth_station.azimuth = np.array( + [param.geometry.azimuth.fixed], + ) + + if param.geometry.elevation.type == "UNIFORM_DIST": + single_earth_station.elevation = np.array([ + random_number_gen.uniform( + param.geometry.elevation.uniform_dist.min, param.geometry.elevation.uniform_dist.max, + ), + ]) + else: + single_earth_station.elevation = np.array( + [param.geometry.elevation.fixed], + ) + + match param.antenna.pattern: + case "OMNI": + single_earth_station.antenna = np.array( + [AntennaOmni(param.antenna.gain)], + ) + case "ITU-R S.465": + single_earth_station.antenna = np.array( + [AntennaS465(param.antenna.itu_r_s_465)], + ) + case "ITU-R S.1855": + single_earth_station.antenna = np.array( + [AntennaS1855(param.antenna.itu_r_s_1855)], + ) + case "MODIFIED ITU-R S.465": + single_earth_station.antenna = np.array( + [AntennaModifiedS465(param.antenna.itu_r_s_465_modified)], + ) + case "ITU-R S.580": + single_earth_station.antenna = np.array( + [AntennaS580(param.antenna.itu_r_s_580)], + ) + case _: + sys.stderr.write( + "ERROR\nInvalid FSS ES antenna pattern: " + param.antenna_pattern, + ) + sys.exit(1) + + single_earth_station.active = np.array([True]) + single_earth_station.bandwidth = np.array([param.bandwidth]) + + single_earth_station.tx_power = np.array( + [param.tx_power_density + 10 * + math.log10(param.bandwidth * 1e6) + 30], + ) + + single_earth_station.noise_temperature = param.noise_temperature + + # TODO: check why this would not be set on the StationManager() constructor itself? + single_earth_station.rx_interference = -500 + single_earth_station.thermal_noise = -500 + single_earth_station.total_interference = -500 + + return single_earth_station @staticmethod def generate_fs_station(param: ParametersFs): + """ + @deprecated + Since this creates a Single Earth Station, you should use StationFactory.generate_single_earth_station instead. + This will be deleted in the future + """ + warn( + "This is deprecated, use StationFactory.generate_single_earth_station() instead; date=2024-10-11", + DeprecationWarning, stacklevel=2, + ) + fs_station = StationManager(1) fs_station.station_type = StationType.FS @@ -555,7 +919,10 @@ def generate_fs_station(param: ParametersFs): fs_station.elevation = np.array([param.elevation]) fs_station.active = np.array([True]) - fs_station.tx_power = np.array([param.tx_power_density + 10*math.log10(param.bandwidth*1e6) + 30]) + fs_station.tx_power = np.array( + [param.tx_power_density + 10 * + math.log10(param.bandwidth * 1e6) + 30], + ) fs_station.rx_interference = -500 if param.antenna_pattern == "OMNI": @@ -563,7 +930,9 @@ def generate_fs_station(param: ParametersFs): elif param.antenna_pattern == "ITU-R F.699": fs_station.antenna = np.array([AntennaF699(param)]) else: - sys.stderr.write("ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid FS antenna pattern: " + param.antenna_pattern, + ) sys.exit(1) fs_station.noise_temperature = param.noise_temperature @@ -571,12 +940,12 @@ def generate_fs_station(param: ParametersFs): return fs_station - @staticmethod - def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_gen: np.random.RandomState()): + def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_gen: np.random.RandomState): num_haps = 1 haps = StationManager(num_haps) haps.station_type = StationType.HAPS + haps.is_space_station = True # d = intersite_distance # h = (d/3)*math.sqrt(3)/2 @@ -587,12 +956,12 @@ def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_ haps.height = param.altitude * np.ones(num_haps) - elev_max = 68.19 # corresponds to 50 km radius and 20 km altitude + elev_max = 68.19 # corresponds to 50 km radius and 20 km altitude haps.azimuth = 360 * random_number_gen.random_sample(num_haps) haps.elevation = ((270 + elev_max) - (270 - elev_max)) * random_number_gen.random_sample(num_haps) + \ (270 - elev_max) - haps.active = np.ones(num_haps, dtype = bool) + haps.active = np.ones(num_haps, dtype=bool) haps.antenna = np.empty(num_haps, dtype=Antenna) @@ -603,19 +972,22 @@ def generate_haps(param: ParametersHaps, intersite_distance: int, random_number_ for i in range(num_haps): haps.antenna[i] = AntennaF1891(param) else: - sys.stderr.write("ERROR\nInvalid HAPS (airbone) antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid HAPS (airbone) antenna pattern: " + + param.antenna_pattern, + ) sys.exit(1) haps.bandwidth = np.array([param.bandwidth]) return haps - @staticmethod - def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState()): + def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState): num_rns = 1 rns = StationManager(num_rns) rns.station_type = StationType.RNS + rns.is_space_station = True rns.x = np.array([param.x]) rns.y = np.array([param.y]) @@ -625,17 +997,23 @@ def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState( azimuth = np.array([-30, 30]) elevation = np.array([-30, 5]) - rns.azimuth = 90 + (azimuth[1] - azimuth[0]) * random_number_gen.random_sample(num_rns) + azimuth[0] - rns.elevation = (elevation[1] - elevation[0]) * random_number_gen.random_sample(num_rns) + elevation[0] + rns.azimuth = 90 + (azimuth[1] - azimuth[0]) * \ + random_number_gen.random_sample(num_rns) + azimuth[0] + rns.elevation = (elevation[1] - elevation[0]) * \ + random_number_gen.random_sample(num_rns) + elevation[0] - rns.active = np.ones(num_rns, dtype = bool) + rns.active = np.ones(num_rns, dtype=bool) if param.antenna_pattern == "OMNI": rns.antenna = np.array([AntennaOmni(param.antenna_gain)]) elif param.antenna_pattern == "ITU-R M.1466": - rns.antenna = np.array([AntennaM1466(param.antenna_gain, rns.azimuth, rns.elevation)]) + rns.antenna = np.array( + [AntennaM1466(param.antenna_gain, rns.azimuth, rns.elevation)], + ) else: - sys.stderr.write("ERROR\nInvalid RNS antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid RNS antenna pattern: " + param.antenna_pattern, + ) sys.exit(1) rns.bandwidth = np.array([param.bandwidth]) @@ -646,151 +1024,197 @@ def generate_rns(param: ParametersRns, random_number_gen: np.random.RandomState( return rns - @staticmethod - def generate_ras_station(param: ParametersRas): - ras_station = StationManager(1) - ras_station.station_type = StationType.RAS - - ras_station.x = np.array([param.x]) - ras_station.y = np.array([param.y]) - ras_station.height = np.array([param.height]) - - ras_station.azimuth = np.array([param.azimuth]) - ras_station.elevation = np.array([param.elevation]) - - ras_station.active = np.array([True]) - ras_station.rx_interference = -500 - - if param.antenna_pattern == "OMNI": - ras_station.antenna = np.array([AntennaOmni(param.antenna_gain)]) - ras_station.antenna[0].effective_area = param.SPEED_OF_LIGHT**2/(4*np.pi*(param.frequency*1e6)**2) - elif param.antenna_pattern == "ITU-R SA.509": - ras_station.antenna = np.array([AntennaSA509(param)]) - else: - sys.stderr.write("ERROR\nInvalid RAS antenna pattern: " + param.antenna_pattern) - sys.exit(1) - - ras_station.noise_temperature = np.array(param.antenna_noise_temperature + \ - param.receiver_noise_temperature) - ras_station.bandwidth = np.array(param.bandwidth) - - return ras_station + def generate_eess_space_station(param: ParametersEessSS): + if param.distribution_enable: + if param.distribution_type == "UNIFORM": + param.nadir_angle = np.random.uniform( + param.nadir_angle_distribution[0], + param.nadir_angle_distribution[1], + ) + return StationFactory.generate_space_station(param, StationType.EESS_SS) @staticmethod - def generate_eess_passive_sensor(param: ParametersEessPassive): - eess_passive_sensor = StationManager(1) - eess_passive_sensor.station_type = StationType.EESS_PASSIVE + def generate_metsat_ss(param: ParametersMetSatSS): + return StationFactory.generate_space_station(param, StationType.METSAT_SS) - # incidence angle according to Rec. ITU-R RS.1861-0 - incidence_angle = math.degrees(math.asin( - math.sin(math.radians(param.nadir_angle))*(1 + (param.altitude/param.EARTH_RADIUS)))) + @staticmethod + def generate_space_station(param: ParametersSpaceStation, station_type: StationType): + # this method uses off-nadir angle and altitude to infer the entire geometry + # TODO: make this usable on more space station cases (initially only works for metsat and eess) + space_station = StationManager(1) + space_station.is_space_station = True + space_station.station_type = station_type + # assert param.station_type is not None + + incidence_angle = math.degrees( + math.asin( + math.sin(math.radians(param.nadir_angle)) * + (1 + (param.altitude / EARTH_RADIUS)), + ), + ) # distance to field of view centre according to Rec. ITU-R RS.1861-0 - distance = param.EARTH_RADIUS * \ - math.sin(math.radians(incidence_angle - param.nadir_angle)) / \ - math.sin(math.radians(param.nadir_angle)) + distance = EARTH_RADIUS * \ + math.sin(math.radians(incidence_angle - param.nadir_angle)) / \ + math.sin(math.radians(param.nadir_angle)) # Elevation at ground (centre of the footprint) theta_grd_elev = 90 - incidence_angle - eess_passive_sensor.x = np.array([0]) - eess_passive_sensor.y = np.array([distance * math.cos(math.radians(theta_grd_elev))]) - eess_passive_sensor.height = np.array([distance * math.sin(math.radians(theta_grd_elev))]) + space_station.x = np.array([0]) + space_station.y = np.array( + [distance * math.cos(math.radians(theta_grd_elev))], + ) + space_station.height = np.array( + [distance * math.sin(math.radians(theta_grd_elev))], + ) # Elevation and azimuth at sensor wrt centre of the footprint # It is assumed the sensor is at y-axis, hence azimuth is 270 deg - eess_passive_sensor.azimuth = 270 - eess_passive_sensor.elevation = -theta_grd_elev + space_station.azimuth = 270 + space_station.elevation = -theta_grd_elev - eess_passive_sensor.active = np.array([True]) - eess_passive_sensor.rx_interference = -500 + space_station.active = np.array([True]) + space_station.rx_interference = -500 if param.antenna_pattern == "OMNI": - eess_passive_sensor.antenna = np.array([AntennaOmni(param.antenna_gain)]) + space_station.antenna = np.array([AntennaOmni(param.antenna_gain)]) elif param.antenna_pattern == "ITU-R RS.1813": - eess_passive_sensor.antenna = np.array([AntennaRS1813(param)]) + space_station.antenna = np.array([AntennaRS1813(param)]) elif param.antenna_pattern == "ITU-R RS.1861 9a": - eess_passive_sensor.antenna = np.array([AntennaRS1861_9A(param)]) + space_station.antenna = np.array([AntennaRS1861_9A(param)]) elif param.antenna_pattern == "ITU-R RS.1861 9b": - eess_passive_sensor.antenna = np.array([AntennaRS1861_9B(param)]) + space_station.antenna = np.array([AntennaRS1861_9B(param)]) elif param.antenna_pattern == "ITU-R RS.1861 9c": - eess_passive_sensor.antenna = np.array([AntennaRS1861_9C()]) + space_station.antenna = np.array([AntennaRS1861_9C()]) + elif param.antenna_pattern == "ITU-R RS.2043": + space_station.antenna = np.array([AntennaRS2043()]) + elif param.antenna_pattern == "ITU-R S.672": + space_station.antenna = np.array([AntennaS672(param)]) else: - sys.stderr.write("ERROR\nInvalid EESS PASSIVE antenna pattern: " + param.antenna_pattern) + sys.stderr.write( + "ERROR\nInvalid EESS PASSIVE antenna pattern: " + param.antenna_pattern, + ) sys.exit(1) - eess_passive_sensor.bandwidth = param.bandwidth - # Noise temperature is not an input parameter for EESS passive. + space_station.bandwidth = param.bandwidth + # Noise temperature is not an input parameter for yet used systems. # It is included here to calculate the useless I/N values - eess_passive_sensor.noise_temperature = 250 - eess_passive_sensor.thermal_noise = -500 - eess_passive_sensor.total_interference = -500 - - return eess_passive_sensor - + space_station.noise_temperature = 500 + return space_station @staticmethod - def get_random_position( num_stas: int, topology: Topology, - random_number_gen: np.random.RandomState, - min_dist_to_bs = 0, central_cell = False, - deterministic_cell = False): + def get_random_position(num_stas: int, + topology: Topology, + random_number_gen: np.random.RandomState, + min_dist_to_bs=0., + central_cell=False, + deterministic_cell=False): + """ + Generate UE random-possitions inside the topolgy area. + + Parameters + ---------- + num_stas : int + Number of UE stations + topology : Topology + The IMT topology object + random_number_gen : np.random.RandomState + Random number generator + min_dist_to_bs : _type_, optional + Minimum distance to the BS, by default 0. + central_cell : bool, optional + Whether the central cell in the cluster is used, by default False + deterministic_cell : bool, optional + Fix the cell to be used as anchor point, by default False + + Returns + ------- + tuple + x, y, azimuth and elevation angles. + """ hexagon_radius = topology.intersite_distance / 3 - min_dist_ok = False + x = np.array([]) + y = np.array([]) + bs_x = -hexagon_radius + bs_y = 0 - while not min_dist_ok: + while len(x) < num_stas: + num_stas_temp = num_stas - len(x) # generate UE uniformly in a triangle - x = random_number_gen.uniform(0, hexagon_radius * np.cos(np.pi / 6), num_stas) - y = random_number_gen.uniform(0, hexagon_radius / 2, num_stas) + x_temp = random_number_gen.uniform(0, hexagon_radius * np.cos(np.pi / 6), num_stas_temp) + y_temp = random_number_gen.uniform(0, hexagon_radius / 2, num_stas_temp) - invert_index = np.arctan(y / x) > np.pi / 6 - y[invert_index] = -(hexagon_radius / 2 - y[invert_index]) - x[invert_index] = (hexagon_radius * np.cos(np.pi / 6) - x[invert_index]) + invert_index = np.arctan(y_temp / x_temp) > np.pi / 6 + y_temp[invert_index] = -(hexagon_radius / 2 - y_temp[invert_index]) + x_temp[invert_index] = (hexagon_radius * np.cos(np.pi / 6) - x_temp[invert_index]) - if any (np.sqrt(x**2 + y**2) < min_dist_to_bs): - min_dist_ok = False - else: - min_dist_ok = True + # randomly choose a hextant + hextant = random_number_gen.random_integers(0, 5, num_stas_temp) + hextant_angle = np.pi / 6 + np.pi / 3 * hextant + + old_x = x_temp + x_temp = x_temp * np.cos(hextant_angle) - y_temp * np.sin(hextant_angle) + y_temp = old_x * np.sin(hextant_angle) + y_temp * np.cos(hextant_angle) + + dist = np.sqrt((x_temp - bs_x) ** 2 + (y_temp - bs_y) ** 2) + indices = dist > min_dist_to_bs - # randomly choose an hextant - hextant = random_number_gen.random_integers(0, 5, num_stas) - hextant_angle = np.pi / 6 + np.pi / 3 * hextant + x_temp = x_temp[indices] + y_temp = y_temp[indices] - old_x = x - x = x * np.cos(hextant_angle) - y * np.sin(hextant_angle) - y = old_x * np.sin(hextant_angle) + y * np.cos(hextant_angle) + x = np.append(x, x_temp) + y = np.append(y, y_temp) - # randomly choose a cell + x = x - bs_x + y = y - bs_y + + # choose cells if central_cell: central_cell_indices = np.where((topology.x == 0) & (topology.y == 0)) + + if not len(central_cell_indices[0]): + sys.stderr.write("ERROR\nTopology does not have a central cell") + sys.exit(1) + cell = central_cell_indices[0][random_number_gen.random_integers(0, len(central_cell_indices[0]) - 1, num_stas)] elif deterministic_cell: num_bs = topology.num_base_stations stas_per_cell = num_stas / num_bs cell = np.repeat(np.arange(num_bs, dtype=int), stas_per_cell) - else: + + else: # random cells num_bs = topology.num_base_stations cell = random_number_gen.random_integers(0, num_bs - 1, num_stas) cell_x = topology.x[cell] cell_y = topology.y[cell] - x = x + cell_x + hexagon_radius * np.cos(topology.azimuth[cell] * np.pi / 180) - y = y + cell_y + hexagon_radius * np.sin(topology.azimuth[cell] * np.pi / 180) + azimuth_rad = topology.azimuth * np.pi / 180 + + # rotqte + x_old = x + x = cell_x + x * np.cos(azimuth_rad[cell]) - y * np.sin(azimuth_rad[cell]) + y = cell_y + x_old * np.sin(azimuth_rad[cell]) + y * np.cos(azimuth_rad[cell]) x = list(x) y = list(y) # calculate UE azimuth wrt serving BS - theta = np.arctan2(y - cell_y, x - cell_x) + if topology.is_space_station is False: + theta = np.arctan2(y - cell_y, x - cell_x) - # calculate elevation angle - # psi is the vertical angle of the UE wrt the serving BS - distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2) + # calculate elevation angle + # psi is the vertical angle of the UE wrt the serving BS + distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2) + else: + theta = np.arctan2(y - topology.space_station_y[cell], x - topology.space_station_x[cell]) + distance = np.sqrt((cell_x - x) ** 2 + (cell_y - y) ** 2 + (topology.bs.height)**2) return x, y, theta, distance @@ -809,58 +1233,63 @@ def __init__(self): self.spectral_mask = 'IMT-2020' self.frequency = 10000 self.topology = 'MACROCELL' - self.ue_distribution_type = "UNIFORM" + self.ue_distribution_type = "UNIFORM_IN_CELL" self.bs_height = 30 self.ue_height = 3 self.ue_indoor_percent = 0 self.ue_k = 3 self.ue_k_m = 1 - self.bandwidth = np.random.rand() + self.bandwidth = np.random.rand() self.ue_noise_figure = np.random.rand() - self.minimum_separation_distance_bs_ue = 10 + self.minimum_separation_distance_bs_ue = 200 self.spurious_emissions = -30 self.intersite_distance = 1000 params = ParamsAux() - ant_param = ParametersAntennaImt() - - ant_param.adjacent_antenna_model = "SINGLE_ELEMENT" - ant_param.bs_element_pattern = "F1336" - ant_param.bs_element_max_g = 5 - ant_param.bs_element_phi_3db = 65 - ant_param.bs_element_theta_3db = 65 - ant_param.bs_element_am = 30 - ant_param.bs_element_sla_v = 30 - ant_param.bs_n_rows = 8 - ant_param.bs_n_columns = 8 - ant_param.bs_element_horiz_spacing = 0.5 - ant_param.bs_element_vert_spacing = 0.5 - ant_param.bs_downtilt_deg = 10 - ant_param.bs_multiplication_factor = 12 - ant_param.bs_minimum_array_gain = -200 - - ant_param.ue_element_pattern = "FIXED" - ant_param.ue_element_max_g = 5 - ant_param.ue_element_phi_3db = 90 - ant_param.ue_element_theta_3db = 90 - ant_param.ue_element_am = 25 - ant_param.ue_element_sla_v = 25 - ant_param.ue_n_rows = 4 - ant_param.ue_n_columns = 4 - ant_param.ue_element_horiz_spacing = 0.5 - ant_param.ue_element_vert_spacing = 0.5 - ant_param.ue_multiplication_factor = 12 - ant_param.ue_minimum_array_gain = -200 - - ant_param.ue_normalization = False - ant_param.bs_normalization = False + bs_ant_param = ParametersAntennaImt() + + bs_ant_param.adjacent_antenna_model = "SINGLE_ELEMENT" + bs_ant_param.element_pattern = "F1336" + bs_ant_param.element_max_g = 5 + bs_ant_param.element_phi_3db = 65 + bs_ant_param.element_theta_3db = 65 + bs_ant_param.element_am = 30 + bs_ant_param.element_sla_v = 30 + bs_ant_param.n_rows = 8 + bs_ant_param.n_columns = 8 + bs_ant_param.element_horiz_spacing = 0.5 + bs_ant_param.element_vert_spacing = 0.5 + bs_ant_param.downtilt = 10 + bs_ant_param.multiplication_factor = 12 + bs_ant_param.minimum_array_gain = -200 + + ue_ant_param = ParametersAntennaImt() + + ue_ant_param.element_pattern = "FIXED" + ue_ant_param.element_max_g = 5 + ue_ant_param.element_phi_3db = 90 + ue_ant_param.element_theta_3db = 90 + ue_ant_param.element_am = 25 + ue_ant_param.element_sla_v = 25 + ue_ant_param.n_rows = 4 + ue_ant_param.n_columns = 4 + ue_ant_param.element_horiz_spacing = 0.5 + ue_ant_param.element_vert_spacing = 0.5 + ue_ant_param.multiplication_factor = 12 + ue_ant_param.minimum_array_gain = -200 + + ue_ant_param.normalization = False + bs_ant_param.normalization = False rnd = np.random.RandomState(1) - imt_ue = factory.generate_imt_ue(params, ant_param, topology, rnd) + imt_ue = factory.generate_imt_ue(params, ue_ant_param, topology, rnd) - fig = plt.figure(figsize=(8, 8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure topology.plot(ax) diff --git a/sharc/station_manager.py b/sharc/station_manager.py index d83fecdb8..339ceff58 100644 --- a/sharc/station_manager.py +++ b/sharc/station_manager.py @@ -6,14 +6,13 @@ """ import numpy as np -import math from sharc.support.enumerations import StationType from sharc.station import Station from sharc.antenna.antenna import Antenna -from sharc.mask.spectral_mask_imt import SpectralMaskImt from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp + class StationManager(object): """ This is the base class that manages an array of stations that will be @@ -32,10 +31,10 @@ def __init__(self, n): self.active = np.ones(n, dtype=bool) self.tx_power = np.empty(n) self.rx_power = np.empty(n) - self.rx_interference = np.empty(n) - self.ext_interference = np.empty(n) + self.rx_interference = np.empty(n) # Rx interferece in dBW + self.ext_interference = np.empty(n) # External interferece in dBW self.antenna = np.empty(n, dtype=Antenna) - self.bandwidth = np.empty(n) + self.bandwidth = np.empty(n) # Bandwidth in MHz self.noise_figure = np.empty(n) self.noise_temperature = np.empty(n) self.thermal_noise = np.empty(n) @@ -43,16 +42,17 @@ def __init__(self, n): self.snr = np.empty(n) self.sinr = np.empty(n) self.sinr_ext = np.empty(n) - self.inr = np.empty(n) - self.pfd = np.empty(n) + self.inr = np.empty(n) # INR in dBm/MHz + self.pfd = np.empty(n) # Powerflux density in dBm/m^2 self.spectral_mask = np.empty(n, dtype=SpectralMask3Gpp) self.center_freq = np.empty(n) self.spectral_mask = None self.station_type = StationType.NONE + self.is_space_station = False self.intersite_dist = 0.0 def get_station_list(self, id=None) -> list: - if(id is None): + if (id is None): id = range(self.num_stations) station_list = list() for i in id: @@ -89,18 +89,22 @@ def get_station(self, id) -> Station: def get_distance_to(self, station) -> np.array: distance = np.empty([self.num_stations, station.num_stations]) for i in range(self.num_stations): - distance[i] = np.sqrt(np.power(self.x[i] - station.x, 2) + - np.power(self.y[i] - station.y, 2)) + distance[i] = np.sqrt( + np.power(self.x[i] - station.x, 2) + + np.power(self.y[i] - station.y, 2), + ) return distance def get_3d_distance_to(self, station) -> np.array: distance = np.empty([self.num_stations, station.num_stations]) for i in range(self.num_stations): - distance[i] = np.sqrt(np.power(self.x[i] - station.x, 2) + - np.power(self.y[i] - station.y, 2) + - np.power(self.height[i] - station.height, 2)) + distance[i] = np.sqrt( + np.power(self.x[i] - station.x, 2) + + np.power(self.y[i] - station.y, 2) + + np.power(self.height[i] - station.height, 2), + ) return distance - + def get_dist_angles_wrap_around(self, station) -> np.array: """ Calcualtes distances and angles using the wrap around technique @@ -115,112 +119,122 @@ def get_dist_angles_wrap_around(self, station) -> np.array: """ # Initialize variables distance_3D = np.empty([self.num_stations, station.num_stations]) - distance_2D = np.inf*np.ones_like(distance_3D) + distance_2D = np.inf * np.ones_like(distance_3D) cluster_num = np.zeros_like(distance_3D, dtype=int) - + # Cluster coordinates - cluster_x = np.array([station.x, - station.x + 3.5*self.intersite_dist, - station.x - 0.5*self.intersite_dist, - station.x - 4.0*self.intersite_dist, - station.x - 3.5*self.intersite_dist, - station.x + 0.5*self.intersite_dist, - station.x + 4.0*self.intersite_dist]) - - cluster_y = np.array([station.y, - station.y + 1.5*np.sqrt(3.0)*self.intersite_dist, - station.y + 2.5*np.sqrt(3.0)*self.intersite_dist, - station.y + 1.0*np.sqrt(3.0)*self.intersite_dist, - station.y - 1.5*np.sqrt(3.0)*self.intersite_dist, - station.y - 2.5*np.sqrt(3.0)*self.intersite_dist, - station.y - 1.0*np.sqrt(3.0)*self.intersite_dist]) - + cluster_x = np.array([ + station.x, + station.x + 3.5 * self.intersite_dist, + station.x - 0.5 * self.intersite_dist, + station.x - 4.0 * self.intersite_dist, + station.x - 3.5 * self.intersite_dist, + station.x + 0.5 * self.intersite_dist, + station.x + 4.0 * self.intersite_dist, + ]) + + cluster_y = np.array([ + station.y, + station.y + 1.5 * + np.sqrt(3.0) * self.intersite_dist, + station.y + 2.5 * + np.sqrt(3.0) * self.intersite_dist, + station.y + 1.0 * + np.sqrt(3.0) * self.intersite_dist, + station.y - 1.5 * + np.sqrt(3.0) * self.intersite_dist, + station.y - 2.5 * + np.sqrt(3.0) * self.intersite_dist, + station.y - 1.0 * np.sqrt(3.0) * self.intersite_dist, + ]) + # Calculate 2D distance temp_distance = np.zeros_like(distance_2D) - for k,(x,y) in enumerate(zip(cluster_x,cluster_y)): - temp_distance = np.sqrt(np.power(x - self.x[:,np.newaxis], 2) + - np.power(y - self.y[:,np.newaxis], 2)) + for k, (x, y) in enumerate(zip(cluster_x, cluster_y)): + temp_distance = np.sqrt( + np.power(x - self.x[:, np.newaxis], 2) + + np.power(y - self.y[:, np.newaxis], 2), + ) is_shorter = temp_distance < distance_2D distance_2D[is_shorter] = temp_distance[is_shorter] cluster_num[is_shorter] = k - + # Calculate 3D distance - distance_3D = np.sqrt(np.power(distance_2D, 2) + - np.power(station.height - self.height[:,np.newaxis], 2)) - + distance_3D = np.sqrt( + np.power(distance_2D, 2) + + np.power(station.height - self.height[:, np.newaxis], 2), + ) + # Calcualte pointing vector - point_vec_x = cluster_x[cluster_num,np.arange(station.num_stations)] \ - - self.x[:,np.newaxis] - point_vec_y = cluster_y[cluster_num,np.arange(station.num_stations)] \ - - self.y[:,np.newaxis] - point_vec_z = station.height - self.height[:,np.newaxis] - - phi = np.array(np.rad2deg(np.arctan2(point_vec_y,point_vec_x)),ndmin=2) - theta = np.rad2deg(np.arccos(point_vec_z/distance_3D)) - + point_vec_x = cluster_x[cluster_num, np.arange(station.num_stations)] \ + - self.x[:, np.newaxis] + point_vec_y = cluster_y[cluster_num, np.arange(station.num_stations)] \ + - self.y[:, np.newaxis] + point_vec_z = station.height - self.height[:, np.newaxis] + + phi = np.array( + np.rad2deg( + np.arctan2( + point_vec_y, point_vec_x, + ), + ), ndmin=2, + ) + theta = np.rad2deg(np.arccos(point_vec_z / distance_3D)) + return distance_2D, distance_3D, phi, theta def get_elevation(self, station) -> np.array: """ Calculates the elevation angle between stations. Can be used for IMT stations. - - TODO: this implementation is essentialy the same as the one from + + TODO: this implementation is essentialy the same as the one from get_elevation_angle (free-space elevation angle), despite the - different matrix dimentions. So, the methods should be merged + different matrix dimentions. So, the methods should be merged in order to reuse the source code """ elevation = np.empty([self.num_stations, station.num_stations]) for i in range(self.num_stations): - distance = np.sqrt(np.power(self.x[i] - station.x, 2) + - np.power(self.y[i] - station.y, 2)) + distance = np.sqrt( + np.power(self.x[i] - station.x, 2) + + np.power(self.y[i] - station.y, 2), + ) rel_z = station.height - self.height[i] elevation[i] = np.degrees(np.arctan2(rel_z, distance)) - - return elevation - - - def get_elevation_angle(self, station, sat_params) -> dict: - free_space_angle = np.empty(self.num_stations) - angle = np.empty(self.num_stations) - for i in range(self.num_stations): - # calculate free-space elevation angle according to Attachment A - rel_x = station.x - self.x[i] - rel_y = station.y - self.y[i] - rel_z = station.height - self.height[i] - - gts = np.sqrt(rel_x**2 + rel_y**2) - theta_0 = np.arctan2(rel_z, gts) # free-space elevation angle - free_space_angle[i] = np.degrees(theta_0) - - ## - # calculate apparent elevation angle according to ITU-R P619, Attachment B - tau_fs1 = 1.728 + 0.5411 * theta_0 + 0.03723 * theta_0**2 - tau_fs2 = 0.1815 + 0.06272 * theta_0 + 0.01380 * theta_0**2 - tau_fs3 = 0.01727 + 0.008288 * theta_0 - - # change in elevation angle due to refraction - tau_fs_deg = 1/(tau_fs1 + sat_params.altitude*tau_fs2 + - sat_params.altitude**2*tau_fs3) - tau_fs = tau_fs_deg / 180. * np.pi + return elevation - angle[i] = np.degrees(theta_0 + tau_fs) + def get_pointing_vector_to(self, station) -> tuple: + """calculate the pointing vector (angles) w.r.t. the other station - return{'free_space': free_space_angle, 'apparent': angle} + Parameters + ---------- + station : StationManager + The other station to calculate the pointing vector - def get_pointing_vector_to(self, station) -> tuple: + Returns + ------- + tuple + phi, theta (phi is calculated with respect to x counter-clock-wise and + theta is calculated with respect to z counter-clock-wise) + """ - point_vec_x = station.x- self.x[:,np.newaxis] - point_vec_y = station.y - self.y[:,np.newaxis] - point_vec_z = station.height - self.height[:,np.newaxis] + point_vec_x = station.x - self.x[:, np.newaxis] + point_vec_y = station.y - self.y[:, np.newaxis] + point_vec_z = station.height - self.height[:, np.newaxis] dist = self.get_3d_distance_to(station) - phi = np.array(np.rad2deg(np.arctan2(point_vec_y,point_vec_x)),ndmin=2) - theta = np.rad2deg(np.arccos(point_vec_z/dist)) + phi = np.array( + np.rad2deg( + np.arctan2( + point_vec_y, point_vec_x, + ), + ), ndmin=2, + ) + theta = np.rad2deg(np.arccos(point_vec_z / dist)) return phi, theta @@ -234,8 +248,28 @@ def get_off_axis_angle(self, station) -> np.array: a = 90 - self.elevation C = Az0 - Az - phi = np.arccos(np.cos(np.radians(a))*np.cos(np.radians(b)) \ - + np.sin(np.radians(a))*np.sin(np.radians(b))*np.cos(np.radians(C))) + phi = np.arccos( + np.cos(np.radians(a)) * np.cos(np.radians(b)) + + np.sin(np.radians(a)) * np.sin(np.radians(b)) * np.cos(np.radians(C)), + ) phi_deg = np.degrees(phi) return phi_deg + + def is_imt_station(self) -> bool: + """Whether this station is IMT or not + + Parameters + ---------- + sta : StationManager + The station that we're testing. + + Returns + ------- + bool + Whether this station is IMT or not + """ + if self.station_type is StationType.IMT_BS or self.station_type is StationType.IMT_UE: + return True + else: + return False diff --git a/sharc/support/__init__.py b/sharc/support/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/support/__init__.py +++ b/sharc/support/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/support/enumerations.py b/sharc/support/enumerations.py index fe43c3c7a..4d3199689 100644 --- a/sharc/support/enumerations.py +++ b/sharc/support/enumerations.py @@ -7,35 +7,40 @@ from enum import Enum -class Action( Enum ): + +class Action(Enum): """ The action that is sent to controller in order to control the simulation """ START_SIMULATION = 1 START_SIMULATION_SINGLE_THREAD = 2 - STOP_SIMULATION = 3 - -class State( Enum ): + STOP_SIMULATION = 3 + + +class State(Enum): """ This is the graphical user interface state """ - INITIAL = 1 - RUNNING = 2 + INITIAL = 1 + RUNNING = 2 FINISHED = 3 - STOPPED = 4 + STOPPED = 4 STOPPING = 5 - + + class StationType(Enum): """ Station types supported by simulator. """ - NONE = 0 # Dummy enum, for initialization purposes only + NONE = 0 # Dummy enum, for initialization purposes only IMT_BS = 1 # IMT Base Station IMT_UE = 2 # IMT User Equipment FSS_SS = 3 # FSS Space Station FSS_ES = 4 # FSS Earth Station - FS = 5 # Fixed Service - HAPS = 6 # HAPS (airbone) station - RNS = 7 # Radionavigation service - RAS = 8 # Radio Astronomy Service - EESS_PASSIVE = 9 # EESS passive sensor + FS = 5 # Fixed Service + HAPS = 6 # HAPS (airbone) station + RNS = 7 # Radionavigation service + RAS = 8 # Radio Astronomy Service + EESS_SS = 9 # EESS Space Station + METSAT_SS = 10 # MetSat Space Station + SINGLE_EARTH_STATION = 11 diff --git a/sharc/support/footprint.py b/sharc/support/footprint.py index 4d97ffe8a..89a3f2516 100644 --- a/sharc/support/footprint.py +++ b/sharc/support/footprint.py @@ -8,32 +8,43 @@ from area import area as earthArea from numpy import cos, sin, tan, arctan, deg2rad, rad2deg, arccos, pi, linspace, arcsin, vstack, arctan2, where, zeros_like import matplotlib.pyplot as plt +from sharc.parameters.constants import EARTH_RADIUS + class Footprint(object): """ Defines a satellite footprint region and calculates its area. - Method for generating footprints (Siocos,1973) is found in the book + Method for generating footprints (Siocos,1973) is found in the book "Satellite Communication Systems" by M. Richharia ISBN 0-07-134208-7 - + Construction: FootprintArea(bore_lat_deg, bore_subsat_long_deg, beam) beam_deg (float): half of beam width in degrees - elevation_deg (float): optional. Satellite elevation at - boresight bore_lat_deg (float): optional, default = 0. - Latitude of boresight point. If elevation is given this + elevation_deg (float): optional. Satellite elevation at + boresight bore_lat_deg (float): optional, default = 0. + Latitude of boresight point. If elevation is given this parameter is not used. Default = 0 bore_subsat_long_deg (float): longitude of boresight with respect to sub-satellite point, taken positive when to the west of the - sub-satellite point. If elevation is given this + sub-satellite point. If elevation is given this parameter is not used. Default = 0 + sat_height (int): optional, Default = 3578600. + Height of satellite in meters. If none are given, it is assumed that it is a geostationary satellite. """ - def __init__(self,beam_deg:float,**kwargs): + + def __init__(self, beam_deg: float, **kwargs): # Initialize attributes + self.sat_height = 35786000 + if 'sat_height' in kwargs.keys(): + self.sat_height = kwargs['sat_height'] + if 'elevation_deg' in kwargs.keys(): self.elevation_deg = kwargs['elevation_deg'] self.bore_lat_deg = 0.0 + self.sigma = EARTH_RADIUS / (EARTH_RADIUS + self.sat_height) self.bore_subsat_long_deg = self.calc_beta(self.elevation_deg) else: + self.sigma = EARTH_RADIUS / (EARTH_RADIUS + self.sat_height) self.bore_lat_deg = 0.0 self.bore_subsat_long_deg = 0.0 if 'bore_lat_deg' in kwargs.keys(): @@ -41,194 +52,338 @@ def __init__(self,beam_deg:float,**kwargs): if 'bore_subsat_long_deg' in kwargs.keys(): self.bore_subsat_long_deg = kwargs['bore_subsat_long_deg'] self.elevation_deg = \ - self.calc_elevation(self.bore_lat_deg,self.bore_subsat_long_deg) - + self.calc_elevation( + self.bore_lat_deg, + self.bore_subsat_long_deg, + ) + self.beam_width_deg = beam_deg - + + # sigma is the relation bewtween earth radius and satellite height + # print(self.sigma) + # Convert to radians self.elevation_rad = deg2rad(self.elevation_deg) self.bore_lat_rad = deg2rad(self.bore_lat_deg) self.bore_subsat_long_rad = deg2rad(self.bore_subsat_long_deg) self.beam_width_rad = deg2rad(self.beam_width_deg) - + # Calculate tilt - self.beta = arccos(cos(self.bore_lat_rad)*\ - cos(self.bore_subsat_long_rad)) - self.bore_tilt = arctan2(sin(self.beta),(6.6235 - cos(self.beta))) - + self.beta = arccos( + cos(self.bore_lat_rad) * + cos(self.bore_subsat_long_rad), + ) + self.bore_tilt = arctan2( + sin(self.beta), ((1 / self.sigma) - cos(self.beta)), + ) + # Maximum tilt and latitute coverage - self.max_gamma_rad = deg2rad(8.6833) - self.max_beta_rad = deg2rad(81.3164) - - def calc_beta(self,elev_deg: float): + self.max_beta_rad = arccos(self.sigma) + self.max_gamma_rad = pi / 2 - self.max_beta_rad + + def calc_beta(self, elev_deg: float): """ - Calculates elevation angle based on given elevation. Beta is the + Calculates elevation angle based on given elevation. Beta is the subpoint to earth station great-circle distance - + Input: elev_deg (float): elevation in degrees - + Output: - beta (float): beta angle in degrees + beta (float): beta angle in degrees """ elev_rad = deg2rad(elev_deg) - beta = 90 - elev_deg - rad2deg(arcsin(cos(elev_rad)/6.6235)) + beta = 90 - elev_deg - rad2deg(arcsin(cos(elev_rad) * self.sigma)) return beta - - def calc_elevation(self,lat_deg: float, long_deg: float): + + def calc_elevation(self, lat_deg: float, long_deg: float): """ - Calculates elevation for given latitude of boresight point and + Calculates elevation for given latitude of boresight point and longitude of boresight with respect to sub-satellite point. - + Inputs: lat_deg (float): latitude of boresight point in degrees long_deg (float): longitude of boresight with respect to sub-satellite point, taken positive when to the west of the sub-satellite point, in degrees - + Output: elev (float): elevation in degrees """ lat_rad = deg2rad(lat_deg) long_rad = deg2rad(long_deg) - beta = arccos(cos(lat_rad)*cos(long_rad)) - elev = arctan2((cos(beta) - 0.1510),sin(beta)) - + beta = arccos(cos(lat_rad) * cos(long_rad)) + elev = arctan2((cos(beta) - self.sigma), sin(beta)) + return rad2deg(elev) - - def set_elevation(self,elev: float): + + def set_elevation(self, elev: float): """ Resets elevation angle to given value """ self.elevation_deg = elev self.bore_lat_deg = 0.0 self.bore_subsat_long_deg = self.calc_beta(self.elevation_deg) - + # Convert to radians self.elevation_rad = deg2rad(self.elevation_deg) self.bore_lat_rad = deg2rad(self.bore_lat_deg) self.bore_subsat_long_rad = deg2rad(self.bore_subsat_long_deg) - + # Calculate tilt - self.beta = arccos(cos(self.bore_lat_rad)*\ - cos(self.bore_subsat_long_rad)) - self.bore_tilt = arctan2(sin(self.beta),(6.6235 - cos(self.beta))) - + self.beta = arccos( + cos(self.bore_lat_rad) * + cos(self.bore_subsat_long_rad), + ) + self.bore_tilt = arctan2( + sin(self.beta), (1 / self.sigma - cos(self.beta)), + ) + def calc_footprint(self, n: int): """ Defines footprint polygonal approximation - + Input: n (int): number of vertices on polygonal - + Outputs: pt_long (np.array): longitude of vertices in deg pt_lat (np.array): latiture of vertices in deg """ # Projection angles - phi = linspace(0,2*pi,num = n) - - cos_gamma_n = cos(self.bore_tilt)*cos(self.beam_width_rad) + \ - sin(self.bore_tilt)*sin(self.beam_width_rad)*\ - cos(phi) - - gamma_n = arccos(cos_gamma_n) - phi_n = arctan2(sin(phi),(sin(self.bore_tilt)*self.cot(self.beam_width_rad) - \ - cos(self.bore_tilt)*cos(phi))) - - eps_n = arctan2(sin(self.bore_subsat_long_rad),tan(self.bore_lat_rad)) + \ - phi_n - - beta_n = arcsin(6.6235*sin(gamma_n)) - gamma_n - beta_n[where(gamma_n > self.max_gamma_rad)] = self.max_beta_rad - - pt_lat = arcsin(sin(beta_n)*cos(eps_n)) - pt_long = arctan(tan(beta_n)*sin(eps_n)) - + phi = linspace(0, 2 * pi, num=n) + + cos_gamma_n = cos(self.bore_tilt) * cos(self.beam_width_rad) + \ + sin(self.bore_tilt) * sin(self.beam_width_rad) *\ + cos(phi) + + gamma_n = arccos(cos_gamma_n) + phi_n = arctan2( + sin(phi), ( + sin(self.bore_tilt) * self.cot(self.beam_width_rad) - + cos(self.bore_tilt) * cos(phi) + ), + ) + + eps_n = arctan2(sin(self.bore_subsat_long_rad), tan(self.bore_lat_rad)) + \ + phi_n + + beta_n = arcsin((1 / self.sigma) * sin(gamma_n)) - gamma_n + beta_n[where(gamma_n > self.max_gamma_rad)] = self.max_beta_rad + + pt_lat = arcsin(sin(beta_n) * cos(eps_n)) + pt_long = arctan(tan(beta_n) * sin(eps_n)) + return rad2deg(pt_long), rad2deg(pt_lat) - - def calc_area(self, n:int): + + def calc_area(self, n: int): """ Returns footprint area in km^2 - + Input: n (int): number of vertices on polygonal approximation Output: a (float): footprint area in km^2 """ + long, lat = self.calc_footprint(n) - + long_lat = vstack((long, lat)).T - - obj = {'type':'Polygon', - 'coordinates':[long_lat.tolist()]} - - return earthArea(obj)*1e-6 - - def cot(self,angle): - return tan(pi/2 - angle) - - def arccot(self,x): - return pi/2 - arctan(x) - + + obj = { + 'type': 'Polygon', + 'coordinates': [long_lat.tolist()], + } + + return earthArea(obj) * 1e-6 + + def cot(self, angle): + return tan(pi / 2 - angle) + + def arccot(self, x): + return pi / 2 - arctan(x) + + if __name__ == '__main__': - # Earth [km] - R = 6371 - - # Create object - fprint90 = Footprint(0.325,elevation_deg=90) - fprint45 = Footprint(0.325,elevation_deg=45) - fprint30 = Footprint(0.325,elevation_deg=30) - fprint20 = Footprint(0.325,elevation_deg=20) - fprint05 = Footprint(0.325,elevation_deg=5) - + + # Create 20km footprints + footprint_20km_10deg = Footprint(5, elevation_deg=10, sat_height=20000) + footprint_20km_20deg = Footprint(5, elevation_deg=20, sat_height=20000) + footprint_20km_30deg = Footprint(5, elevation_deg=30, sat_height=20000) + footprint_20km_45deg = Footprint(5, elevation_deg=45, sat_height=20000) + footprint_20km_90deg = Footprint(5, elevation_deg=90, sat_height=20000) + + plt.figure(figsize=(15, 2)) + n = 100 + lng, lat = footprint_20km_90deg.calc_footprint(n) + plt.plot(lng, lat, 'k', label='$90^o$') + lng, lat = footprint_20km_45deg.calc_footprint(n) + plt.plot(lng, lat, 'b', label='$45^o$') + lng, lat = footprint_20km_30deg.calc_footprint(n) + plt.plot(lng, lat, 'r', label='$30^o$') + lng, lat = footprint_20km_20deg.calc_footprint(n) + plt.plot(lng, lat, 'g', label='$20^o$') + lng, lat = footprint_20km_10deg.calc_footprint(n) + plt.plot(lng, lat, 'y', label='$10^o$') + + plt.title("Footprints at 20km") + plt.legend(loc='upper right') + plt.xlabel('Longitude [deg]') + plt.ylabel('Latitude [deg]') + # plt.xlim([-5, 6]) + plt.grid() + plt.show() + + # Create 500km footprints + footprint_500km_10deg = Footprint(5, elevation_deg=10, sat_height=500000) + footprint_500km_20deg = Footprint(5, elevation_deg=20, sat_height=500000) + footprint_500km_30deg = Footprint(5, elevation_deg=30, sat_height=500000) + footprint_500km_45deg = Footprint(5, elevation_deg=45, sat_height=500000) + footprint_500km_90deg = Footprint(5, elevation_deg=90, sat_height=500000) + print( + "Sat at 500km elevation 90 deg: area = {}".format( + footprint_500km_90deg.calc_area(n),), + ) + + plt.figure(figsize=(15, 2)) + n = 100 + lng, lat = footprint_500km_90deg.calc_footprint(n) + plt.plot(lng, lat, 'k', label='$90^o$') + lng, lat = footprint_500km_45deg.calc_footprint(n) + plt.plot(lng, lat, 'b', label='$45^o$') + lng, lat = footprint_500km_30deg.calc_footprint(n) + plt.plot(lng, lat, 'r', label='$30^o$') + lng, lat = footprint_500km_20deg.calc_footprint(n) + plt.plot(lng, lat, 'g', label='$20^o$') + lng, lat = footprint_500km_10deg.calc_footprint(n) + plt.plot(lng, lat, 'y', label='$10^o$') + + plt.title("Footprints at 500km") + plt.legend(loc='upper right') + plt.xlabel('Longitude [deg]') + plt.ylabel('Latitude [deg]') + # plt.xlim([-5, 20]) + plt.grid() + plt.show() + + # Create GEO footprints + fprint90 = Footprint(0.325, elevation_deg=90) + fprint45 = Footprint(0.325, elevation_deg=45) + fprint30 = Footprint(0.325, elevation_deg=30) + fprint20 = Footprint(0.325, elevation_deg=20) + fprint05 = Footprint(0.325, elevation_deg=5) + # Plot coordinates n = 100 - plt.figure(figsize=(15,2)) + plt.figure(figsize=(15, 2)) long, lat = fprint90.calc_footprint(n) - plt.plot(long,lat,'k',label='$90^o$') + plt.plot(long, lat, 'k', label='$90^o$') long, lat = fprint45.calc_footprint(n) - plt.plot(long,lat,'b',label='$45^o$') + plt.plot(long, lat, 'b', label='$45^o$') long, lat = fprint30.calc_footprint(n) - plt.plot(long,lat,'r',label='$30^o$') + plt.plot(long, lat, 'r', label='$30^o$') long, lat = fprint20.calc_footprint(n) - plt.plot(long,lat,'g',label='$20^o$') + plt.plot(long, lat, 'g', label='$20^o$') long, lat = fprint05.calc_footprint(n) - plt.plot(long,lat,'y',label='$5^o$') + plt.plot(long, lat, 'y', label='$5^o$') + + plt.title("Footprints at 35786km (GEO)") plt.legend(loc='upper right') plt.xlabel('Longitude [deg]') plt.ylabel('Latitude [deg]') - plt.xlim([-5, 90]) + # plt.xlim([-5, 90]) plt.grid() plt.show() - + # Print areas n = 1000 - print("Sat elevation 90 deg: area = {}".format(fprint90.calc_area(n))) - print("Sat elevation 45 deg: area = {}".format(fprint45.calc_area(n))) - print("Sat elevation 30 deg: area = {}".format(fprint30.calc_area(n))) - print("Sat elevation 20 deg: area = {}".format(fprint20.calc_area(n))) - print("Sat elevation 05 deg: area = {}".format(fprint05.calc_area(n))) - + + # area 20km + print( + "Sat at 20km elevation 90 deg: area = {}".format( + footprint_20km_90deg.calc_area(n), + ), + ) + print( + "Sat at 20km elevation 45 deg: area = {}".format( + footprint_20km_45deg.calc_area(n), + ), + ) + print( + "Sat at 20km elevation 30 deg: area = {}".format( + footprint_20km_30deg.calc_area(n), + ), + ) + print( + "Sat at 20km elevation 20 deg: area = {}".format( + footprint_20km_20deg.calc_area(n), + ), + ) + print( + "Sat at 20km elevation 10 deg: area = {}".format( + footprint_20km_10deg.calc_area(n), + ), + ) + + # area 500km + print( + "Sat at 500km elevation 90 deg: area = {}".format( + footprint_500km_90deg.calc_area(n), + ), + ) + print( + "Sat at 500km elevation 45 deg: area = {}".format( + footprint_500km_45deg.calc_area(n), + ), + ) + print( + "Sat at 500km elevation 30 deg: area = {}".format( + footprint_500km_30deg.calc_area(n), + ), + ) + print( + "Sat at 500km elevation 20 deg: area = {}".format( + footprint_500km_20deg.calc_area(n), + ), + ) + print( + "Sat at 500km elevation 10 deg: area = {}".format( + footprint_500km_10deg.calc_area(n), + ), + ) + # Plot area vs elevation n_el = 100 n_poly = 1000 - elevation = linspace(0,90,num=n_el) - area = zeros_like(elevation) - - fprint = Footprint(0.320,elevation_deg=0) - + elevation = linspace(0, 90, num=n_el) + area_20km = zeros_like(elevation) + area_500km = zeros_like(elevation) + area_35786km = zeros_like(elevation) + + fprint_20km = Footprint(5, elevation_deg=0, sat_height=20000) + fprint_500km = Footprint(5, elevation_deg=0, sat_height=500000) + fprint_35786km = Footprint(0.325, elevation_deg=0, sat_height=35786000) + for k in range(len(elevation)): - fprint.set_elevation(elevation[k]) - area[k] = fprint.calc_area(n_poly) - - plt.plot(elevation,area) + fprint_20km.set_elevation(elevation[k]) + area_20km[k] = fprint_20km.calc_area(n_poly) + fprint_500km.set_elevation(elevation[k]) + area_500km[k] = fprint_500km.calc_area(n_poly) + fprint_35786km.set_elevation(elevation[k]) + area_35786km[k] = fprint_35786km.calc_area(n_poly) + + plt.plot(elevation, area_20km, color='r', label='20km') + plt.plot(elevation, area_500km, color='g', label='500km') + plt.plot(elevation, area_35786km, color='b', label='35786km') plt.xlabel('Elevation [deg]') plt.ylabel('Footprint area [$km^2$]') + plt.legend(loc='upper right') plt.xlim([0, 90]) plt.grid() plt.show() - - - - \ No newline at end of file + + # Plot area vs elevation + n_el = 100 + n_poly = 1000 + elevation = linspace(0, 90, num=n_el) + area = zeros_like(elevation) diff --git a/sharc/support/logging.py b/sharc/support/logging.py index e1e929b31..464a3482f 100644 --- a/sharc/support/logging.py +++ b/sharc/support/logging.py @@ -10,12 +10,14 @@ import yaml + class Logging(): - @staticmethod - def setup_logging(default_path='support/logging.yaml', - default_level=logging.INFO, env_key='LOG_CFG'): + def setup_logging( + default_path='support/logging.yaml', + default_level=logging.INFO, env_key='LOG_CFG', + ): """ Setup logging configuration """ diff --git a/sharc/support/named_tuples.py b/sharc/support/named_tuples.py index 4cbe8e97a..75945ad5c 100644 --- a/sharc/support/named_tuples.py +++ b/sharc/support/named_tuples.py @@ -7,5 +7,10 @@ from collections import namedtuple -AntennaPar = namedtuple("AntennaPar", - "adjacent_antenna_model normalization normalization_data element_pattern element_max_g element_phi_3db element_theta_3db element_am element_sla_v n_rows n_columns element_horiz_spacing element_vert_spacing multiplication_factor minimum_array_gain downtilt") +AntennaPar = namedtuple( + "AntennaPar", + "adjacent_antenna_model normalization normalization_data element_pattern \ + element_max_g element_phi_3db element_theta_3db element_am element_sla_v n_rows \ + n_columns element_horiz_spacing element_vert_spacing multiplication_factor \ + minimum_array_gain downtilt", +) diff --git a/sharc/support/observable.py b/sharc/support/observable.py index c9730e84f..5ce4f6c9f 100644 --- a/sharc/support/observable.py +++ b/sharc/support/observable.py @@ -5,41 +5,42 @@ @author: edgar """ + class Observable(object): """ - This class represents an observable object, or "data" in the model-view - paradigm. It can be subclassed to represent an object that the application - wants to have observed. An observable object can have one or more - observers. An observer may be any object that implements interface - Observer. After an observable instance changes, an application calling the - Observable's notify_observers method causes all of its observers to be + This class represents an observable object, or "data" in the model-view + paradigm. It can be subclassed to represent an object that the application + wants to have observed. An observable object can have one or more + observers. An observer may be any object that implements interface + Observer. After an observable instance changes, an application calling the + Observable's notify_observers method causes all of its observers to be notified of the change by a call to their update method. - + Attributes ---------- observers : Observer The list of observers """ - + def __init__(self): self.observers = list() - + def add_observer(self, observer): """ Adds a new observer to the current list of observers. - + Parameters ---------- observer : Observer The observer to be added """ - if not observer in self.observers: + if observer not in self.observers: self.observers.append(observer) - + def delete_observer(self, observer): """ Deletes an observer from the current list of observers. - + Parameters ---------- observer : Observer @@ -47,15 +48,15 @@ def delete_observer(self, observer): """ if observer in self.observers: self.observers.remove(observer) - + def delete_observers(self): """ - Clears the observer list so that this object no longer has any + Clears the observer list so that this object no longer has any observers. """ if self.observers: del self.observers[:] - + def notify_observers(self, *args, **kwargs): """ Notifies all observers that this object has changed diff --git a/sharc/support/observer.py b/sharc/support/observer.py index 3058281bb..fc40b9a26 100644 --- a/sharc/support/observer.py +++ b/sharc/support/observer.py @@ -6,21 +6,21 @@ """ from abc import ABCMeta, abstractmethod - + + class Observer(object): """ - This is the abstract base class of the Observer pattern. A concrete - class can extend this class when it wants to be informed of changes in + This is the abstract base class of the Observer pattern. A concrete + class can extend this class when it wants to be informed of changes in observable objects. """ - + __metaclass__ = ABCMeta - + @abstractmethod def notify_observer(self, *args, **kwargs): """ - This method is called whenever the observed object is changed. An - application calls an Observable object's notify_observers method to + This method is called whenever the observed object is changed. An + application calls an Observable object's notify_observers method to have all the object's observers notified of the change. """ - pass diff --git a/sharc/support/sharc_utils.py b/sharc/support/sharc_utils.py new file mode 100644 index 000000000..9bc643236 --- /dev/null +++ b/sharc/support/sharc_utils.py @@ -0,0 +1,18 @@ +def is_float(s: str) -> bool: + """Check if string represents a float value + + Parameters + ---------- + s : str + input string + + Returns + ------- + bool + whether the string is a float value or not + """ + try: + float(s) + return True + except ValueError: + return False diff --git a/sharc/thread_simulation.py b/sharc/thread_simulation.py index 0c2358695..192570daa 100644 --- a/sharc/thread_simulation.py +++ b/sharc/thread_simulation.py @@ -10,46 +10,47 @@ import time from threading import Thread, Event + class ThreadSimulation(Thread): """ This class extends the Thread class and controls the simulation (start/stop) - + Attributes ---------- _stop (Event) : This flag is used to control when simulation is stopped by user model (Model) : Reference to the Model implementation of MVC """ - + def __init__(self, model: Model): Thread.__init__(self) self.model = model self.stop_flag = Event() - + def stop(self): """ - This is called by the controller when it receives the stop command by - view. This method sets the stop flag that is checked during the + This is called by the controller when it receives the stop command by + view. This method sets the stop flag that is checked during the simulation. Simulation stops when stop flag is set. """ self.stop_flag.set() - + def is_stopped(self) -> bool: """ Checks if stop flag is set. - + Returns ------- True if simulation is stopped """ return self.stop_flag.isSet() - + def run(self): """ This is overriden from base class and represents the thread's activity. """ start = time.perf_counter() - + self.model.initialize() while not self.model.is_finished() and not self.is_stopped(): self.model.snapshot() @@ -57,7 +58,8 @@ def run(self): # calculates simulation time when it finishes and sets the elapsed time end = time.perf_counter() elapsed_time = time.gmtime(end - start) - self.model.set_elapsed_time(time.strftime("%H h %M min %S seg", elapsed_time)) - - - \ No newline at end of file + self.model.set_elapsed_time( + time.strftime( + "%H h %M min %S seg", elapsed_time, + ), + ) diff --git a/sharc/topology/__init__.py b/sharc/topology/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/topology/__init__.py +++ b/sharc/topology/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.README.html b/sharc/topology/countries/ne_110m_admin_0_countries.README.html new file mode 100644 index 000000000..87125d229 --- /dev/null +++ b/sharc/topology/countries/ne_110m_admin_0_countries.README.html @@ -0,0 +1,547 @@ + + + + + + +Natural Earth » Blog Archive » Admin 0 – Countries - Free vector and raster map data at 1:10m, 1:50m, and 1:110m scales + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+

Admin 0 – Countries

+ +
+
+
countries_thumb
+
There are 258 countries in the world. Greenland as separate from Denmark. Most users will want this file instead of sovereign states, though some users will want map units instead when needing to distinguish overseas regions of France.
+
+

Natural Earth shows de facto boundaries by default according to who controls the territory, versus de jure.

+ + +

+
+
+
+

countries_banner

+

About

+

Countries distinguish between metropolitan (homeland) and independent and semi-independent portions of sovereign states. If you want to see the dependent overseas regions broken out (like in ISO codes, see France for example), use map units instead.

+

Each country is coded with a world region that roughly follows the United Nations setup.

+

Includes some thematic data from the United Nations, U.S. Central Intelligence Agency, and elsewhere.

+

Disclaimer

+

Natural Earth Vector draws boundaries of countries according to defacto status. We show who actually controls the situation on the ground. Please feel free to mashup our disputed areas (link) theme to match your particular political outlook.

+

Known Problems

+

None.

+

Version History

+ + +

The master changelog is available on Github ยป

+
+ + + + +
+ +
+ + + + + +
+ + + + + + +
+
    +
  1. +
    + + + + +

    […] earlier. It’s the result of a conversion of a polygon shapefile of country boundaries (from Natural Earth, a fantastic, public domain, physical/cultural spatial data source) to a raster data […]

    + + +
    +
  2. +
  3. +
    + + + + +

    […] Le mappe sono scaricate da https://www.naturalearthdata.com […]

    + + +
    +
  4. +
  5. +
    + + + + +

    […] Le mappe sono scaricate da https://www.naturalearthdata.com […]

    + + +
    +
  6. +
+
+
+ + + + + + + + +
+ + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.VERSION.txt b/sharc/topology/countries/ne_110m_admin_0_countries.VERSION.txt new file mode 100644 index 000000000..ac14c3dfa --- /dev/null +++ b/sharc/topology/countries/ne_110m_admin_0_countries.VERSION.txt @@ -0,0 +1 @@ +5.1.1 diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.cpg b/sharc/topology/countries/ne_110m_admin_0_countries.cpg new file mode 100644 index 000000000..3ad133c04 --- /dev/null +++ b/sharc/topology/countries/ne_110m_admin_0_countries.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.dbf b/sharc/topology/countries/ne_110m_admin_0_countries.dbf new file mode 100644 index 000000000..e0acd0664 Binary files /dev/null and b/sharc/topology/countries/ne_110m_admin_0_countries.dbf differ diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.prj b/sharc/topology/countries/ne_110m_admin_0_countries.prj new file mode 100644 index 000000000..b13a71791 --- /dev/null +++ b/sharc/topology/countries/ne_110m_admin_0_countries.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.shp b/sharc/topology/countries/ne_110m_admin_0_countries.shp new file mode 100644 index 000000000..9318e45c7 Binary files /dev/null and b/sharc/topology/countries/ne_110m_admin_0_countries.shp differ diff --git a/sharc/topology/countries/ne_110m_admin_0_countries.shx b/sharc/topology/countries/ne_110m_admin_0_countries.shx new file mode 100644 index 000000000..c3728e0dd Binary files /dev/null and b/sharc/topology/countries/ne_110m_admin_0_countries.shx differ diff --git a/sharc/topology/topology.py b/sharc/topology/topology.py index 32499b1b2..5b77721aa 100644 --- a/sharc/topology/topology.py +++ b/sharc/topology/topology.py @@ -9,13 +9,16 @@ import numpy as np import matplotlib.axes + class Topology(object): __metaclass__ = ABCMeta - def __init__(self, - intersite_distance: float, - cell_radius: float): + def __init__( + self, + intersite_distance: float, + cell_radius: float, + ): self.intersite_distance = intersite_distance self.cell_radius = cell_radius @@ -25,22 +28,19 @@ def __init__(self, self.y = np.empty(0) self.azimuth = np.empty(0) self.indoor = np.empty(0) + self.is_space_station = False self.num_base_stations = -1 self.static_base_stations = False - @abstractmethod def calculate_coordinates(self, random_number_gen=np.random.RandomState()): """ Calculates the coordinates of the stations according to the class atributes. """ - pass - @abstractmethod def plot(self, ax: matplotlib.axes.Axes): """ Plots the topology on the given axis. """ - pass diff --git a/sharc/topology/topology_factory.py b/sharc/topology/topology_factory.py index 1d4f5503e..6418c3ae2 100644 --- a/sharc/topology/topology_factory.py +++ b/sharc/topology/topology_factory.py @@ -10,21 +10,45 @@ from sharc.topology.topology_macrocell import TopologyMacrocell from sharc.topology.topology_hotspot import TopologyHotspot from sharc.topology.topology_indoor import TopologyIndoor +from sharc.topology.topology_ntn import TopologyNTN from sharc.topology.topology_single_base_station import TopologySingleBaseStation from sharc.parameters.parameters import Parameters + class TopologyFactory(object): - + @staticmethod def createTopology(parameters: Parameters) -> Topology: - if parameters.imt.topology == "SINGLE_BS": - return TopologySingleBaseStation(parameters.imt.intersite_distance*2/3, parameters.imt.num_clusters) - elif parameters.imt.topology == "MACROCELL": - return TopologyMacrocell(parameters.imt.intersite_distance, parameters.imt.num_clusters) - elif parameters.imt.topology == "HOTSPOT": - return TopologyHotspot(parameters.hotspot, parameters.imt.intersite_distance, parameters.imt.num_clusters) - elif parameters.imt.topology == "INDOOR": - return TopologyIndoor(parameters.indoor) + if parameters.imt.topology.type == "SINGLE_BS": + return TopologySingleBaseStation( + parameters.imt.topology.single_bs.cell_radius, + parameters.imt.topology.single_bs.num_clusters + ) + elif parameters.imt.topology.type == "MACROCELL": + return TopologyMacrocell( + parameters.imt.topology.macrocell.intersite_distance, + parameters.imt.topology.macrocell.num_clusters + ) + elif parameters.imt.topology.type == "HOTSPOT": + return TopologyHotspot( + parameters.imt.topology.hotspot, + parameters.imt.topology.hotspot.intersite_distance, + parameters.imt.topology.hotspot.num_clusters + ) + elif parameters.imt.topology.type == "INDOOR": + return TopologyIndoor(parameters.imt.topology.indoor) + elif parameters.imt.topology.type == "NTN": + return TopologyNTN( + parameters.imt.topology.ntn.intersite_distance, + parameters.imt.topology.ntn.cell_radius, + parameters.imt.topology.ntn.bs_height, + parameters.imt.topology.ntn.bs_azimuth, + parameters.imt.topology.ntn.bs_elevation, + parameters.imt.topology.ntn.num_sectors, + ) else: - sys.stderr.write("ERROR\nInvalid topology: " + parameters.imt.topology) - sys.exit(1) + sys.stderr.write( + "ERROR\nInvalid topology: " + + parameters.imt.topology, + ) + sys.exit(1) diff --git a/sharc/topology/topology_hotspot.py b/sharc/topology/topology_hotspot.py index da68d7d59..25a0e6c99 100644 --- a/sharc/topology/topology_hotspot.py +++ b/sharc/topology/topology_hotspot.py @@ -16,7 +16,7 @@ from sharc.topology.topology import Topology from sharc.topology.topology_macrocell import TopologyMacrocell -from sharc.parameters.parameters_hotspot import ParametersHotspot +from sharc.parameters.imt.parameters_hotspot import ParametersHotspot class TopologyHotspot(Topology): @@ -56,28 +56,35 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()): y = np.empty(0) azimuth = np.empty(0) for cell_x, cell_y, cell_azimuth in zip(self.macrocell.x, self.macrocell.y, self.macrocell.azimuth): - #print("base station #{}".format(i)) + # print("base station #{}".format(i)) i += 1 # find the center coordinates of the sector (hexagon) - macro_cell_x = cell_x + self.macrocell.intersite_distance/3*math.cos(math.radians(cell_azimuth)) - macro_cell_y = cell_y + self.macrocell.intersite_distance/3*math.sin(math.radians(cell_azimuth)) + macro_cell_x = cell_x + self.macrocell.intersite_distance / \ + 3 * math.cos(math.radians(cell_azimuth)) + macro_cell_y = cell_y + self.macrocell.intersite_distance / \ + 3 * math.sin(math.radians(cell_azimuth)) # Hotspots are generated inside an inscribed circle of a regular hexagon (sector). # The backoff factor (1.0) controls the overlapping rate between hotspots # coverage areas (overlapping of hotspots in different macro cells) - r = np.maximum(0, (self.macrocell.intersite_distance/3)*np.sqrt(3)/2 - self.param.max_dist_hotspot_ue/1.0) + r = np.maximum( + 0, (self.macrocell.intersite_distance / 3) * + np.sqrt(3) / 2 - self.param.max_dist_hotspot_ue / 1.0, + ) hotspot_x = np.array(0) hotspot_y = np.array(0) hotspot_azimuth = np.array(0) - + for hs in range(self.param.num_hotspots_per_cell): num_attempts = 0 - while(True): + while (True): # create one hotspot - hotspot_radius = r*random_number_gen.rand(1) - hotspot_angle = 2*np.pi*random_number_gen.rand(1) - candidate_x = hotspot_radius*np.cos(hotspot_angle) + macro_cell_x - candidate_y = hotspot_radius*np.sin(hotspot_angle) + macro_cell_y - candidate_azimuth = 360*random_number_gen.rand(1) + hotspot_radius = r * random_number_gen.rand(1) + hotspot_angle = 2 * np.pi * random_number_gen.rand(1) + candidate_x = hotspot_radius * \ + np.cos(hotspot_angle) + macro_cell_x + candidate_y = hotspot_radius * \ + np.sin(hotspot_angle) + macro_cell_y + candidate_azimuth = 360 * random_number_gen.rand(1) if hs == 0: # the candidate is valid if it is the first to be created hotspot_x = candidate_x @@ -85,51 +92,67 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()): hotspot_azimuth = candidate_azimuth break else: - overlapping_hotspots = self.overlapping_hotspots(candidate_x, - candidate_y, - candidate_azimuth, - hotspot_x, - hotspot_y, - hotspot_azimuth, - self.cell_radius) - min_dist_validated = self.validade_min_dist_bs_hotspot(candidate_x, - candidate_y, - self.macrocell.x, - self.macrocell.y, - self.param.min_dist_bs_hotspot) - candidate_valid = (not overlapping_hotspots) and min_dist_validated + overlapping_hotspots = self.overlapping_hotspots( + candidate_x, + candidate_y, + candidate_azimuth, + hotspot_x, + hotspot_y, + hotspot_azimuth, + self.cell_radius, + ) + min_dist_validated = self.validade_min_dist_bs_hotspot( + candidate_x, + candidate_y, + self.macrocell.x, + self.macrocell.y, + self.param.min_dist_bs_hotspot, + ) + candidate_valid = ( + not overlapping_hotspots + ) and min_dist_validated if candidate_valid: - hotspot_x = np.concatenate((hotspot_x, candidate_x)) - hotspot_y = np.concatenate((hotspot_y, candidate_y)) - hotspot_azimuth = np.concatenate((hotspot_azimuth, candidate_azimuth)) + hotspot_x = np.concatenate( + (hotspot_x, candidate_x), + ) + hotspot_y = np.concatenate( + (hotspot_y, candidate_y), + ) + hotspot_azimuth = np.concatenate( + (hotspot_azimuth, candidate_azimuth), + ) break else: num_attempts = num_attempts + 1 - + if num_attempts > TopologyHotspot.MAX_NUM_LOOPS: - sys.stderr.write("ERROR\nInfinite loop while creating hotspots.\nTry less hotspots per cell or greater macro cell intersite distance.\n") + sys.stderr.write( + "ERROR\nInfinite loop while creating hotspots.\n \ + Try less hotspots per cell or greater macro cell intersite distance.\n", + ) sys.exit(1) - #if num_attempts > 1: print("number of attempts: {}".format(num_attempts)) + # if num_attempts > 1: print("number of attempts: {}".format(num_attempts)) x = np.concatenate([x, hotspot_x]) y = np.concatenate([y, hotspot_y]) - azimuth = np.concatenate([azimuth, hotspot_azimuth]) - + azimuth = np.concatenate([azimuth, hotspot_azimuth]) + self.x = x self.y = y self.azimuth = azimuth # In the end, we have to update the number of base stations self.num_base_stations = len(self.x) - self.indoor = np.zeros(self.num_base_stations, dtype = bool) - - - def overlapping_hotspots(self, - candidate_x: np.array, - candidate_y: np.array, - candidate_azimuth: np.array, - set_x: np.array, - set_y: np.array, - set_azimuth: np.array, - radius: float) -> bool: + self.indoor = np.zeros(self.num_base_stations, dtype=bool) + + def overlapping_hotspots( + self, + candidate_x: np.array, + candidate_y: np.array, + candidate_azimuth: np.array, + set_x: np.array, + set_y: np.array, + set_azimuth: np.array, + radius: float, + ) -> bool: """ Evaluates the spatial relationships among hotspots and checks whether the hotspot defined by (x, y, azimuth) intersects with any hotspot of @@ -142,7 +165,7 @@ def overlapping_hotspots(self, candidate_azimuth: horizontal angle of the candidate hotspot (orientation) set_x: x-coordinates of the set of hotspots set_y: y-coordinates of the set of hotspots - set_azimuth: horizontal angle of the set of hotspots (orientation) + set_azimuth: horizontal angle of the set of hotspots (orientation) radius: radius of all hotspots Returns @@ -157,20 +180,24 @@ def overlapping_hotspots(self, set_points = list() set_points.append((x, y)) for a in range(len(azimuth_values)): - set_points.append((x + radius*math.cos(np.radians(azimuth + azimuth_values[a])), - y + radius*math.sin(np.radians(azimuth + azimuth_values[a])))) + set_points.append(( + x + radius * math.cos(np.radians(azimuth + azimuth_values[a])), + y + radius * math.sin(np.radians(azimuth + azimuth_values[a])), + )) set_polygons.append(Polygon(set_points)) # Creating the candidate polygon points = list() points.append((candidate_x, candidate_y)) for a in range(len(azimuth_values)): - points.append((candidate_x + radius*math.cos(np.radians(candidate_azimuth + azimuth_values[a])), - candidate_y + radius*math.sin(np.radians(candidate_azimuth + azimuth_values[a])))) + points.append(( + candidate_x + radius * math.cos(np.radians(candidate_azimuth + azimuth_values[a])), + candidate_y + radius * math.sin(np.radians(candidate_azimuth + azimuth_values[a])), + )) polygon = Polygon(points) - - # Check if there is overlapping between the candidate hotspot and - # any of the hotspots of the set. In other words, check if any polygons + + # Check if there is overlapping between the candidate hotspot and + # any of the hotspots of the set. In other words, check if any polygons # intersect for p in range(len(set_polygons)): overlapping = polygon.intersects(set_polygons[p]) @@ -182,13 +209,14 @@ def overlapping_hotspots(self, # If this point is reached, then there is no intersection between polygons return False - - def validade_min_dist_bs_hotspot(self, - hotspot_x: np.array, - hotspot_y: np.array, - macrocell_x: np.array, - macrocell_y: np.array, - min_dist_bs_hotspot: float) -> bool: + def validade_min_dist_bs_hotspot( + self, + hotspot_x: np.array, + hotspot_y: np.array, + macrocell_x: np.array, + macrocell_y: np.array, + min_dist_bs_hotspot: float, + ) -> bool: """ Checks minimum 2D distance between macro cell base stations and hotspots. @@ -202,30 +230,35 @@ def validade_min_dist_bs_hotspot(self, # Here we have a 2D matrix whose values indicates the distance between # base station and hotspots. In this matrix, each line corresponds to # a macro cell base station and each column corresponds to a hotspot - distance = np.sqrt((hotspot_x - macrocell_x.reshape((-1, 1)))**2 + - (hotspot_y - macrocell_y.reshape((-1, 1)))**2) + distance = np.sqrt( + (hotspot_x - macrocell_x.reshape((-1, 1)))**2 + + (hotspot_y - macrocell_y.reshape((-1, 1)))**2, + ) # count the number of values that are less than the minimum distance and # return true if any value is equal os less than minimum 2D distance # between macro cell base stations and hotspot centers occ = np.where(distance < min_dist_bs_hotspot)[0] return len(occ) == 0 - def plot(self, ax: matplotlib.axes.Axes): # plot macrocells self.macrocell.plot(ax) # plot hotspots - plt.scatter(self.x, self.y, color='g', edgecolor="w", linewidth=0.5, label="Hotspot") + plt.scatter( + self.x, self.y, color='g', edgecolor="w", + linewidth=0.5, label="Hotspot", + ) # plot hotspots coverage area for x, y, a in zip(self.x, self.y, self.azimuth): - pa = patches.Wedge( (x, y), self.cell_radius, a-60, a+60, fill=False, - edgecolor="green", linestyle='solid' ) + pa = patches.Wedge( + (x, y), self.cell_radius, a - 60, a + 60, fill=False, + edgecolor="green", linestyle='solid', + ) ax.add_patch(pa) - if __name__ == '__main__': param = ParametersHotspot() param.num_hotspots_per_cell = 2 @@ -239,7 +272,10 @@ def plot(self, ax: matplotlib.axes.Axes): topology = TopologyHotspot(param, intersite_distance, num_clusters) topology.calculate_coordinates() - fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure topology.plot(ax) diff --git a/sharc/topology/topology_indoor.py b/sharc/topology/topology_indoor.py index 5b804f441..7465ca8eb 100644 --- a/sharc/topology/topology_indoor.py +++ b/sharc/topology/topology_indoor.py @@ -6,24 +6,24 @@ """ from sharc.topology.topology import Topology -from sharc.parameters.parameters_indoor import ParametersIndoor +from sharc.parameters.imt.parameters_indoor import ParametersIndoor import matplotlib.pyplot as plt import matplotlib.axes import numpy as np from itertools import product + class TopologyIndoor(Topology): """ Generates the coordinates of the sites based on the indoor network - topology. + topology. """ - def __init__(self, param: ParametersIndoor): """ Constructor method that sets the parameters. - + Parameters ---------- param : parameters of the indoor topology @@ -34,10 +34,10 @@ def __init__(self, param: ParametersIndoor): self.b_w = 120 self.b_d = 50 self.b_h = 3 - - cell_radius = param.intersite_distance/2 + + cell_radius = param.intersite_distance / 2 super().__init__(param.intersite_distance, cell_radius) - + self.n_rows = param.n_rows self.n_colums = param.n_colums self.street_width = param.street_width @@ -47,55 +47,64 @@ def __init__(self, param: ParametersIndoor): self.num_floors = param.num_floors if param.num_imt_buildings == 'ALL': self.all_buildings = True - self.num_imt_buildings = self.n_rows*self.n_colums + self.num_imt_buildings = self.n_rows * self.n_colums else: self.all_buildings = False self.num_imt_buildings = int(param.num_imt_buildings) self.imt_buildings = list() - self.total_bs_level = self.num_imt_buildings*self.num_cells - + self.total_bs_level = self.num_imt_buildings * self.num_cells + self.height = np.empty(0) - - + def calculate_coordinates(self, random_number_gen=np.random.RandomState()): """ Calculates the coordinates of the stations according to the inter-site - distance parameter. This method is invoked in all snapshots but it can - be called only once for the indoor topology. So we set + distance parameter. This method is invoked in all snapshots but it can + be called only once for the indoor topology. So we set static_base_stations to True to avoid unnecessary calculations. """ if not self.static_base_stations: self.reset() self.static_base_stations = self.all_buildings - - x_base = np.array([ (2*k + 1)*self.cell_radius for k in range(self.num_cells)]) - y_base = self.b_d/2*np.ones(self.num_cells) - + + x_base = np.array( + [(2 * k + 1) * self.cell_radius for k in range(self.num_cells)], + ) + y_base = self.b_d / 2 * np.ones(self.num_cells) + # Choose random buildings - all_buildings = list(product(range(self.n_rows),range(self.n_colums))) + all_buildings = list( + product(range(self.n_rows), range(self.n_colums)), + ) random_number_gen.shuffle(all_buildings) self.imt_buildings = all_buildings[:self.num_imt_buildings] - + floor_x = np.empty(0) floor_y = np.empty(0) for build in self.imt_buildings: r = build[0] c = build[1] - floor_x = np.concatenate((floor_x, x_base + c*(self.b_w + self.street_width))) - floor_y = np.concatenate((floor_y, y_base + r*(self.b_d + self.street_width))) + floor_x = np.concatenate( + (floor_x, x_base + c * (self.b_w + self.street_width)), + ) + floor_y = np.concatenate( + (floor_y, y_base + r * (self.b_d + self.street_width)), + ) for f in range(self.num_floors): self.x = np.concatenate((self.x, floor_x)) self.y = np.concatenate((self.y, floor_y)) - self.height = np.concatenate((self.height, - (f+1)*self.b_h*np.ones_like(floor_x))) + self.height = np.concatenate(( + self.height, + (f + 1) * self.b_h * np.ones_like(floor_x), + )) # In the end, we have to update the number of base stations - self.num_base_stations = len(self.x) + self.num_base_stations = len(self.x) self.azimuth = np.zeros(self.num_base_stations) - self.indoor = np.ones(self.num_base_stations, dtype = bool) - + self.indoor = np.ones(self.num_base_stations, dtype=bool) + def reset(self): self.x = np.empty(0) self.y = np.empty(0) @@ -104,67 +113,84 @@ def reset(self): self.indoor = np.empty(0) self.num_base_stations = -1 self.static_base_stations = False - - def plot(self, ax: matplotlib.axes.Axes, top_view = True): + + def plot(self, ax: matplotlib.axes.Axes, top_view=True): if top_view: self.plot_top_view(ax) else: self.plot_side_view(ax) - + def plot_top_view(self, ax: matplotlib.axes.Axes): # create the building - for b in range(int(self.num_base_stations/self.num_cells)): - x_b = self.x[self.num_cells*b] - self.cell_radius - y_b = self.y[self.num_cells*b] - self.b_d/2 - points = list([[x_b, y_b], - [x_b + self.b_w, y_b], - [x_b + self.b_w, y_b + self.b_d], - [x_b, y_b + + self.b_d]]) + for b in range(int(self.num_base_stations / self.num_cells)): + x_b = self.x[self.num_cells * b] - self.cell_radius + y_b = self.y[self.num_cells * b] - self.b_d / 2 + points = list([ + [x_b, y_b], + [x_b + self.b_w, y_b], + [x_b + self.b_w, y_b + self.b_d], + [x_b, y_b + + self.b_d], + ]) sector = plt.Polygon(points, fill=None, edgecolor='k') - ax.add_patch(sector) + ax.add_patch(sector) for q in range(8): points = list() - x_b = self.x[self.num_cells*b] - self.cell_radius + q*15 - y_b = self.y[self.num_cells*b] + 10 - points.extend([[x_b, y_b], - [x_b + 15, y_b], - [x_b + 15, y_b + 15], - [x_b, y_b + 15]]) + x_b = self.x[self.num_cells * b] - self.cell_radius + q * 15 + y_b = self.y[self.num_cells * b] + 10 + points.extend([ + [x_b, y_b], + [x_b + 15, y_b], + [x_b + 15, y_b + 15], + [x_b, y_b + 15], + ]) sector = plt.Polygon(points, fill=None, edgecolor='k') ax.add_patch(sector) - + for q in range(8): points = list() - x_b = self.x[self.num_cells*b] - self.cell_radius + q*15 - y_b = self.y[self.num_cells*b] - self.b_d/2 - points.extend([[x_b, y_b], - [x_b + 15, y_b], - [x_b + 15, y_b + 15], - [x_b, y_b + 15]]) + x_b = self.x[self.num_cells * b] - self.cell_radius + q * 15 + y_b = self.y[self.num_cells * b] - self.b_d / 2 + points.extend([ + [x_b, y_b], + [x_b + 15, y_b], + [x_b + 15, y_b + 15], + [x_b, y_b + 15], + ]) sector = plt.Polygon(points, fill=None, edgecolor='k') - ax.add_patch(sector) - - + ax.add_patch(sector) + # indoor base stations - ax.scatter(self.x, self.y, color='k', edgecolor="k", linewidth=2, label="Base station") - + ax.scatter( + self.x, self.y, color='k', edgecolor="k", + linewidth=2, label="Base station", + ) + def plot_side_view(self, ax: matplotlib.axes.Axes): - + # Loop on each floor of each column of buildings for f in range(int(self.num_floors)): for build in self.imt_buildings: c = build[1] - x_b = self.x[f*self.total_bs_level + c*self.num_cells] - self.cell_radius - z_b = self.height[f*self.total_bs_level + c*self.num_cells] - points = list([[x_b, z_b], - [x_b + self.b_w, z_b], - [x_b + self.b_w, z_b - self.b_h], - [x_b, z_b - self.b_h]]) + x_b = self.x[ + f * self.total_bs_level + + c * self.num_cells + ] - self.cell_radius + z_b = self.height[f * self.total_bs_level + c * self.num_cells] + points = list([ + [x_b, z_b], + [x_b + self.b_w, z_b], + [x_b + self.b_w, z_b - self.b_h], + [x_b, z_b - self.b_h], + ]) sector = plt.Polygon(points, fill=None, edgecolor='k') - ax.add_patch(sector) - - ax.scatter(self.x, self.height-0.05, color='k', edgecolor="k", linewidth=2, label="Base station") + ax.add_patch(sector) + + ax.scatter( + self.x, self.height - 0.05, color='k', + edgecolor="k", linewidth=2, label="Base station", + ) + if __name__ == '__main__': param = ParametersIndoor() @@ -180,36 +206,44 @@ def plot_side_view(self, ax: matplotlib.axes.Axes): param.building_class = "TRADITIONAL" topology = TopologyIndoor(param) topology.calculate_coordinates() - + # Plot top view - fig = plt.figure(figsize=(10,8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(10, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure - + topology.plot(ax) - - plt.axis('image') + + plt.axis('image') plt.title("Indoor topology") plt.xlabel("x-coordinate [m]") plt.ylabel("y-coordinate [m]") - plt.tight_layout() - + plt.tight_layout() + axes = plt.gca() -# axes.set_xlim([-param.street_width, param.n_colums*3*param.intersite_distance + (param.n_colums-1)*param.street_width + param.street_width]) -# axes.set_ylim([-param.street_width, param.n_rows*topology.b_d + (param.n_rows-1)*param.street_width + param.street_width]) - - plt.show() - +# axes.set_xlim([-param.street_width, param.n_colums*3*param.intersite_distance + \ +# (param.n_colums-1)*param.street_width + param.street_width]) +# axes.set_ylim([-param.street_width, param.n_rows*topology.b_d + \ +# (param.n_rows-1)*param.street_width + param.street_width]) + + plt.show() + # Plot side view - fig = plt.figure(figsize=(10,8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(10, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure - - topology.plot(ax,top_view=False) - + + topology.plot(ax, top_view=False) + plt.title("Indoor topology") plt.xlabel("x-coordinate [m]") - plt.ylabel("z-coordinate [m]") + plt.ylabel("z-coordinate [m]") plt.tight_layout() - + axes = plt.gca() - axes.set_ylim((0,3*param.num_floors + 1)) - plt.show() \ No newline at end of file + axes.set_ylim((0, 3 * param.num_floors + 1)) + plt.show() diff --git a/sharc/topology/topology_macrocell.py b/sharc/topology/topology_macrocell.py index 59466312c..7764785f2 100644 --- a/sharc/topology/topology_macrocell.py +++ b/sharc/topology/topology_macrocell.py @@ -12,6 +12,7 @@ import math import numpy as np + class TopologyMacrocell(Topology): """ Generates the coordinates of the sites based on the macrocell network @@ -34,10 +35,12 @@ def __init__(self, intersite_distance: float, num_clusters: int): num_clusters : Number of clusters, should be 1 or 7 """ if num_clusters not in TopologyMacrocell.ALLOWED_NUM_CLUSTERS: - error_message = "invalid number of clusters ({})".format(num_clusters) + error_message = "invalid number of clusters ({})".format( + num_clusters, + ) raise ValueError(error_message) - cell_radius = intersite_distance*2/3 + cell_radius = intersite_distance * 2 / 3 super().__init__(intersite_distance, cell_radius) self.num_clusters = num_clusters @@ -55,49 +58,63 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()): self.static_base_stations = True d = self.intersite_distance - h = (d/3)*math.sqrt(3)/2 + h = (d / 3) * math.sqrt(3) / 2 # these are the coordinates of the central cluster - x_central = np.array([0, d, d/2, -d/2, -d, -d/2, - d/2, 2*d, 3*d/2, d, 0, -d, - -3*d/2, -2*d, -3*d/2, -d, 0, d, 3*d/2]) - y_central = np.array([0, 0, 3*h, 3*h, 0, -3*h, - -3*h, 0, 3*h, 6*h, 6*h, 6*h, - 3*h, 0, -3*h, -6*h, -6*h, -6*h, -3*h]) + x_central = np.array([ + 0, d, d / 2, -d / 2, -d, -d / 2, + d / 2, 2 * d, 3 * d / 2, d, 0, -d, + -3 * d / 2, -2 * d, -3 * d / 2, -d, 0, d, 3 * d / 2, + ]) + y_central = np.array([ + 0, 0, 3 * h, 3 * h, 0, -3 * h, + -3 * h, 0, 3 * h, 6 * h, 6 * h, 6 * h, + 3 * h, 0, -3 * h, -6 * h, -6 * h, -6 * h, -3 * h, + ]) self.x = np.copy(x_central) self.y = np.copy(y_central) # other clusters are calculated by shifting the central cluster if self.num_clusters == 7: - x_shift = np.array([7*d/2, -d/2, -4*d, -7*d/2, d/2, 4*d]) - y_shift = np.array([9*h, 15*h, 6*h, -9*h, -15*h, -6*h]) + x_shift = np.array( + [7 * d / 2, -d / 2, -4 * d, -7 * d / 2, d / 2, 4 * d], + ) + y_shift = np.array( + [9 * h, 15 * h, 6 * h, -9 * h, -15 * h, -6 * h], + ) for xs, ys in zip(x_shift, y_shift): self.x = np.concatenate((self.x, x_central + xs)) self.y = np.concatenate((self.y, y_central + ys)) self.x = np.repeat(self.x, 3) self.y = np.repeat(self.y, 3) - self.azimuth = np.tile(self.AZIMUTH, 19*self.num_clusters) + self.azimuth = np.tile(self.AZIMUTH, 19 * self.num_clusters) # In the end, we have to update the number of base stations self.num_base_stations = len(self.x) - self.indoor = np.zeros(self.num_base_stations, dtype = bool) + self.indoor = np.zeros(self.num_base_stations, dtype=bool) def plot(self, ax: matplotlib.axes.Axes): # create the hexagons - r = self.intersite_distance/3 + r = self.intersite_distance / 3 for x, y, az in zip(self.x, self.y, self.azimuth): - se = list([[x,y]]) + se = list([[x, y]]) angle = int(az - 60) for a in range(6): - se.extend([[se[-1][0] + r*math.cos(math.radians(angle)), se[-1][1] + r*math.sin(math.radians(angle))]]) + se.extend([[ + se[-1][0] + r * math.cos(math.radians(angle)), + se[-1][1] + r * math.sin(math.radians(angle)), + ]]) angle += 60 sector = plt.Polygon(se, fill=None, edgecolor='k') ax.add_patch(sector) # macro cell base stations - ax.scatter(self.x, self.y, color='k', edgecolor="k", linewidth=4, label="Macro cell") + ax.scatter( + self.x, self.y, color='k', edgecolor="k", + linewidth=4, label="Macro cell", + ) if __name__ == '__main__': @@ -106,7 +123,10 @@ def plot(self, ax: matplotlib.axes.Axes): topology = TopologyMacrocell(intersite_distance, num_clusters) topology.calculate_coordinates() - fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure topology.plot(ax) @@ -117,5 +137,3 @@ def plot(self, ax: matplotlib.axes.Axes): plt.ylabel("y-coordinate [m]") plt.tight_layout() plt.show() - - diff --git a/sharc/topology/topology_ntn.py b/sharc/topology/topology_ntn.py new file mode 100644 index 000000000..0896d9dbc --- /dev/null +++ b/sharc/topology/topology_ntn.py @@ -0,0 +1,300 @@ +from sharc.topology.topology import Topology +import numpy as np +import math +import matplotlib.pyplot as plt +import matplotlib.axes +import geopandas as gpd +from shapely.geometry import Polygon, MultiPolygon + + +class TopologyNTN(Topology): + """ + Class to generate and manage the topology of Non-Terrestrial Network (NTN) sites + based on a specified macrocell network topology. + """ + + ALLOWED_NUM_SECTORS = [1, 7, 19] + + def __init__( + self, intersite_distance: float, cell_radius: int, bs_height: float, bs_azimuth: float, + bs_elevation: float, num_sectors=7, + ): + """ + Initializes the NTN topology with specific network settings. + + Parameters: + intersite_distance: Distance between adjacent sites in meters. + cell_radius: Radius of the coverage area for each site in meters. + bs_height: Height of the base station (satellite) from the x-y plane. + bs_azimuth: Azimuth angle of the base station in degrees. + bs_elevation: Elevation angle of the base station in degrees. + num_sectors: Number of sectors for the topology (default is 7). + """ + + if num_sectors not in self.ALLOWED_NUM_SECTORS: + raise ValueError( + f"Invalid number of sectors: {num_sectors}. Allowed values are {self.ALLOWED_NUM_SECTORS}.", + ) + + # Call to the superclass constructor to set common properties + super().__init__(intersite_distance, cell_radius) + self.is_space_station = True + self.space_station_x = None + self.space_station_y = None + self.space_station_z = None + self.bs_azimuth = np.radians(bs_azimuth) + self.bs_elevation = np.radians(bs_elevation) + self.bs_radius = bs_height / np.sin(self.bs_elevation) + self.num_sectors = num_sectors + + # Calculate the base station coordinates + + self.space_station_x = self.bs_radius * \ + np.cos(self.bs_elevation) * np.cos(self.bs_azimuth) + self.space_station_y = self.bs_radius * \ + np.cos(self.bs_elevation) * np.sin(self.bs_azimuth) + self.space_station_z = bs_height + + self.calculate_coordinates() + + def calculate_coordinates(self, random_number_gen=np.random.RandomState()): + """ + Computes the coordinates of each site. This is where the actual layout calculation would be implemented. + """ + + d = self.intersite_distance + + self.x = [0] + self.y = [0] + + # First ring (6 points) + if self.num_sectors == 7 or self.num_sectors == 19: + + for k in range(6): + angle = k * 60 + self.x.append(d * np.cos(np.radians(angle))) + self.y.append(d * np.sin(np.radians(angle))) + + if self.num_sectors == 19: + # Coordinates with 19 sectors + # Second ring (12 points) + for k in range(6): + angle = k * 60 + self.x.append(2 * d * np.cos(np.radians(angle))) + self.y.append(2 * d * np.sin(np.radians(angle))) + self.x.append( + d * np.cos(np.radians(angle)) + + d * np.cos(np.radians(angle + 60)), + ) + self.y.append( + d * np.sin(np.radians(angle)) + + d * np.sin(np.radians(angle + 60)), + ) + + self.x = np.array(self.x) + self.y = np.array(self.y) + + # Assuming all points are at ground level + self.z = np.zeros(len(self.x)) + + # Rotate the anchor points by 30 degrees + theta = np.radians(30) + cos_theta = np.cos(theta) + sin_theta = np.sin(theta) + self.x_rotated = self.x * cos_theta - self.y * sin_theta + self.y_rotated = self.x * sin_theta + self.y * cos_theta + + # Calculate azimuth and elevation for each point + self.azimuth = np.arctan2( + self.y_rotated - self.space_station_y, + self.x_rotated - self.space_station_x, + ) * 180 / np.pi + distance_xy = np.sqrt( + (self.x_rotated - self.space_station_x) ** + 2 + (self.y_rotated - self.space_station_y)**2, + ) + self.elevation = np.arctan2( + self.z - self.space_station_z, distance_xy, + ) * 180 / np.pi + + # Update the number of base stations after setup + self.num_base_stations = len(self.x) + self.indoor = np.zeros(self.num_base_stations, dtype=bool) + + self.x = self.x_rotated + self.y = self.y_rotated + + def plot(self, axis: matplotlib.axes.Axes): + r = self.cell_radius / 1000 # Convert to kilometers + + # Plot each sector + for x, y in zip(self.x / 1000, self.y / 1000): # Convert to kilometers + hexagon = [] + for a in range(6): + angle_rad = math.radians(a * 60) + hexagon.append([ + x + r * math.cos(angle_rad), + y + r * math.sin(angle_rad), + ]) + hexagon.append(hexagon[0]) # Close the hexagon + hexagon = np.array(hexagon) + + sector = plt.Polygon(hexagon, fill=None, edgecolor='k') + axis.add_patch(sector) + + # Plot base stations + axis.scatter( + self.x / 1000, self.y / 1000, s=200, marker='v', c='k', edgecolor='k', linewidth=1, alpha=1, + label="Anchor Points", + ) + + # Add labels and title + axis.set_xlabel("x-coordinate [km]") + axis.set_ylabel("y-coordinate [km]") + axis.set_title(f"NTN Topology - {self.num_sectors} Sectors") + axis.legend() + plt.tight_layout() + + def plot_3d(self, axis: matplotlib.axes.Axes, map=False): + r = self.cell_radius / 1000 # Convert to kilometers + + if map: + # Load the map of Brazil using GeoPandas + brazil = gpd.read_file( + "${workspaceFolder}\\sharc\\topology\\countries\\ne_110m_admin_0_countries.shp", + ) + brazil = brazil[brazil['NAME'] == "Brazil"] + + # Coordinates of the Federal District (Brasรญlia) + federal_district_coords = (-47.9292, -15.7801) + + # Approximate conversion factors (1 degree latitude = 111 km, 1 degree longitude = 111 km) + lat_to_km = 111 + lon_to_km = 111 + + # Convert Federal District coordinates to kilometers + federal_district_coords_km = ( + federal_district_coords[0] * lon_to_km, federal_district_coords[1] * lat_to_km, + ) + + # Calculate the shift required to move the Federal District to (0, 0) + x_shift = federal_district_coords_km[0] + y_shift = federal_district_coords_km[1] + + # Manually plot the map of Brazil on the xy-plane + for geom in brazil.geometry: + if isinstance(geom, Polygon): + lon, lat = geom.exterior.xy + x = np.array(lon) * lon_to_km - x_shift + y = np.array(lat) * lat_to_km - y_shift + axis.plot(x, y, zs=0, zdir='z', color='lightgray') + elif isinstance(geom, MultiPolygon): + for poly in geom: + lon, lat = poly.exterior.xy + x = np.array(lon) * lon_to_km - x_shift + y = np.array(lat) * lat_to_km - y_shift + axis.plot(x, y, zs=0, zdir='z', color='lightgray') + + # Add the Federal District location to the plot + axis.scatter(0, 0, 0, color='red', zorder=5) + axis.text(0, 0, 0, 'Federal District', fontsize=12, ha='right') + + # Plot each sector + for x, y in zip(self.x / 1000, self.y / 1000): # Convert to kilometers + hexagon = [] + for a in range(6): + angle_rad = math.radians(a * 60) + hexagon.append([ + x + r * math.cos(angle_rad), + y + r * math.sin(angle_rad), + ]) + hexagon.append(hexagon[0]) # Close the hexagon + hexagon = np.array(hexagon) + + # 3D hexagon + axis.plot( + hexagon[:, 0], hexagon[:, 1], + np.zeros_like(hexagon[:, 0]), 'k-', + ) + + # Plot base stations + axis.scatter( + self.x / 1000, self.y / 1000, np.zeros_like(self.x), s=75, marker='v', c='k', edgecolor='k', + linewidth=1, alpha=1, + label="Anchor Points", + ) + + # Plot the satellite + axis.scatter( + self.space_station_x / 1000, self.space_station_y / 1000, self.space_station_z / 1000, s=75, c='r', + marker='^', edgecolor='k', linewidth=1, alpha=1, + label=f"Satellite (ฯ†={np.degrees(self.bs_azimuth):.1f}ยฐ, ฮธ={np.degrees(self.bs_elevation):.1f}ยฐ)", + ) + + # Plot the height line + axis.plot( + [self.space_station_x / 1000, self.space_station_x / 1000], + [self.space_station_y / 1000, self.space_station_y / 1000], + [0, self.space_station_z / 1000], 'b-', label=f'Height = {self.space_station_z / 1000:.1f} km', + ) + + # Plot the slant range line + axis.plot( + [0, self.space_station_x / 1000], + [0, self.space_station_y / 1000], + [0, self.space_station_z / 1000], 'g--', label=f'Slant range = {self.bs_radius / 1000:.1f} km', + ) + + # Add labels and title + axis.set_xlabel("x-coordinate [km]") + axis.set_ylabel("y-coordinate [km]") + axis.set_zlabel("z-coordinate [km]") + axis.set_title(f"NTN Topology - {self.num_sectors} Sectors") + axis.legend() + plt.tight_layout() + + +# Example usage +if __name__ == '__main__': + + bs_height = 1000e3 # meters + bs_azimuth = 45 # degrees + bs_elevation = 45 # degrees + beamwidth = 10 + cell_radius = bs_height * \ + math.tan(np.radians(beamwidth)) / math.cos(bs_elevation) + intersite_distance = cell_radius * np.sqrt(3) # meters + map = False + + # Test for 1 sector + ntn_topology_1 = TopologyNTN( + intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=1, + ) + ntn_topology_1.calculate_coordinates() # Calculate the site coordinates + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ntn_topology_1.plot_3d(ax, map) # Plot the 3D topology + plt.show() + + # Test for 7 sectors + ntn_topology_7 = TopologyNTN( + intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=7, + ) + ntn_topology_7.calculate_coordinates() # Calculate the site coordinates + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ntn_topology_7.plot_3d(ax, map) # Plot the 3D topology + plt.show() + + # Test for 19 sectors + ntn_topology_19 = TopologyNTN( + intersite_distance, cell_radius, bs_height, bs_azimuth, bs_elevation, num_sectors=19, + ) + ntn_topology_19.calculate_coordinates() # Calculate the site coordinates + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ntn_topology_19.plot_3d(ax, map) # Plot the 3D topology + plt.show() diff --git a/sharc/topology/topology_single_base_station.py b/sharc/topology/topology_single_base_station.py index 8fd3b932c..f19cfe944 100644 --- a/sharc/topology/topology_single_base_station.py +++ b/sharc/topology/topology_single_base_station.py @@ -11,6 +11,7 @@ from sharc.topology.topology import Topology + class TopologySingleBaseStation(Topology): """ Generates the a single base station centered at (0,0), with azimuth = 0ยฐ @@ -21,7 +22,6 @@ class TopologySingleBaseStation(Topology): AZIMUTH = [0, 180] ALLOWED_NUM_CLUSTERS = [1, 2] - def __init__(self, cell_radius: float, num_clusters: int): """ Constructor method that sets the object attributes @@ -31,10 +31,12 @@ def __init__(self, cell_radius: float, num_clusters: int): cell_radius : radius of the cell """ if num_clusters not in TopologySingleBaseStation.ALLOWED_NUM_CLUSTERS: - error_message = "invalid number of clusters ({})".format(num_clusters) + error_message = "invalid number of clusters ({})".format( + num_clusters, + ) raise ValueError(error_message) - intersite_distance = 2*cell_radius + intersite_distance = 2 * cell_radius super().__init__(intersite_distance, cell_radius) self.num_clusters = num_clusters @@ -47,24 +49,30 @@ def calculate_coordinates(self, random_number_gen=np.random.RandomState()): if self.num_clusters == 1: self.x = np.array([0]) self.y = np.array([0]) - self.azimuth = TopologySingleBaseStation.AZIMUTH[0]*np.ones(1) + self.azimuth = TopologySingleBaseStation.AZIMUTH[0] * np.ones( + 1, + ) self.num_base_stations = 1 elif self.num_clusters == 2: self.x = np.array([0, self.intersite_distance]) self.y = np.array([0, 0]) self.azimuth = np.array(TopologySingleBaseStation.AZIMUTH) self.num_base_stations = 2 - self.indoor = np.zeros(self.num_base_stations, dtype = bool) - + self.indoor = np.zeros(self.num_base_stations, dtype=bool) def plot(self, ax: matplotlib.axes.Axes): # plot base station - plt.scatter(self.x, self.y, color='g', edgecolor="w", linewidth=0.5, label="Hotspot") + plt.scatter( + self.x, self.y, color='g', edgecolor="w", + linewidth=0.5, label="Hotspot", + ) # plot base station coverage area for x, y, a in zip(self.x, self.y, self.azimuth): - pa = patches.Wedge( (x, y), self.cell_radius, a-60, a+60, fill=False, - edgecolor="green", linestyle='solid' ) + pa = patches.Wedge( + (x, y), self.cell_radius, a - 60, a + 60, fill=False, + edgecolor="green", linestyle='solid', + ) ax.add_patch(pa) @@ -74,7 +82,10 @@ def plot(self, ax: matplotlib.axes.Axes): topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() - fig = plt.figure(figsize=(8,8), facecolor='w', edgecolor='k') # create a figure object + fig = plt.figure( + figsize=(8, 8), facecolor='w', + edgecolor='k', + ) # create a figure object ax = fig.add_subplot(1, 1, 1) # create an axes object in the figure topology.plot(ax) @@ -85,4 +96,3 @@ def plot(self, ax: matplotlib.axes.Axes): plt.ylabel("y-coordinate [m]") plt.tight_layout() plt.show() - diff --git a/tests/_test_beamforming_normalizer.py b/tests/_test_beamforming_normalizer.py index dbe3db662..beae7061c 100644 --- a/tests/_test_beamforming_normalizer.py +++ b/tests/_test_beamforming_normalizer.py @@ -14,13 +14,14 @@ from sharc.support.named_tuples import AntennaPar from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt + class BeamformingNormalizerTest(unittest.TestCase): - + def setUp(self): # Test 1 resolution = 30 tolerance = 1e-2 - self.norm_1 = BeamformingNormalizer(resolution,tolerance) + self.norm_1 = BeamformingNormalizer(resolution, tolerance) adjacent_antenna_model = "SINGLE_ELEMENT" norm = False norm_file = None @@ -53,11 +54,11 @@ def setUp(self): multiplication_factor, minimum_array_gain, downtilt) - + # Test 2: UE configuration resolution = 5 tolerance = 1e-2 - self.norm_2 = BeamformingNormalizer(resolution,tolerance) + self.norm_2 = BeamformingNormalizer(resolution, tolerance) adjacent_antenna_model = "SINGLE_ELEMENT" norm = False norm_file = None @@ -90,11 +91,11 @@ def setUp(self): multiplication_factor, minimum_array_gain, downtilt) - + # Test 3: BS configuration resolution = 180 tolerance = 5e-2 - self.norm_3 = BeamformingNormalizer(resolution,tolerance) + self.norm_3 = BeamformingNormalizer(resolution, tolerance) adjacent_antenna_model = "SINGLE_ELEMENT" norm = False norm_file = None @@ -127,78 +128,79 @@ def setUp(self): multiplication_factor, minimum_array_gain, downtilt) - + def test_construction(self): # Test 1 - self.assertEqual(self.norm_1.resolution_deg,30) - self.assertEqual(self.norm_1.phi_min_deg,-180) - self.assertEqual(self.norm_1.phi_max_deg,180) - self.assertEqual(self.norm_1.theta_min_deg,0) - self.assertEqual(self.norm_1.theta_max_deg,180) - self.assertEqual(self.norm_1.phi_min_rad,-np.pi) - self.assertEqual(self.norm_1.phi_max_rad,np.pi) - self.assertEqual(self.norm_1.theta_min_rad,0) - self.assertEqual(self.norm_1.theta_max_rad,np.pi) - npt.assert_equal(self.norm_1.phi_vals_deg,np.arange(-180,180,30)) - npt.assert_equal(self.norm_1.theta_vals_deg,np.arange(0,180,30)) - + self.assertEqual(self.norm_1.resolution_deg, 30) + self.assertEqual(self.norm_1.phi_min_deg, -180) + self.assertEqual(self.norm_1.phi_max_deg, 180) + self.assertEqual(self.norm_1.theta_min_deg, 0) + self.assertEqual(self.norm_1.theta_max_deg, 180) + self.assertEqual(self.norm_1.phi_min_rad, -np.pi) + self.assertEqual(self.norm_1.phi_max_rad, np.pi) + self.assertEqual(self.norm_1.theta_min_rad, 0) + self.assertEqual(self.norm_1.theta_max_rad, np.pi) + npt.assert_equal(self.norm_1.phi_vals_deg, np.arange(-180, 180, 30)) + npt.assert_equal(self.norm_1.theta_vals_deg, np.arange(0, 180, 30)) + def test_calculate_correction_factor(self): # Test 1: omni pattern azi = 0 ele = 0 - self.norm_1.antenna = AntennaBeamformingImt(self.par_1,azi,ele) + self.norm_1.antenna = AntennaBeamformingImt(self.par_1, azi, ele) # Test adjacent channel case: single antenna element c_chan = False - c_fac, err = self.norm_1.calculate_correction_factor(0,0,c_chan) - self.assertAlmostEqual(c_fac,0.0,delta = 1e-2) - self.assertLess(np.max(np.abs(err)),1e-3) - + c_fac, err = self.norm_1.calculate_correction_factor(0, 0, c_chan) + self.assertAlmostEqual(c_fac, 0.0, delta=1e-2) + self.assertLess(np.max(np.abs(err)), 1e-3) + # Test 2: UE element pattern azi = 0 ele = 0 - self.norm_2.antenna = AntennaBeamformingImt(self.par_2,azi,ele) + self.norm_2.antenna = AntennaBeamformingImt(self.par_2, azi, ele) # Test adjacent channel case: single antenna element c_chan = False - c_fac, err = self.norm_2.calculate_correction_factor(0,0,c_chan) - self.assertAlmostEqual(c_fac,2.4,delta = 1e-1) - self.assertGreater(err[0],2.35) - self.assertLess(err[1],2.45) - + c_fac, err = self.norm_2.calculate_correction_factor(0, 0, c_chan) + self.assertAlmostEqual(c_fac, 2.4, delta=1e-1) + self.assertGreater(err[0], 2.35) + self.assertLess(err[1], 2.45) + # Test 3.1: BS element pattern azi = 0 ele = 0 - self.norm_3.antenna = AntennaBeamformingImt(self.par_3,azi,ele) + self.norm_3.antenna = AntennaBeamformingImt(self.par_3, azi, ele) # Test adjacent channel case: single antenna element c_chan = False - c_fac, err = self.norm_3.calculate_correction_factor(0,0,c_chan) - self.assertAlmostEqual(c_fac,4.8,delta = 1e-1) - self.assertGreater(err[0],4.75) - self.assertLess(err[1],4.85) - + c_fac, err = self.norm_3.calculate_correction_factor(0, 0, c_chan) + self.assertAlmostEqual(c_fac, 4.8, delta=1e-1) + self.assertGreater(err[0], 4.75) + self.assertLess(err[1], 4.85) + # Test 3.2: BS array azi = 0 ele = 0 - self.norm_3.antenna = AntennaBeamformingImt(self.par_3,azi,ele) + self.norm_3.antenna = AntennaBeamformingImt(self.par_3, azi, ele) # Test adjacent channel case: single antenna element c_chan = True - c_fac, err = self.norm_3.calculate_correction_factor(0,0,c_chan) - self.assertAlmostEqual(c_fac,8.0,delta = 1e-1) - self.assertGreater(err[0],7.5) - self.assertLess(err[1],8.5) - + c_fac, err = self.norm_3.calculate_correction_factor(0, 0, c_chan) + self.assertAlmostEqual(c_fac, 8.0, delta=1e-1) + self.assertGreater(err[0], 7.5) + self.assertLess(err[1], 8.5) + def test_generate_correction_matrix(self): # Test 3.1: BS element pattern file_name = "test_2.npz" self.norm_3.generate_correction_matrix(self.par_3, file_name, True) - data = np.load(file_name) + data = np.load(file_name) self.assertAlmostEqual(data['correction_factor_adj_channel'], - 4.8,delta = 1e-1) - + 4.8, delta=1e-1) + # Test 3.2: BS array npt.assert_allclose(data['correction_factor_co_channel'], - np.array([[8.0],[8.0]]),atol = 1e-1) + np.array([[8.0], [8.0]]), atol=1e-1) data.close() os.remove(file_name) - + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/_test_propagation_p619.py b/tests/_test_propagation_p619.py index 5eeb66359..863b2bcb3 100644 --- a/tests/_test_propagation_p619.py +++ b/tests/_test_propagation_p619.py @@ -23,7 +23,7 @@ class TestPropagationP619(unittest.TestCase): def setUp(self): self.p619 = PropagationP619(np.random.RandomState()) - def test_atmospheric_gasses_loss (self): + def test_atmospheric_gasses_loss(self): # compare with benchmark from ITU-R P-619 Fig. 3 frequency_MHz = 30000. sat_params = DummyParams() @@ -47,13 +47,12 @@ def test_atmospheric_gasses_loss (self): self.assertLessEqual(loss_lower, loss) self.assertGreaterEqual(loss_upper, loss) - def test_beam_spreading_attenuation(self): # compare with benchmark from ITU-R P-619 Fig. 7 - altitude_vec = np.array([0,1,2,3,4,6]) * 1000 - elevation_vec = [0,.5,1,2,3,5] - att_lower_vec = [.8, .6 , .4, .2, .1, .05] + altitude_vec = np.array([0, 1, 2, 3, 4, 6]) * 1000 + elevation_vec = [0, .5, 1, 2, 3, 5] + att_lower_vec = [.8, .6, .4, .2, .1, .05] att_upper_vec = [.9, .7, .5, .3, .2, .1] earth_to_space_vec = [True, False, True, False, True, False] @@ -63,7 +62,8 @@ def test_beam_spreading_attenuation(self): att_upper_vec, earth_to_space_vec): - attenuation = self.p619._get_beam_spreading_att(elevation, altitude, earth_to_space) + attenuation = self.p619._get_beam_spreading_att( + elevation, altitude, earth_to_space) self.assertLessEqual(lower, abs(attenuation)) self.assertGreaterEqual(upper, abs(attenuation)) diff --git a/tests/main.py b/tests/main.py index 2f80560b4..d1f6069ac 100644 --- a/tests/main.py +++ b/tests/main.py @@ -13,5 +13,5 @@ testRunner = unittest.runner.TextTestRunner() test_results = testRunner.run(tests) -if(test_results.errors != [] or test_results.failures != []): sys.exit(1) - \ No newline at end of file +if (test_results.errors != [] or test_results.failures != []): + sys.exit(1) diff --git a/sharc/input/__init__.py b/tests/parameters/__init__.py similarity index 92% rename from sharc/input/__init__.py rename to tests/parameters/__init__.py index faaaf799c..40a96afc6 100644 --- a/sharc/input/__init__.py +++ b/tests/parameters/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - - diff --git a/tests/parameters/parameters_for_testing.ini b/tests/parameters/parameters_for_testing.ini new file mode 100644 index 000000000..f192b9802 --- /dev/null +++ b/tests/parameters/parameters_for_testing.ini @@ -0,0 +1,813 @@ +[GENERAL] +########################################################################### +# Number of simulation snapshots +num_snapshots = 100 +########################################################################### +# IMT link that will be simulated (DOWNLINK or UPLINK) +imt_link = DOWNLINK +########################################################################### +# The chosen system for sharing study +# EESS_SS, FSS_SS, FSS_ES, FS, RAS +system = FSS_ES +########################################################################### +# Compatibility scenario (co-channel and/or adjacent channel interference) +enable_cochannel = FALSE +enable_adjacent_channel = TRUE +########################################################################### +# Seed for random number generator +seed = 101 +########################################################################### +# if FALSE, then a new output directory is created +overwrite_output = TRUE + +[IMT] +########################################################################### +# Network topology. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" +# "INDOOR" +topology = INDOOR +########################################################################### +# 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 = 399 +########################################################################### +# Minimum 2D separation distance from BS to UE [m] +minimum_separation_distance_bs_ue = 1.3 +########################################################################### +# Defines if IMT service is the interferer or interfered-with service +# TRUE : IMT suffers interference +# FALSE : IMT generates interference +interfered_with = FALSE +########################################################################### +# IMT center frequency [MHz] +frequency = 24360 +########################################################################### +# IMT bandwidth [MHz] +bandwidth = 200.5 +########################################################################### +# IMT resource block bandwidth [MHz] +rb_bandwidth = 0.181 +########################################################################### +# IMT spectrum emission mask. Options are: +# "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 +# "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in +# TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) +spectral_mask = 3GPP E-UTRA +########################################################################### +# level of spurious emissions [dBm/MHz] +spurious_emissions = -13.1 +########################################################################### +# Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 +# means that 10% of the total bandwidth will be used as guard band: 5% in +# the lower +guard_band_ratio = 0.14 +########################################################################### +# The load probability (or activity factor) models the statistical +# variation of the network load by defining the number of fully loaded +# base stations that are simultaneously transmitting +bs_load_probability = .2 +########################################################################### +# Conducted power per antenna element [dBm/bandwidth] +bs_conducted_power = 11.1 +########################################################################### +# Base station height [m] +bs_height = 6.1 +########################################################################### +# Base station noise figure [dB] +bs_noise_figure = 10.1 +########################################################################### +# User equipment noise temperature [K] +bs_noise_temperature = 290.1 +########################################################################### +# Base station array ohmic loss [dB] +bs_ohmic_loss = 3.1 +########################################################################### +# Uplink attenuation factor used in link-to-system mapping +ul_attenuation_factor = 0.4 +########################################################################### +# Uplink minimum SINR of the code set [dB] +ul_sinr_min = -10 +########################################################################### +# Uplink maximum SINR of the code set [dB] +ul_sinr_max = 22 +########################################################################### +# Number of UEs that are allocated to each cell within handover margin. +# Remember that in macrocell network each base station has 3 cells (sectors) +ue_k = 3 +########################################################################### +# Multiplication factor that is used to ensure that the sufficient number +# of UE's will distributed throughout ths system area such that the number +# of K users is allocated to each cell. Normally, this values varies +# between 2 and 10 according to the user drop method +ue_k_m = 1 +########################################################################### +# Percentage of indoor UE's [%] +ue_indoor_percent = 5 +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter states how the UEs will be distributed +# Possible values: UNIFORM : UEs will be uniformly distributed within the +# whole simulation area. Not applicable to +# hotspots. +# ANGLE_AND_DISTANCE : UEs will be distributed following +# given distributions for angle and +# distance. In this case, these must be +# defined later. +ue_distribution_type = ANGLE_AND_DISTANCE +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter models the distance between UE's and BS. +# Possible values: RAYLEIGH, UNIFORM +ue_distribution_distance = UNIFORM +########################################################################### +# Regarding the distribution of active UE's over the cell area, this +# parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). +# Possible values: NORMAL, UNIFORM +ue_distribution_azimuth = NORMAL +########################################################################### +# Power control algorithm +# ue_tx_power_control = "ON",power control On +# ue_tx_power_control = "OFF",power control Off +ue_tx_power_control = ON +########################################################################### +# Power per RB used as target value [dBm] +ue_p_o_pusch = -95 +########################################################################### +# Alfa is the balancing factor for UEs with bad channel +# and UEs with good channel +ue_alpha = 1 +########################################################################### +# Maximum UE transmit power [dBm] +ue_p_cmax = 22 +########################################################################### +# UE power dynamic range [dB] +# The minimum transmit power of a UE is (ue_p_cmax - ue_dynamic_range) +ue_power_dynamic_range = 63 +########################################################################### +# UE height [m] +ue_height = 1.5 +########################################################################### +# User equipment noise figure [dB] +ue_noise_figure = 10 +########################################################################### +# User equipment feed loss [dB] +ue_ohmic_loss = 3 +########################################################################### +# User equipment body loss [dB] +ue_body_loss = 4 +########################################################################### +# Downlink attenuation factor used in link-to-system mapping +dl_attenuation_factor = 0.6 +########################################################################### +# Downlink minimum SINR of the code set [dB] +dl_sinr_min = -10 +########################################################################### +# Downlink maximum SINR of the code set [dB] +dl_sinr_max = 30 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "CI" (close-in FS reference distance) +# "UMa" (Urban Macro - 3GPP) +# "UMi" (Urban Micro - 3GPP) +# "TVRO-URBAN" +# "TVRO-SUBURBAN" +# "ABG" (Alpha-Beta-Gamma) +channel_model = FSPL +########################################################################### +# Adjustment factor for LoS probability in UMi path loss model. +# Original value: 18 (3GPP) +los_adjustment_factor = 18 +########################################################################### +# If shadowing should be applied or not +shadowing = FALSE +noise_temperature = 290 + +[IMT_ANTENNA] +########################################################################### +# Defines the antenna model to be used in compatibility studies between +# IMT and other services in adjacent band +# Possible values: SINGLE_ELEMENT, BEAMFORMING +adjacent_antenna_model = BEAMFORMING +########################################################################### +# If normalization of M2101 should be applied for BS +bs_normalization = FALSE +########################################################################### +# If normalization of M2101 should be applied for UE +ue_normalization = FALSE +########################################################################### +# File to be used in the BS beamforming normalization +# Normalization files can be generated with the +# antenna/beamforming_normalization/normalize_script.py script +bs_normalization_file = antenna/beamforming_normalization/bs_norm.npz +########################################################################### +# File to be used in the UE beamforming normalization +# Normalization files can be generated with the +# antenna/beamforming_normalization/normalize_script.py script +ue_normalization_file = antenna/beamforming_normalization/ue_norm.npz +########################################################################### +# Radiation pattern of each antenna element +# Possible values: "M2101", "F1336", "FIXED" +bs_element_pattern = F1336 +ue_element_pattern = F1336 +########################################################################### +# Minimum array gain for the beamforming antenna [dBi] +bs_minimum_array_gain = -200 +ue_minimum_array_gain = -200 +########################################################################### +# mechanical downtilt [degrees] +# NOTE: consider defining it to 90 degrees in case of indoor simulations +bs_downtilt = 6 +########################################################################### +# BS/UE maximum transmit/receive element gain [dBi] +# default: bs_element_max_g = 5, for M.2101 +# = 15, for M.2292 +# default: ue_element_max_g = 5, for M.2101 +# = -3, for M.2292 +bs_element_max_g = 5 +ue_element_max_g = 5 +########################################################################### +# BS/UE horizontal 3dB beamwidth of single element [degrees] +bs_element_phi_3db = 65 +ue_element_phi_3db = 90 +########################################################################### +# BS/UE vertical 3dB beamwidth of single element [degrees] +# For F1336: if equal to 0, then beamwidth is calculated automaticaly +bs_element_theta_3db = 65 +ue_element_theta_3db = 90 +########################################################################### +# BS/UE number of rows in antenna array +bs_n_rows = 8 +ue_n_rows = 4 +########################################################################### +# BS/UE number of columns in antenna array +bs_n_columns = 8 +ue_n_columns = 4 +########################################################################### +# BS/UE array horizontal element spacing (d/lambda) +bs_element_horiz_spacing = 0.5 +ue_element_horiz_spacing = 0.5 +########################################################################### +# BS/UE array vertical element spacing (d/lambda) +bs_element_vert_spacing = 0.5 +ue_element_vert_spacing = 0.5 +########################################################################### +# BS/UE front to back ratio of single element [dB] +bs_element_am = 30 +ue_element_am = 25 +########################################################################### +# BS/UE single element vertical sidelobe attenuation [dB] +bs_element_sla_v = 30 +ue_element_sla_v = 25 +########################################################################### +# Multiplication factor k that is used to adjust the single-element pattern. +# According to Report ITU-R M.[IMT.AAS], this may give a closer match of the +# side lobes when beamforming is assumed in adjacent channel. +# Original value: 12 (Rec. ITU-R M.2101) +bs_multiplication_factor = 12 +ue_multiplication_factor = 12 + +[HOTSPOT] +########################################################################### +# Number of hotspots per macro cell (sector) +num_hotspots_per_cell = 1 +########################################################################### +# Maximum 2D distance between hotspot and UE [m] +# This is the hotspot radius +max_dist_hotspot_ue = 99.9 +########################################################################### +# Minimum 2D distance between macro cell base station and hotspot [m] +min_dist_bs_hotspot = 1.2 + +[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 + +[FSS_SS] +########################################################################### +# satellite center frequency [MHz] +frequency = 43000 +########################################################################### +# satellite bandwidth [MHz] +bandwidth = 200 +########################################################################### +# satellite altitude [m] and latitude [deg] +altitude = 35780000 +lat_deg = 0 +########################################################################### +# Elevation angle [deg] +elevation = 270 +########################################################################### +# Azimuth angle [deg] +azimuth = 0 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -5 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 950 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 0 +########################################################################### +# Satellite peak receive antenna gain [dBi] +antenna_gain = 46.6 +########################################################################### +# Antenna pattern of the FSS space station +# Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" +antenna_pattern = FSS_SS +# IMT parameters relevant to the satellite system +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +imt_altitude = 0 +imt_lat_deg = 0 +imt_long_diff_deg = 0 +season = SUMMER +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 + clutter loss) +# "P619" +channel_model = P619 +########################################################################### +# The required near-in-side-lobe level (dB) relative to peak gain +# according to ITU-R S.672-4 +antenna_l_s = -20 +########################################################################### +# 3 dB beamwidth angle (3 dB below maximum gain) [degrees] +antenna_3_dB = 0.65 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + +[FSS_ES] +########################################################################### +# type of FSS-ES location: +# FIXED - position must be given +# CELL - random within central cell +# NETWORK - random within whole network +# UNIFORM_DIST - uniform distance from cluster centre, +# between min_dist_to_bs and max_dist_to_bs +location = UNIFORM_DIST +########################################################################### +# x-y coordinates [m] (only if FIXED location is chosen) +x = 10000 +y = 0 +########################################################################### +# minimum distance from BSs [m] +min_dist_to_bs = 10 +########################################################################### +# maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen) +max_dist_to_bs = 600 +########################################################################### +# antenna height [m] +height = 6 +########################################################################### +# Elevation angle [deg], minimum and maximum, values +elevation_min = 48 +elevation_max = 80 +########################################################################### +# Azimuth angle [deg] +# either a specific angle or string 'RANDOM' +azimuth = RANDOM +########################################################################### +# center frequency [MHz] +frequency = 43000 +########################################################################### +# bandwidth [MHz] +bandwidth = 6 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 950 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 0 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -68.3 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 32 +########################################################################### +# Antenna pattern of the FSS Earth station +# Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", +# "Modified ITU-R S.465" +antenna_pattern = Modified ITU-R S.465 +########################################################################### +# Diameter of antenna [m] +diameter = 1.8 +########################################################################### +# Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model +antenna_envelope_gain = 0 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +# "P452" +# "TVRO-URBAN" +# "TVRO-SUBURBAN" +# "HDFSS" +channel_model = P452 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 +########################################################################### +# P452 parameters +########################################################################### +# Total air pressure in hPa +atmospheric_pressure = 935 +########################################################################### +# Temperature in Kelvin +air_temperature = 300 +########################################################################### +#Sea-level surface refractivity (use the map) +N0 = 352.58 +########################################################################### +#Average radio-refractive (use the map) +delta_N = 43.127 +########################################################################### +#percentage p. Float (0 to 100) or RANDOM +percentage_p = 0.2 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dct = 70 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dcr = 70 +########################################################################### +##Effective height of interfering antenna (m) +Hte = 20 +########################################################################### +#Effective height of interfered-with antenna (m) +Hre = 3 +########################################################################### +##Latitude of transmitter +tx_lat = -23.55028 +########################################################################### +#Latitude of receiver +rx_lat = -23.17889 +########################################################################### +#Antenna polarization +polarization = horizontal +########################################################################### +#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) +clutter_loss = TRUE +########################################################################### +# HDFSS propagation parameters +########################################################################### +# HDFSS position relative to building it is on. Possible values are +# ROOFTOP and BUILDINGSIDE +es_position = ROOFTOP +########################################################################### +# Enable shadowing loss +shadow_enabled = TRUE +########################################################################### +# Enable building entry loss +building_loss_enabled = TRUE +########################################################################### +# Enable interference from IMT stations at the same building as the HDFSS +same_building_enabled = FALSE +########################################################################### +# Enable diffraction loss +diffraction_enabled = FALSE +########################################################################### +# Building entry loss type applied between BSs and HDFSS ES. Options are: +# P2109_RANDOM: random probability at P.2109 model, considering elevation +# P2109_FIXED: fixed probability at P.2109 model, considering elevation. +# Probability must be specified in bs_building_entry_loss_prob. +# FIXED_VALUE: fixed value per BS. Value must be specified in +# bs_building_entry_loss_value. +bs_building_entry_loss_type = P2109_FIXED +########################################################################### +# Probability of building entry loss not exceeded if +# bs_building_entry_loss_type = P2109_FIXED +bs_building_entry_loss_prob = 0.75 +########################################################################### +# Value in dB of building entry loss if +# bs_building_entry_loss_type = FIXED_VALUE +bs_building_entry_loss_value = 35 + +[FS] +########################################################################### +# x-y coordinates [m] +x = 1000 +y = 0 +########################################################################### +# antenna height [m] +height = 15 +########################################################################### +# Elevation angle [deg] +elevation = -10 +########################################################################### +# Azimuth angle [deg] +azimuth = 180 +########################################################################### +# center frequency [MHz] +frequency = 27250 +########################################################################### +# bandwidth [MHz] +bandwidth = 112 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 290 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 20 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -68.3 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 36.9 +########################################################################### +# Antenna pattern of the fixed wireless service +# Possible values: "ITU-R F.699", "OMNI" +antenna_pattern = OMNI +########################################################################### +# Diameter of antenna [m] +diameter = 0.3 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +channel_model = TerrestrialSimple + +[HAPS] +########################################################################### +# HAPS center frequency [MHz] +frequency = 27251.1 +########################################################################### +# HAPS bandwidth [MHz] +bandwidth = 200 +########################################################################### +# HAPS altitude [m] and latitude [deg] +altitude = 20001.1 +lat_deg = 0.1 +########################################################################### +# Elevation angle [deg] +elevation = 270 +########################################################################### +# Azimuth angle [deg] +azimuth = 0 +########################################################################### +# EIRP spectral density [dBW/MHz] +eirp_density = 4.4 +########################################################################### +# HAPS peak antenna gain [dBi] +antenna_gain = 28.1 +########################################################################### +# Adjacent channel selectivity [dB] +acs = 30 +########################################################################### +# Antenna pattern of the HAPS (airbone) station +# Possible values: "ITU-R F.1891", "OMNI" +antenna_pattern = OMNI +# IMT parameters relevant to the HAPS system +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# difference between longitudes of IMT and satellite system +# (positive if space-station is to the East of earth-station) +imt_altitude = 0 +imt_lat_deg = 0 +imt_long_diff_deg = 0 +season = SUMMER + +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 + clutter loss) +# "P619" +channel_model = P619 + +########################################################################### +# Near side-lobe level (dB) relative to the peak gain required by the system +# design, and has a maximum value of โˆ’25 dB +antenna_l_n = -25 +########################################################################### +# Constants +BOLTZMANN_CONSTANT = 1.38064852e-23 +EARTH_RADIUS = 6371000 + +[RNS] +########################################################################### +# x-y coordinates [m] +x = 660.1 +y = -370.1 +########################################################################### +# altitude [m] +altitude = 150.1 +########################################################################### +# center frequency [MHz] +frequency = 32000.1 +########################################################################### +# bandwidth [MHz] +bandwidth = 60.1 +########################################################################### +# System receive noise temperature [K] +noise_temperature = 1154.1 +########################################################################### +# Peak transmit power spectral density (clear sky) [dBW/Hz] +tx_power_density = -70.79 +########################################################################### +# antenna peak gain [dBi] +antenna_gain = 30.1 +########################################################################### +# Adjacent channel selectivity [dB] +acs = 30.1 +########################################################################### +# Antenna pattern of the fixed wireless service +# Possible values: "ITU-R M.1466", "OMNI" +antenna_pattern = ITU-R M.1466 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "SatelliteSimple" (FSPL + 4 dB + clutter loss) +# "P619" +channel_model = P619 +########################################################################### +# Specific parameters for P619 +season = SUMMER +imt_altitude = 0 +imt_lat_deg = 0 + +[RAS] +########################################################################### +# x-y coordinates [m] +x = 81000.1 +y = 0.1 +########################################################################### +# antenna height [m] +height = 15.1 +########################################################################### +# Elevation angle [deg] +elevation = 45.1 +########################################################################### +# Azimuth angle [deg] +azimuth = -90.1 +########################################################################### +# center frequency [MHz] +frequency = 43000.1 +########################################################################### +# bandwidth [MHz] +bandwidth = 1000.1 +########################################################################### +# Antenna noise temperature [K] +antenna_noise_temperature = 25.1 +########################################################################### +# Receiver noise temperature [K] +receiver_noise_temperature = 65.1 +########################################################################### +# adjacent channel selectivity (dB) +adjacent_ch_selectivity = 20.1 +########################################################################### +# Antenna efficiency +antenna_efficiency = 0.9 +########################################################################### +# Antenna pattern of the FSS Earth station +# Possible values: "ITU-R SA.509", "OMNI" +antenna_pattern = ITU-R SA.509 +########################################################################### +# Antenna gain for "OMNI" pattern +antenna_gain = 0.5 +########################################################################### +# Diameter of antenna [m] +diameter = 15.1 + +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "TerrestrialSimple" (FSPL + clutter loss) +# "P452" +channel_model = P452 +########################################################################### +# P452 parameters +########################################################################### +# Total air pressure in hPa +atmospheric_pressure = 935.2 +########################################################################### +# Temperature in Kelvin +air_temperature = 300.1 +########################################################################### +#Sea-level surface refractivity (use the map) +N0 = 352.58 +########################################################################### +#Average radio-refractive (use the map) +delta_N = 43.127 +########################################################################### +#percentage p. Float (0 to 100) or RANDOM +percentage_p = 0.2 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dct = 70.1 +########################################################################### +#Distance over land from the transmit and receive antennas to the coast (km) +Dcr = 70.1 +########################################################################### +##Effective height of interfering antenna (m) +Hte = 20.1 +########################################################################### +#Effective height of interfered-with antenna (m) +Hre = 3.1 +########################################################################### +##Latitude of transmitter +tx_lat = -23.55028 +########################################################################### +#Latitude of receiver +rx_lat = -23.17889 +########################################################################### +#Antenna polarization +polarization = horizontal +########################################################################### +#determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) +clutter_loss = TRUE + +[EESS_SS] +########################################################################### +# sensor center frequency [MHz] +frequency = 23900.1 +########################################################################### +# sensor bandwidth [MHz] +bandwidth = 200.1 +########################################################################### +# Off-nadir pointing angle [deg] +nadir_angle = 46.6 +########################################################################### +# sensor altitude [m] +altitude = 828000.1 +########################################################################### +# Antenna pattern of the sensor +# Possible values: "ITU-R RS.1813" +# "ITU-R RS.1861 9a" +# "ITU-R RS.1861 9b" +# "ITU-R RS.1861 9c" +# "OMNI" +antenna_pattern = ITU-R RS.1813 +# Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] +antenna_efficiency = 0.6 +# Antenna diameter for ITU-R RS.1813 [m] +antenna_diameter = 2.2 +########################################################################### +# receive antenna gain - applicable for 9a, 9b and OMNI [dBi] +antenna_gain = 52.1 +########################################################################### +# Channel parameters +# channel model, possible values are "FSPL" (free-space path loss), +# "P619" +channel_model = FSPL +# Relevant IMT parameters which apply for ITU-R P.619 +# altitude of IMT system (in meters) +# latitude of IMT system (in degrees) +# season of the year: "SUMMER", "WINTER" +imt_altitude = 20.1 +imt_lat_deg = -22.9 +season = WINTER +###########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 = ON +distribution_type = UNIFORM +nadir_angle_distribution = 18.6,49.4 diff --git a/tests/parameters/parameters_for_testing.yaml b/tests/parameters/parameters_for_testing.yaml new file mode 100644 index 000000000..153e91bf4 --- /dev/null +++ b/tests/parameters/parameters_for_testing.yaml @@ -0,0 +1,1119 @@ +general: + ########################################################################### + # Number of simulation snapshots + num_snapshots : 100 + ########################################################################### + # IMT link that will be simulated (DOWNLINK or UPLINK) + imt_link : DOWNLINK + ########################################################################### + # The chosen system for sharing study + # EESS_PASSIVE, FSS_SS, FSS_ES, FS, RAS + system : FSS_ES + ########################################################################### + # Compatibility scenario (co-channel and/or adjacent channel interference) + enable_cochannel : FALSE + enable_adjacent_channel : TRUE + ########################################################################### + # Seed for random number generator + seed : 101 + ########################################################################### + # if FALSE, then a new output directory is created + overwrite_output : TRUE +imt: + ########################################################################### + # Minimum 2D separation distance from BS to UE [m] + minimum_separation_distance_bs_ue : 1.3 + ########################################################################### + # Defines if IMT service is the interferer or interfered-with service + # TRUE : IMT suffers interference + # FALSE : IMT generates interference + interfered_with : FALSE + ########################################################################### + # IMT center frequency [MHz] + frequency : 24360 + ########################################################################### + # IMT bandwidth [MHz] + bandwidth : 200.5 + ########################################################################### + # IMT resource block bandwidth [MHz] + rb_bandwidth : 0.181 + ########################################################################### + # IMT spectrum emission mask. Options are: + # "IMT-2020" : for mmWave as described in ITU-R TG 5/1 Contribution 36 + # "3GPP E-UTRA" : for E-UTRA bands > 1 GHz as described in + # TS 36.104 v11.2.0 (BS) and TS 36.101 v11.2.0 (UE) + spectral_mask : 3GPP E-UTRA + ########################################################################### + # level of spurious emissions [dBm/MHz] + spurious_emissions : -13.1 + ########################################################################### + # Amount of guard band wrt total bandwidth. Setting this parameter to 0.1 + # means that 10% of the total bandwidth will be used as guard band: 5% in + # the lower + guard_band_ratio : 0.14 + ########################################################################### + # Network topology parameters. + topology: + ########################################################################### + # Topology Type. Possible values are "MACROCELL", "HOTSPOT", "SINGLE_BS" + # "INDOOR" + type: INDOOR + ########################################################################### + # Macrocell Topology parameters. Relevant when imt.topology.type == "MACROCELL" + macrocell: + ########################################################################### + # Inter-site distance in macrocell network topology [m] + intersite_distance: 543 + ########################################################################### + # Enable wrap around. + wrap_around: true + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Single Base Station Topology parameters. Relevant when imt.topology.type == "SINGLE_BS" + single_bs: + ########################################################################### + # Inter-site distance or Cell Radius in single Base Station network topology [m] + # You can either provide 'cell_radius' or 'intersite_distance' for this topology + # The relationship used is cell_radius = intersite_distance * 2 / 3 + cell_radius: 543 + # intersite_distance: 1 + ########################################################################### + # Number of clusters in single base station topology + # You can simulate 1 or 2 BS's with this topology + num_clusters: 2 + ########################################################################### + # Hotspot Topology parameters. Relevant when imt.topology.type == "HOTSPOT" + hotspot: + ########################################################################### + # Inter-site distance in hotspot network topology [m] + intersite_distance: 321 + ########################################################################### + # Enable wrap around. + wrap_around: true + ########################################################################### + # Number of clusters in macro cell topology + num_clusters: 7 + ########################################################################### + # Number of hotspots per macro cell (sector) + num_hotspots_per_cell : 1 + ########################################################################### + # Maximum 2D distance between hotspot and UE [m] + # This is the hotspot radius + max_dist_hotspot_ue : 99.9 + ########################################################################### + # Minimum 2D distance between macro cell base station and hotspot [m] + min_dist_bs_hotspot : 1.2 + ########################################################################### + # Indoor Topology parameters. Relevant when imt.topology.type == "INOOR" + indoor: + ########################################################################### + # Basic path loss model for indoor topology. Possible values: + # "FSPL" (free-space path loss), + # "INH_OFFICE" (3GPP Indoor Hotspot - Office) + basic_path_loss : FSPL + ########################################################################### + # Number of rows of buildings in the simulation scenario + n_rows : 3 + ########################################################################### + # Number of colums of buildings in the simulation scenario + n_colums : 2 + ########################################################################### + # Number of buildings containing IMT stations. Options: + # 'ALL': all buildings contain IMT stations. + # Number of buildings. + num_imt_buildings : 2 + ########################################################################### + # Street width (building separation) [m] + street_width : 30.1 + ########################################################################### + # Intersite distance [m] + intersite_distance : 40.1 + ########################################################################### + # Number of cells per floor + num_cells : 3 + ########################################################################### + # Number of floors per building + num_floors : 1 + ########################################################################### + # Percentage of indoor UE's [0, 1] + ue_indoor_percent : .95 + ########################################################################### + # Building class: "TRADITIONAL" or "THERMALLY_EFFICIENT" + building_class : THERMALLY_EFFICIENT + ########################################################################### + # NTN Topology Parameters + ntn: + ########################################################################### + # NTN cell radius or intersite distance in network topology [m] + # @important: You can set only one of cell_radius or intersite_distance + # NTN Intersite Distance (m). Intersite distance = Cell Radius * sqrt(3) + # NOTE: note that intersite distance has a different geometric meaning in ntn + cell_radius : 123 + # intersite_distance : 155884 + ########################################################################### + # NTN space station azimuth [degree] + bs_azimuth : 45 + ########################################################################### + # NTN space station elevation [degree] + bs_elevation : 45 + ########################################################################### + # number of sectors [degree] + num_sectors : 19 + ########################################################################### + # Conducted power per element [dBm/bandwidth] + bs_conducted_power : 37 + ########################################################################### + # Backoff Power [Layer 2] [dB]. Allowed: 7 sector topology - Layer 2 + bs_backoff_power : 3 + ########################################################################### + # NTN Antenna configuration + bs_n_rows_layer1 : 2 + bs_n_columns_layer1 : 2 + bs_n_rows_layer2 : 4 + bs_n_columns_layer2 : 2 + ########################################################################### + # Defines the antenna model to be used in compatibility studies between + # IMT and other services in adjacent band + # Possible values: SINGLE_ELEMENT, BEAMFORMING + adjacent_antenna_model : BEAMFORMING + # Base station parameters + bs: + ########################################################################### + # The load probability (or activity factor) models the statistical + # variation of the network load by defining the number of fully loaded + # base stations that are simultaneously transmitting + load_probability : .2 + ########################################################################### + # Conducted power per antenna element [dBm/bandwidth] + conducted_power : 11.1 + ########################################################################### + # Base station height [m] + height : 6.1 + ########################################################################### + # Base station noise figure [dB] + noise_figure : 10.1 + ########################################################################### + # Base station array ohmic loss [dB] + ohmic_loss : 3.1 + # Base Station Antenna parameters: + antenna: + ########################################################################### + # If normalization of M2101 should be applied for BS + normalization : FALSE + ########################################################################### + # 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 : F1336 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain : -200 + ########################################################################### + # mechanical downtilt [degrees] + # NOTE: consider defining it to 90 degrees in case of indoor simulations + downtilt : 6 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # default: element_max_g = 5, for M.2101 + # = 15, for M.2292 + # = -3, for M.2292 + element_max_g : 5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db : 65 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db : 65 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows : 8 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns : 8 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing : 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing : 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am : 30 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v : 30 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor : 12 + + ########################################################################### + # User Equipment parameters: + ue: + ########################################################################### + # Number of UEs that are allocated to each cell within handover margin. + # Remember that in macrocell network each base station has 3 cells (sectors) + k : 3 + ########################################################################### + # Multiplication factor that is used to ensure that the sufficient number + # of UE's will distributed throughout ths system area such that the number + # of K users is allocated to each cell. Normally, this values varies + # between 2 and 10 according to the user drop method + k_m : 1 + ########################################################################### + # Percentage of indoor UE's [%] + indoor_percent : 5 + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter states how the UEs will be distributed + # Possible values: UNIFORM : UEs will be uniformly distributed within the + # whole simulation area. Not applicable to + # hotspots. + # ANGLE_AND_DISTANCE : UEs will be distributed following + # given distributions for angle and + # distance. In this case, these must be + # defined later. + distribution_type : ANGLE_AND_DISTANCE + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the distance between UE's and BS. + # Possible values: RAYLEIGH, UNIFORM + distribution_distance : UNIFORM + ########################################################################### + # Regarding the distribution of active UE's over the cell area, this + # parameter models the azimuth between UE and BS (within ยฑ60ยฐ range). + # Possible values: NORMAL, UNIFORM + distribution_azimuth : NORMAL + ########################################################################### + # Azimuth range used when distributing the UE within the cell. + # Default value is (-60, 60) which representes a 120 degree sector. + # Change this to (-180, 180) when deploying the NTN topology + azimuth_range: !!python/tuple [-70, 90] + ########################################################################### + # Power control algorithm + # tx_power_control = "ON",power control On + # tx_power_control = "OFF",power control Off + tx_power_control : ON + ########################################################################### + # Power per RB used as target value [dBm] + p_o_pusch : -95 + ########################################################################### + # Alfa is the balancing factor for UEs with bad channel + # and UEs with good channel + alpha : 1 + ########################################################################### + # Maximum UE transmit power [dBm] + p_cmax : 22 + ########################################################################### + # UE power dynamic range [dB] + # The minimum transmit power of a UE is (p_cmax - dynamic_range) + power_dynamic_range : 63 + ########################################################################### + # UE height [m] + height : 1.5 + ########################################################################### + # User equipment noise figure [dB] + noise_figure : 10 + ########################################################################### + # User equipment feed loss [dB] + ohmic_loss : 3 + ########################################################################### + # User equipment body loss [dB] + body_loss : 4 + antenna: + ########################################################################### + # If normalization of M2101 should be applied for UE + normalization : FALSE + ########################################################################### + # File to be used in the UE beamforming normalization + # Normalization files can be generated with the + # antenna/beamforming_normalization/normalize_script.py script + normalization_file : antenna/beamforming_normalization/ue_norm.npz + ########################################################################### + # Radiation pattern of each antenna element + # Possible values: "M2101", "F1336", "FIXED" + element_pattern : F1336 + ########################################################################### + # Minimum array gain for the beamforming antenna [dBi] + minimum_array_gain : -200 + ########################################################################### + # BS/UE maximum transmit/receive element gain [dBi] + # = 15, for M.2292 + # default: element_max_g = 5, for M.2101 + # = -3, for M.2292 + element_max_g : 5 + ########################################################################### + # BS/UE horizontal 3dB beamwidth of single element [degrees] + element_phi_3db : 90 + ########################################################################### + # BS/UE vertical 3dB beamwidth of single element [degrees] + # For F1336: if equal to 0, then beamwidth is calculated automaticaly + element_theta_3db : 90 + ########################################################################### + # BS/UE number of rows in antenna array + n_rows : 4 + ########################################################################### + # BS/UE number of columns in antenna array + n_columns : 4 + ########################################################################### + # BS/UE array horizontal element spacing (d/lambda) + element_horiz_spacing : 0.5 + ########################################################################### + # BS/UE array vertical element spacing (d/lambda) + element_vert_spacing : 0.5 + ########################################################################### + # BS/UE front to back ratio of single element [dB] + element_am : 25 + ########################################################################### + # BS/UE single element vertical sidelobe attenuation [dB] + element_sla_v : 25 + ########################################################################### + # Multiplication factor k that is used to adjust the single-element pattern. + # According to Report ITU-R M.[IMT.AAS], this may give a closer match of the + # side lobes when beamforming is assumed in adjacent channel. + # Original value: 12 (Rec. ITU-R M.2101) + multiplication_factor : 12 + ########################################################################### + # Uplink parameters. Only needed when using uplink on imt + uplink: + ########################################################################### + # Uplink attenuation factor used in link-to-system mapping + attenuation_factor : 0.4 + ########################################################################### + # Uplink minimum SINR of the code set [dB] + sinr_min : -10 + ########################################################################### + # Uplink maximum SINR of the code set [dB] + sinr_max : 22 + ########################################################################### + # Downlink parameters. Only needed when using donwlink on imt + downlink: + ########################################################################### + # Downlink attenuation factor used in link-to-system mapping + attenuation_factor : 0.6 + ########################################################################### + # Downlink minimum SINR of the code set [dB] + sinr_min : -10 + ########################################################################### + # Downlink maximum SINR of the code set [dB] + sinr_max : 30 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "CI" (close-in FS reference distance) + # "UMa" (Urban Macro - 3GPP) + # "UMi" (Urban Micro - 3GPP) + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "ABG" (Alpha-Beta-Gamma) + # "P619" + channel_model : FSPL + ########################################################################### + # Adjustment factor for LoS probability in UMi path loss model. + # Original value: 18 (3GPP) + los_adjustment_factor : 18 + ########################################################################### + # If shadowing should be applied or not + shadowing : FALSE + noise_temperature : 290 + param_p619: + ########################################################################### + # altitude of the space station [m] + space_station_alt_m: 540000 + ########################################################################### + # altitude of ES system [m] + earth_station_alt_m: 1200 + ########################################################################### + # latitude of ES system [m] + earth_station_lat_deg: 13 + ########################################################################### + # difference between longitudes of IMT-NTN station and FSS-ES system [deg] + # (positive if space-station is to the East of earth-station) + earth_station_long_diff_deg: 10 +fss_ss: + ########################################################################### + # satellite center frequency [MHz] + frequency : 43000 + ########################################################################### + # satellite bandwidth [MHz] + bandwidth : 200 + ########################################################################### + # satellite altitude [m] and latitude [deg] + altitude : 35780000 + lat_deg : 0 + ########################################################################### + # Elevation angle [deg] + elevation : 270 + ########################################################################### + # Azimuth angle [deg] + azimuth : 0 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density : -5 + ########################################################################### + # System receive noise temperature [K] + noise_temperature : 950 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity : 0 + ########################################################################### + # Satellite peak receive antenna gain [dBi] + antenna_gain : 46.6 + ########################################################################### + # Antenna pattern of the FSS space station + # Possible values: "ITU-R S.672", "ITU-R S.1528", "FSS_SS", "OMNI" + antenna_pattern : FSS_SS + ########################################################################### + # Paramters for the Antenna ITU-R S.1528 + antenna_s1528: + # Antenna pattern according to ITU-R-S.1528. Possible values are + # "ITU-R-S.1528-Section1.2", "ITU-R-S.1528-LEO", "ITU-R-S.1528-Taylor" + antenna_pattern: "ITU-R-S.1528-LEO" + ########################################################################### + # 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: 21 + # Number of secondary lobes considered in the diagram (coincide with the roots of the Bessel function) + n_side_lobes : 5 + # Radial and transverse sizes of the effective radiating area of the satellite transmit antenna (m). + l_r : 0.4 + l_t : 0.4 + # Roll-off factor + roll_off : 2 + # IMT parameters relevant to the satellite system + # altitude of IMT system (in meters) + # latitude of IMT system (in degrees) + # difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + earth_station_alt_m : 0 + earth_station_lat_deg : 0 + earth_station_long_diff_deg : 0 + season : SUMMER + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model : P619 + ########################################################################### + # The required near-in-side-lobe level (dB) relative to peak gain + # according to ITU-R S.672-4 + antenna_l_s : -20.1 + ########################################################################### + # 3 dB beamwidth angle (3 dB below maximum gain) [degrees] + antenna_3_dB : 0.65 + +fss_es: + ########################################################################### + # type of FSS-ES location: + # FIXED - position must be given + # CELL - random within central cell + # NETWORK - random within whole network + # UNIFORM_DIST - uniform distance from cluster centre, + # between min_dist_to_bs and max_dist_to_bs + location : UNIFORM_DIST + ########################################################################### + # x-y coordinates [m] (only if FIXED location is chosen) + x : 10000 + y : 0 + ########################################################################### + # minimum distance from BSs [m] + min_dist_to_bs : 10 + ########################################################################### + # maximum distance from centre BSs [m] (only if UNIFORM_DIST is chosen) + max_dist_to_bs : 600 + ########################################################################### + # antenna height [m] + height : 6 + ########################################################################### + # Elevation angle [deg], minimum and maximum, values + elevation_min : 48 + elevation_max : 80 + ########################################################################### + # Azimuth angle [deg] + # either a specific angle or string 'RANDOM' + azimuth : RANDOM + ########################################################################### + # center frequency [MHz] + frequency : 43000 + ########################################################################### + # bandwidth [MHz] + bandwidth : 6 + ########################################################################### + # System receive noise temperature [K] + noise_temperature : 950 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity : 0 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density : -68.3 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain : 32 + ########################################################################### + # Antenna pattern of the FSS Earth station + # Possible values: "ITU-R S.1855", "ITU-R S.465", "ITU-R S.580", "OMNI", + # "Modified ITU-R S.465" + antenna_pattern : Modified ITU-R S.465 + ########################################################################### + # Diameter of antenna [m] + diameter : 1.8 + ########################################################################### + # Antenna envelope gain (dBi) - only relevant for "Modified ITU-R S.465" model + antenna_envelope_gain : 0 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + # "P452" + # "TVRO-URBAN" + # "TVRO-SUBURBAN" + # "HDFSS" + channel_model : P452 + ########################################################################### + # Constants + BOLTZMANN_CONSTANT : 1.38064852e-23 + EARTH_RADIUS : 6371000 + ########################################################################### + # P452 parameters + ########################################################################### + # Total air pressure in hPa + atmospheric_pressure : 935 + ########################################################################### + # Temperature in Kelvin + air_temperature : 300 + ########################################################################### + #Sea-level surface refractivity (use the map) + N0 : 352.58 + ########################################################################### + #Average radio-refractive (use the map) + delta_N : 43.127 + ########################################################################### + #percentage p. Float (0 to 100) or RANDOM + percentage_p : 0.2 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dct : 70 + ########################################################################### + #Distance over land from the transmit and receive antennas to the coast (km) + Dcr : 70 + ########################################################################### + ##Effective height of interfering antenna (m) + Hte : 20 + ########################################################################### + #Effective height of interfered-with antenna (m) + Hre : 3 + ########################################################################### + ##Latitude of transmitter + tx_lat : -23.55028 + ########################################################################### + #Latitude of receiver + rx_lat : -23.17889 + ########################################################################### + #Antenna polarization + polarization : horizontal + ########################################################################### + #determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss : TRUE + ########################################################################### + # HDFSS propagation parameters + ########################################################################### + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position : ROOFTOP + ########################################################################### + # Enable shadowing loss + shadow_enabled : TRUE + ########################################################################### + # Enable building entry loss + building_loss_enabled : TRUE + ########################################################################### + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled : FALSE + ########################################################################### + # Enable diffraction loss + diffraction_enabled : FALSE + ########################################################################### + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type : P2109_FIXED + ########################################################################### + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob : 0.75 + ########################################################################### + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value : 35 +fs: + ########################################################################### + # x-y coordinates [m] + x : 1000 + y : 0 + ########################################################################### + # antenna height [m] + height : 15 + ########################################################################### + # Elevation angle [deg] + elevation : -10 + ########################################################################### + # Azimuth angle [deg] + azimuth : 180 + ########################################################################### + # center frequency [MHz] + frequency : 27250 + ########################################################################### + # bandwidth [MHz] + bandwidth : 112 + ########################################################################### + # System receive noise temperature [K] + noise_temperature : 290 + ########################################################################### + # adjacent channel selectivity (dB) + adjacent_ch_selectivity : 20 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density : -68.3 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain : 36.9 + ########################################################################### + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R F.699", "OMNI" + antenna_pattern : OMNI + ########################################################################### + # Diameter of antenna [m] + diameter : 0.3 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "TerrestrialSimple" (FSPL + clutter loss) + channel_model : TerrestrialSimple +haps: + ########################################################################### + # HAPS center frequency [MHz] + frequency : 27251.1 + ########################################################################### + # HAPS bandwidth [MHz] + bandwidth : 200 + ########################################################################### + # HAPS altitude [m] and latitude [deg] + altitude : 20001.1 + lat_deg : 0.1 + ########################################################################### + # Elevation angle [deg] + elevation : 270 + ########################################################################### + # Azimuth angle [deg] + azimuth : 0 + ########################################################################### + # EIRP spectral density [dBW/MHz] + eirp_density : 4.4 + ########################################################################### + # HAPS peak antenna gain [dBi] + antenna_gain : 28.1 + ########################################################################### + # Adjacent channel selectivity [dB] + acs : 30 + ########################################################################### + # Antenna pattern of the HAPS (airbone) station + # Possible values: "ITU-R F.1891", "OMNI" + antenna_pattern : OMNI + # IMT parameters relevant to the HAPS system + # altitude of IMT system (in meters) + # latitude of IMT system (in degrees) + # difference between longitudes of IMT and satellite system + # (positive if space-station is to the East of earth-station) + earth_station_alt_m : 0 + earth_station_lat_deg : 0 + earth_station_long_diff_deg : 0 + season : SUMMER + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 + clutter loss) + # "P619" + channel_model : P619 + ########################################################################### + # Near side-lobe level (dB) relative to the peak gain required by the system + # design, and has a maximum value of โˆ’25 dB + antenna_l_n : -25 + ########################################################################### + # Constants + BOLTZMANN_CONSTANT : 1.38064852e-23 + EARTH_RADIUS : 6371000 +rns: + ########################################################################### + # x-y coordinates [m] + x : 660.1 + y : -370.1 + ########################################################################### + # altitude [m] + altitude : 150.1 + ########################################################################### + # center frequency [MHz] + frequency : 32000.1 + ########################################################################### + # bandwidth [MHz] + bandwidth : 60.1 + ########################################################################### + # System receive noise temperature [K] + noise_temperature : 1154.1 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density : -70.79 + ########################################################################### + # antenna peak gain [dBi] + antenna_gain : 30.1 + ########################################################################### + # Adjacent channel selectivity [dB] + acs : 30.1 + ########################################################################### + # Antenna pattern of the fixed wireless service + # Possible values: "ITU-R M.1466", "OMNI" + antenna_pattern : ITU-R M.1466 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "SatelliteSimple" (FSPL + 4 dB + clutter loss) + # "P619" + channel_model : P619 + ########################################################################### + # Specific parameters for P619 + season : SUMMER + earth_station_alt_m : 0 + earth_station_lat_deg : 0 +ras: + ########################################################################### + # 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.1 + 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.5 + channel_model: P452 + + 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 +eess_passive: + ########################################################################### + # sensor center frequency [MHz] + frequency : 23900.1 + ########################################################################### + # sensor bandwidth [MHz] + bandwidth : 200.1 + ########################################################################### + # Off-nadir pointing angle [deg] + nadir_angle : 46.6 + ########################################################################### + # sensor altitude [m] + altitude : 828000.1 + ########################################################################### + # Antenna pattern of the sensor + # Possible values: "ITU-R RS.1813" + # "ITU-R RS.1861 9a" + # "ITU-R RS.1861 9b" + # "ITU-R RS.1861 9c" + # "OMNI" + antenna_pattern : ITU-R RS.1813 + # Antenna efficiency for pattern described in ITU-R RS.1813 [0-1] + antenna_efficiency : 0.6 + # Antenna diameter for ITU-R RS.1813 [m] + antenna_diameter : 2.2 + ########################################################################### + # receive antenna gain - applicable for 9a, 9b and OMNI [dBi] + antenna_gain : 52.1 + ########################################################################### + # Channel parameters + # channel model, possible values are "FSPL" (free-space path loss), + # "P619" + channel_model : FSPL + # Relevant IMT parameters which apply for ITU-R P.619 + # altitude of IMT system (in meters) + # latitude of IMT system (in degrees) + # season of the year: "SUMMER", "WINTER" + earth_station_alt_m : 20.1 + earth_station_lat_deg : -22.9 + season : WINTER + ########################################################################### + # Constants + BOLTZMANN_CONSTANT : 1.38064852e-23 + EARTH_RADIUS : 6371000 + +single_earth_station: + ########################################################################### + # Sensor center frequency [MHz] + frequency: 8250 + ########################################################################### + # Sensor bandwidth [MHz] + bandwidth: 100 + ########################################################################### + # System receive noise temperature [K] + noise_temperature: 300 + ########################################################################### + # Peak transmit power spectral density (clear sky) [dBW/Hz] + tx_power_density: -65 + # Adjacent channel selectivity [dB] + adjacent_ch_selectivity: 20.0 + geometry: + ########################################################################### + # Antenna height [meters] + height: 6 + ########################################################################### + # Azimuth angle [degrees] + azimuth: + ########################################################################### + # Type of azimuth. May be "UNIFORM_DIST", "FIXED" + type: FIXED + ########################################################################### + # Value of azimuth when type == "FIXED" [deg] + fixed: 0 + ########################################################################### + # Limits of random uniform distribution when type == "UNIFORM_DIST" + uniform_dist: + ########################################################################### + # uniform distribution lower bound [deg] + min: -180 + ########################################################################### + # uniform distribution upper bound [deg] + max: 180 + ########################################################################### + # Elevation angle [degrees] + elevation: + # Type of elevation. May be "UNIFORM_DIST", "FIXED" + type: FIXED + # Value of elevation when type == "FIXED" [deg] + fixed: 60 + # Limits of random uniform distribution when type == "UNIFORM_DIST" + uniform_dist: + # uniform distribution lower bound [deg] + min: 30 + # uniform distribution upper bound [deg] + max: 65 + ########################################################################### + # Station 2d location [meters]: + location: + ########################################################################### + # choose way to set location. May be "CELL", "NETWORK", "UNIFORM_DIST" or "FIXED" + # location.type == CELL means that the ES will be distributed randomly, + # but always in the central cell + # location.type == NETWORK means that the ES will be distributed randomly, + # in a random cell + # location.type == UNIFORM_DIST means that the ES will be distributed randomly, + # in a ring shaped section with delimited outer and inner radius + # location.type == FIXED means that the ES will be always at SAME fixed location + type: CELL + ########################################################################### + # Value of position (x,y) when type == "FIXED" [(m, m)] + fixed: + x: 10 + y: 100 + cell: + ########################################################################### + # Minimum distance to IMT BS when location.type == CELL [m] + min_dist_to_bs: 100 + network: + ########################################################################### + # Minimum distance to IMT BS when location.type == NETWORK [m] + min_dist_to_bs: 150 + uniform_dist: + ########################################################################### + # Ring inner radius [m] + min_dist_to_center: 101 + ########################################################################### + # Ring outer radius [m] + max_dist_to_center: 102 + antenna: + ########################################################################### + # Choose the antenna pattern. Can be one of: + # "OMNI", "ITU-R F.699", "ITU-R S.465", "ITU-R S.580", "MODIFIED ITU-R S.465", "ITU-R S.1855" + pattern: ITU-R S.465 + ########################################################################### + # Earth station peak receive antenna gain [dBi] + gain: 28 + itu_r_f_699: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_465: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_580: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_1855: + ########################################################################### + # Antenna diameter [m] + diameter: 1.1 + itu_r_s_465_modified: + ########################################################################### + # Antenna envelope gain [dBi] + envelope_gain: -4 + ########################################################################### + # Selected channel model + # Possible values are "P619", "P452", "TerrestrialSimple" + channel_model: "P619" + + ########################################################################### + # Parameters for P619 channel model + param_p619: + ########################################################################### + # altitude of the space station [m] + space_station_alt_m: 540000 + ########################################################################### + # altitude of ES system [m] + earth_station_alt_m: 1200 + ########################################################################### + # latitude of ES system [m] + earth_station_lat_deg: 13 + ########################################################################### + # difference between longitudes of IMT-NTN station and FSS-ES system [deg] + # (positive if space-station is to the East of earth-station) + earth_station_long_diff_deg: 10 + + ########################################################################### + # season - season of the year. + # Only actually useful for P619, but we can't put it inside param_p619 without changing more code + season: SUMMER + + ########################################################################### + # Parameters for P452 channel model + param_p452: + ########################################################################### + # Total air pressure [hPa] + atmospheric_pressure: 1 + ########################################################################### + # Temperature [K] + air_temperature: 2 + ########################################################################### + # Sea-level surface refractivity (use the map) + N0: 3 + ########################################################################### + # Average radio-refractive (use the map) + delta_N: 4 + ########################################################################### + # percentage p. Float (0 to 100) or RANDOM + percentage_p: 5 + ########################################################################### + # Distance over land from the transmit and receive antennas to the coast (km) + Dct: 6 + ########################################################################### + # Distance over land from the transmit and receive antennas to the coast (km) + Dcr: 7 + ########################################################################### + # Effective height of interfering antenna (m) + Hte: 8 + ########################################################################### + # Effective height of interfered-with antenna (m) + Hre: 9 + ########################################################################### + # Latitude of transmitter + tx_lat: 10 + ########################################################################### + # Latitude of receiver + rx_lat: 11 + ########################################################################### + # Antenna polarization. Possible values are "horizontal", "vertical" + polarization: horizontal + ########################################################################### + # determine whether clutter loss following ITU-R P.2108 is added (TRUE/FALSE) + clutter_loss: TRUE + + # HDFSS propagation parameters + param_hdfss: + ########################################################################### + # HDFSS position relative to building it is on. Possible values are + # ROOFTOP and BUILDINGSIDE + es_position: BUILDINGSIDE + ########################################################################### + # Enable shadowing loss + shadow_enabled: FALSE + ########################################################################### + # Enable building entry loss + building_loss_enabled: FALSE + ########################################################################### + # Enable interference from IMT stations at the same building as the HDFSS + same_building_enabled: TRUE + ########################################################################### + # Enable diffraction loss + diffraction_enabled: FALSE + ########################################################################### + # Building entry loss type applied between BSs and HDFSS ES. Options are: + # P2109_RANDOM: random probability at P.2109 model, considering elevation + # P2109_FIXED: fixed probability at P.2109 model, considering elevation. + # Probability must be specified in bs_building_entry_loss_prob. + # FIXED_VALUE: fixed value per BS. Value must be specified in + # bs_building_entry_loss_value. + bs_building_entry_loss_type: FIXED_VALUE + ########################################################################### + # Probability of building entry loss not exceeded if + # bs_building_entry_loss_type = P2109_FIXED + bs_building_entry_loss_prob: 0.19 + ########################################################################### + # Value in dB of building entry loss if + # bs_building_entry_loss_type = FIXED_VALUE + bs_building_entry_loss_value: 47 diff --git a/tests/parameters/test_parameters.py b/tests/parameters/test_parameters.py new file mode 100644 index 000000000..04af455e3 --- /dev/null +++ b/tests/parameters/test_parameters.py @@ -0,0 +1,454 @@ +from pathlib import Path +import unittest +from sharc.parameters.parameters import Parameters +import numpy as np + + +class ParametersTest(unittest.TestCase): + """Run Parameter class tests. + """ + + def setUp(self): + self.parameters = Parameters() + param_file = Path(__file__).parent.resolve() / \ + 'parameters_for_testing.yaml' + self.parameters.set_file_name(param_file) + self.parameters.read_params() + + def test_parameters_imt(self): + """Unit test for ParametersIMT + """ + self.assertEqual(self.parameters.imt.topology.type, "INDOOR") + self.assertEqual( + self.parameters.imt.minimum_separation_distance_bs_ue, 1.3) + self.assertEqual(self.parameters.imt.interfered_with, False) + self.assertEqual(self.parameters.imt.frequency, 24360) + self.assertEqual(self.parameters.imt.bandwidth, 200.5) + self.assertEqual(self.parameters.imt.rb_bandwidth, 0.181) + self.assertEqual(self.parameters.imt.spectral_mask, "3GPP E-UTRA") + self.assertEqual(self.parameters.imt.spurious_emissions, -13.1) + self.assertEqual(self.parameters.imt.guard_band_ratio, 0.14) + + self.assertEqual(self.parameters.imt.bs.load_probability, 0.2) + self.assertEqual(self.parameters.imt.bs.conducted_power, 11.1) + self.assertEqual(self.parameters.imt.bs.height, 6.1) + self.assertEqual(self.parameters.imt.bs.noise_figure, 10.1) + self.assertEqual(self.parameters.imt.bs.ohmic_loss, 3.1) + self.assertEqual(self.parameters.imt.uplink.attenuation_factor, 0.4) + self.assertEqual(self.parameters.imt.uplink.sinr_min, -10.0) + self.assertEqual(self.parameters.imt.uplink.sinr_max, 22.0) + self.assertEqual(self.parameters.imt.ue.k, 3) + self.assertEqual(self.parameters.imt.ue.k_m, 1) + self.assertEqual(self.parameters.imt.ue.indoor_percent, 5.0) + self.assertEqual( + self.parameters.imt.ue.distribution_type, + "ANGLE_AND_DISTANCE") + self.assertEqual( + self.parameters.imt.ue.distribution_distance, + "UNIFORM") + self.assertEqual(self.parameters.imt.ue.azimuth_range, (-70, 90)) + self.assertEqual(self.parameters.imt.ue.tx_power_control, True) + self.assertEqual(self.parameters.imt.ue.p_o_pusch, -95.0) + self.assertEqual(self.parameters.imt.ue.alpha, 1.0) + self.assertEqual(self.parameters.imt.ue.p_cmax, 22.0) + self.assertEqual(self.parameters.imt.ue.power_dynamic_range, 63.0) + self.assertEqual(self.parameters.imt.ue.height, 1.5) + self.assertEqual(self.parameters.imt.ue.noise_figure, 10.0) + self.assertEqual(self.parameters.imt.ue.ohmic_loss, 3.0) + self.assertEqual(self.parameters.imt.ue.body_loss, 4.0) + self.assertEqual(self.parameters.imt.downlink.attenuation_factor, 0.6) + self.assertEqual(self.parameters.imt.downlink.sinr_min, -10.0) + self.assertEqual(self.parameters.imt.downlink.sinr_max, 30.0) + self.assertEqual(self.parameters.imt.channel_model, "FSPL") + self.assertEqual(self.parameters.imt.los_adjustment_factor, 18.0) + self.assertEqual(self.parameters.imt.shadowing, False) + + """Test ParametersImtAntenna parameters + """ + self.assertEqual( + self.parameters.imt.adjacent_antenna_model, + "BEAMFORMING") + self.assertEqual(self.parameters.imt.bs.antenna.normalization, False) + self.assertEqual(self.parameters.imt.ue.antenna.normalization, False) + self.assertEqual(self.parameters.imt.bs.antenna.normalization_file, + "antenna/beamforming_normalization/bs_norm.npz") + self.assertEqual(self.parameters.imt.ue.antenna.normalization_file, + "antenna/beamforming_normalization/ue_norm.npz") + self.assertEqual( + self.parameters.imt.bs.antenna.element_pattern, + "F1336") + self.assertEqual( + self.parameters.imt.ue.antenna.element_pattern, + "F1336") + self.assertEqual( + self.parameters.imt.bs.antenna.minimum_array_gain, -200) + self.assertEqual( + self.parameters.imt.ue.antenna.minimum_array_gain, -200) + self.assertEqual(self.parameters.imt.bs.antenna.downtilt, 6) + self.assertEqual(self.parameters.imt.bs.antenna.element_max_g, 5) + self.assertEqual(self.parameters.imt.ue.antenna.element_max_g, 5) + self.assertEqual(self.parameters.imt.bs.antenna.element_phi_3db, 65) + self.assertEqual(self.parameters.imt.ue.antenna.element_phi_3db, 90) + self.assertEqual(self.parameters.imt.bs.antenna.element_theta_3db, 65) + self.assertEqual(self.parameters.imt.ue.antenna.element_theta_3db, 90) + self.assertEqual(self.parameters.imt.bs.antenna.n_rows, 8) + self.assertEqual(self.parameters.imt.ue.antenna.n_rows, 4) + self.assertEqual(self.parameters.imt.bs.antenna.n_columns, 8) + self.assertEqual(self.parameters.imt.ue.antenna.n_columns, 4) + self.assertEqual( + self.parameters.imt.bs.antenna.element_horiz_spacing, 0.5) + self.assertEqual( + self.parameters.imt.ue.antenna.element_horiz_spacing, 0.5) + self.assertEqual( + self.parameters.imt.bs.antenna.element_vert_spacing, 0.5) + self.assertEqual( + self.parameters.imt.ue.antenna.element_vert_spacing, 0.5) + self.assertEqual(self.parameters.imt.bs.antenna.element_am, 30) + self.assertEqual(self.parameters.imt.ue.antenna.element_am, 25) + self.assertEqual(self.parameters.imt.bs.antenna.element_sla_v, 30) + self.assertEqual(self.parameters.imt.ue.antenna.element_sla_v, 25) + self.assertEqual( + self.parameters.imt.bs.antenna.multiplication_factor, 12) + self.assertEqual( + self.parameters.imt.ue.antenna.multiplication_factor, 12) + + """Test ParametersHotspot + """ + self.assertEqual(self.parameters.imt.topology.hotspot.num_hotspots_per_cell, 1) + self.assertEqual(self.parameters.imt.topology.hotspot.max_dist_hotspot_ue, 99.9) + self.assertEqual(self.parameters.imt.topology.hotspot.min_dist_bs_hotspot, 1.2) + self.assertEqual(self.parameters.imt.topology.hotspot.intersite_distance, 321) + self.assertEqual(self.parameters.imt.topology.hotspot.num_clusters, 7) + self.assertEqual(self.parameters.imt.topology.hotspot.wrap_around, True) + + """Test ParametersMacrocell + """ + self.assertEqual(self.parameters.imt.topology.macrocell.intersite_distance, 543) + self.assertEqual(self.parameters.imt.topology.macrocell.num_clusters, 7) + self.assertEqual(self.parameters.imt.topology.macrocell.wrap_around, True) + + """Test ParametersSingleBaseStation + """ + self.assertEqual(self.parameters.imt.topology.single_bs.cell_radius, 543) + self.assertEqual(self.parameters.imt.topology.single_bs.intersite_distance, + self.parameters.imt.topology.single_bs.cell_radius * 3 / 2) + self.assertEqual(self.parameters.imt.topology.single_bs.num_clusters, 2) + + """Test ParametersIndoor + """ + self.assertEqual(self.parameters.imt.topology.indoor.basic_path_loss, "FSPL") + self.assertEqual(self.parameters.imt.topology.indoor.n_rows, 3) + self.assertEqual(self.parameters.imt.topology.indoor.n_colums, 2) + self.assertEqual(self.parameters.imt.topology.indoor.num_imt_buildings, 2) + self.assertEqual(self.parameters.imt.topology.indoor.street_width, 30.1) + self.assertEqual(self.parameters.imt.topology.indoor.intersite_distance, 40.1) + self.assertEqual(self.parameters.imt.topology.indoor.num_cells, 3) + self.assertEqual(self.parameters.imt.topology.indoor.num_floors, 1) + self.assertEqual(self.parameters.imt.topology.indoor.ue_indoor_percent, .95) + self.assertEqual( + self.parameters.imt.topology.indoor.building_class, + "THERMALLY_EFFICIENT") + + self.assertEqual(self.parameters.imt.topology.ntn.bs_height, self.parameters.imt.bs.height) + self.assertEqual(self.parameters.imt.topology.ntn.cell_radius, 123) + self.assertEqual(self.parameters.imt.topology.ntn.intersite_distance, + self.parameters.imt.topology.ntn.cell_radius * np.sqrt(3)) + self.assertEqual(self.parameters.imt.topology.ntn.bs_azimuth, 45) + self.assertEqual(self.parameters.imt.topology.ntn.bs_elevation, 45) + self.assertEqual(self.parameters.imt.topology.ntn.num_sectors, 19) + + def test_parameters_fss_ss(self): + """Test ParametersFssSs + """ + self.assertEqual(self.parameters.fss_ss.frequency, 43000.0) + self.assertEqual(self.parameters.fss_ss.bandwidth, 200.0) + self.assertEqual(self.parameters.fss_ss.tx_power_density, -5.0) + self.assertEqual(self.parameters.fss_ss.altitude, 35780000.0) + self.assertEqual(self.parameters.fss_ss.lat_deg, 0.0) + self.assertEqual(self.parameters.fss_ss.elevation, 270.0) + self.assertEqual(self.parameters.fss_ss.azimuth, 0.0) + self.assertEqual(self.parameters.fss_ss.noise_temperature, 950.0) + self.assertEqual(self.parameters.fss_ss.adjacent_ch_selectivity, 0.0) + self.assertEqual(self.parameters.fss_ss.antenna_gain, 46.6) + self.assertEqual(self.parameters.fss_ss.antenna_pattern, "FSS_SS") + self.assertEqual(self.parameters.fss_ss.earth_station_alt_m, 0.0) + self.assertEqual(self.parameters.fss_ss.earth_station_lat_deg, 0.0) + self.assertEqual( + self.parameters.fss_ss.earth_station_long_diff_deg, 0.0) + self.assertEqual(self.parameters.fss_ss.season, "SUMMER") + self.assertEqual(self.parameters.fss_ss.channel_model, "P619") + self.assertEqual(self.parameters.fss_ss.antenna_l_s, -20.1) + self.assertEqual(self.parameters.fss_ss.antenna_3_dB, 0.65) + + self.assertEqual(self.parameters.fss_ss.antenna_s1528.antenna_pattern, "ITU-R-S.1528-LEO") + self.assertEqual(self.parameters.fss_ss.antenna_s1528.slr, 21) + self.assertEqual(self.parameters.fss_ss.antenna_s1528.antenna_l_s, -20.1) + self.assertEqual(self.parameters.fss_ss.antenna_s1528.n_side_lobes, 5) + self.assertEqual(self.parameters.fss_ss.antenna_s1528.l_r, .4) + self.assertEqual(self.parameters.fss_ss.antenna_s1528.l_t, .4) + self.assertEqual(self.parameters.fss_ss.antenna_s1528.roll_off, 2) + + def test_parameters_fss_es(self): + """Test ParametersFssEs + """ + self.assertEqual(self.parameters.fss_es.location, "UNIFORM_DIST") + self.assertEqual(self.parameters.fss_es.x, 10000) + self.assertEqual(self.parameters.fss_es.y, 0) + self.assertEqual(self.parameters.fss_es.min_dist_to_bs, 10) + self.assertEqual(self.parameters.fss_es.max_dist_to_bs, 600) + self.assertEqual(self.parameters.fss_es.height, 6) + self.assertEqual(self.parameters.fss_es.elevation_min, 48) + self.assertEqual(self.parameters.fss_es.elevation_max, 80) + self.assertEqual(self.parameters.fss_es.azimuth, "RANDOM") + self.assertEqual(self.parameters.fss_es.frequency, 43000) + self.assertEqual(self.parameters.fss_es.bandwidth, 6) + self.assertEqual(self.parameters.fss_es.adjacent_ch_selectivity, 0) + self.assertEqual(self.parameters.fss_es.tx_power_density, -68.3) + self.assertEqual(self.parameters.fss_es.noise_temperature, 950) + self.assertEqual(self.parameters.fss_es.antenna_gain, 32) + self.assertEqual( + self.parameters.fss_es.antenna_pattern, + "Modified ITU-R S.465") + self.assertEqual(self.parameters.fss_es.antenna_envelope_gain, 0) + self.assertEqual(self.parameters.fss_es.diameter, 1.8) + self.assertEqual(self.parameters.fss_es.channel_model, "P452") + self.assertEqual(self.parameters.fss_es.atmospheric_pressure, 935) + self.assertEqual(self.parameters.fss_es.air_temperature, 300) + self.assertEqual(self.parameters.fss_es.N0, 352.58) + self.assertEqual(self.parameters.fss_es.delta_N, 43.127) + self.assertEqual(self.parameters.fss_es.percentage_p, 0.2) + self.assertEqual(self.parameters.fss_es.Dct, 70) + self.assertEqual(self.parameters.fss_es.Dcr, 70) + self.assertEqual(self.parameters.fss_es.Hte, 20) + self.assertEqual(self.parameters.fss_es.Hre, 3) + self.assertEqual(self.parameters.fss_es.tx_lat, -23.55028) + self.assertEqual(self.parameters.fss_es.rx_lat, -23.17889) + self.assertEqual(self.parameters.fss_es.polarization, "horizontal") + self.assertEqual(self.parameters.fss_es.clutter_loss, True) + self.assertEqual(self.parameters.fss_es.es_position, "ROOFTOP") + self.assertEqual(self.parameters.fss_es.shadow_enabled, True) + self.assertEqual(self.parameters.fss_es.building_loss_enabled, True) + self.assertEqual(self.parameters.fss_es.same_building_enabled, False) + self.assertEqual(self.parameters.fss_es.diffraction_enabled, False) + self.assertEqual( + self.parameters.fss_es.bs_building_entry_loss_type, + "P2109_FIXED") + self.assertEqual( + self.parameters.fss_es.bs_building_entry_loss_prob, 0.75) + self.assertEqual( + self.parameters.fss_es.bs_building_entry_loss_value, 35.0) + + def test_parameters_fs(self): + """Test ParametersFs + """ + self.assertEqual(self.parameters.fs.x, 1000.0) + self.assertEqual(self.parameters.fs.y, 0.0) + self.assertEqual(self.parameters.fs.height, 15.0) + self.assertEqual(self.parameters.fs.elevation, -10.0) + self.assertEqual(self.parameters.fs.azimuth, 180.0) + self.assertEqual(self.parameters.fs.frequency, 27250.0) + self.assertEqual(self.parameters.fs.bandwidth, 112.0) + self.assertEqual(self.parameters.fs.noise_temperature, 290.0) + self.assertEqual(self.parameters.fs.adjacent_ch_selectivity, 20.0) + self.assertEqual(self.parameters.fs.tx_power_density, -68.3) + self.assertEqual(self.parameters.fs.antenna_gain, 36.9) + self.assertEqual(self.parameters.fs.antenna_pattern, "OMNI") + self.assertEqual(self.parameters.fs.diameter, 0.3) + self.assertEqual(self.parameters.fs.channel_model, "TerrestrialSimple") + + def test_parameters_haps(self): + """Test ParametersHaps + """ + self.assertEqual(self.parameters.haps.frequency, 27251.1) + self.assertEqual(self.parameters.haps.bandwidth, 200.0) + self.assertEqual(self.parameters.haps.antenna_gain, 28.1) + self.assertEqual(self.parameters.haps.eirp_density, 4.4) + self.assertEqual(self.parameters.haps.tx_power_density, + self.parameters.haps.eirp_density - self.parameters.haps.antenna_gain - 60) + self.assertEqual(self.parameters.haps.altitude, 20001.1) + self.assertEqual(self.parameters.haps.lat_deg, 0.1) + self.assertEqual(self.parameters.haps.elevation, 270.0) + self.assertEqual(self.parameters.haps.azimuth, 0) + self.assertEqual(self.parameters.haps.antenna_pattern, "OMNI") + self.assertEqual(self.parameters.haps.earth_station_alt_m, 0.0) + self.assertEqual(self.parameters.haps.earth_station_lat_deg, 0.0) + self.assertEqual(self.parameters.haps.earth_station_long_diff_deg, 0.0) + self.assertEqual(self.parameters.haps.season, "SUMMER") + self.assertEqual(self.parameters.haps.acs, 30.0) + self.assertEqual(self.parameters.haps.channel_model, "P619") + self.assertEqual(self.parameters.haps.antenna_l_n, -25) + + def test_paramters_rns(self): + """Test ParametersRns + """ + self.assertEqual(self.parameters.rns.x, 660.1) + self.assertEqual(self.parameters.rns.y, -370.1) + self.assertEqual(self.parameters.rns.altitude, 150.1) + self.assertEqual(self.parameters.rns.frequency, 32000.1) + self.assertEqual(self.parameters.rns.bandwidth, 60.1) + self.assertEqual(self.parameters.rns.noise_temperature, 1154.1) + self.assertEqual(self.parameters.rns.tx_power_density, -70.79) + self.assertEqual(self.parameters.rns.antenna_gain, 30.1) + self.assertEqual(self.parameters.rns.antenna_pattern, "ITU-R M.1466") + self.assertEqual(self.parameters.rns.channel_model, "P619") + self.assertEqual(self.parameters.rns.season, "SUMMER") + self.assertEqual(self.parameters.rns.earth_station_alt_m, 0.0) + self.assertEqual(self.parameters.rns.earth_station_lat_deg, 0.0) + self.assertEqual(self.parameters.rns.acs, 30.1) + + def test_parametes_ras(self): + """Test ParametersRas + """ + self.assertEqual(self.parameters.ras.frequency, 2695) + self.assertEqual(self.parameters.ras.bandwidth, 10) + self.assertEqual(self.parameters.ras.noise_temperature, 90) + self.assertEqual(self.parameters.ras.adjacent_ch_selectivity, 20.1) + + self.assertEqual(self.parameters.ras.geometry.height, 15) + self.assertEqual(self.parameters.ras.geometry.azimuth.type, "FIXED") + self.assertEqual(self.parameters.ras.geometry.azimuth.fixed, -90) + self.assertEqual(self.parameters.ras.geometry.elevation.type, "FIXED") + self.assertEqual(self.parameters.ras.geometry.elevation.fixed, 45) + self.assertEqual(self.parameters.ras.antenna.pattern, "OMNI") + self.assertEqual(self.parameters.ras.antenna.gain, 0.5) + self.assertEqual(self.parameters.ras.channel_model, "P452") + self.assertEqual(self.parameters.ras.polarization_loss, 0.0) + + def test_parameters_single_earth_station(self): + """Test ParametersSingleEarthStation + """ + self.assertEqual(self.parameters.single_earth_station.frequency, 8250) + self.assertEqual(self.parameters.single_earth_station.bandwidth, 100) + self.assertEqual( + self.parameters.single_earth_station.adjacent_ch_selectivity, 20.0) + self.assertEqual( + self.parameters.single_earth_station.tx_power_density, -65.0) + self.assertEqual( + self.parameters.single_earth_station.noise_temperature, 300) + self.assertEqual( + self.parameters.single_earth_station.geometry.height, 6) + self.assertEqual( + self.parameters.single_earth_station.geometry.azimuth.type, + "FIXED") + self.assertEqual( + self.parameters.single_earth_station.geometry.azimuth.fixed, 0) + self.assertEqual( + self.parameters.single_earth_station.geometry.azimuth.uniform_dist.min, -180) + self.assertEqual( + self.parameters.single_earth_station.geometry.azimuth.uniform_dist.max, 180) + self.assertEqual( + self.parameters.single_earth_station.geometry.elevation.type, + "FIXED") + self.assertEqual( + self.parameters.single_earth_station.geometry.elevation.fixed, 60) + self.assertEqual( + self.parameters.single_earth_station.geometry.elevation.uniform_dist.min, 30) + self.assertEqual( + self.parameters.single_earth_station.geometry.elevation.uniform_dist.max, 65) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.type, + "CELL") + self.assertEqual( + self.parameters.single_earth_station.geometry.location.fixed.x, 10) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.fixed.y, 100) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.uniform_dist.min_dist_to_center, + 101) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.uniform_dist.max_dist_to_center, + 102) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.cell.min_dist_to_bs, + 100) + self.assertEqual( + self.parameters.single_earth_station.geometry.location.network.min_dist_to_bs, + 150) + self.assertEqual(self.parameters.single_earth_station.antenna.gain, 28) + self.assertEqual( + self.parameters.single_earth_station.antenna.itu_r_f_699.diameter, 1.1) + self.assertEqual( + self.parameters.single_earth_station.antenna.itu_r_f_699.frequency, + self.parameters.single_earth_station.frequency) + self.assertEqual( + self.parameters.single_earth_station.antenna.itu_r_f_699.antenna_gain, + self.parameters.single_earth_station.antenna.gain) + + self.assertEqual( + self.parameters.single_earth_station.param_p619.earth_station_alt_m, 1200) + self.assertEqual( + self.parameters.single_earth_station.param_p619.space_station_alt_m, + 540000) + self.assertEqual( + self.parameters.single_earth_station.param_p619.earth_station_lat_deg, 13) + self.assertEqual( + self.parameters.single_earth_station.param_p619.earth_station_long_diff_deg, + 10) + + self.assertEqual( + self.parameters.single_earth_station.param_p452.atmospheric_pressure, 1) + self.assertEqual( + self.parameters.single_earth_station.param_p452.air_temperature, 2) + self.assertEqual(self.parameters.single_earth_station.param_p452.N0, 3) + self.assertEqual( + self.parameters.single_earth_station.param_p452.delta_N, 4) + self.assertEqual( + self.parameters.single_earth_station.param_p452.percentage_p, 5) + self.assertEqual( + self.parameters.single_earth_station.param_p452.Dct, 6) + self.assertEqual( + self.parameters.single_earth_station.param_p452.Dcr, 7) + self.assertEqual( + self.parameters.single_earth_station.param_p452.Hte, 8) + self.assertEqual( + self.parameters.single_earth_station.param_p452.Hre, 9) + self.assertEqual( + self.parameters.single_earth_station.param_p452.tx_lat, 10) + self.assertEqual( + self.parameters.single_earth_station.param_p452.rx_lat, 11) + self.assertEqual( + self.parameters.single_earth_station.param_p452.polarization, + "horizontal") + self.assertEqual( + self.parameters.single_earth_station.param_p452.clutter_loss, True) + + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.es_position, + "BUILDINGSIDE") + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.shadow_enabled, + False) + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.building_loss_enabled, + False) + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.same_building_enabled, True) + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.diffraction_enabled, + False) + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_type, + "FIXED_VALUE") + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_prob, + 0.19) + self.assertEqual( + self.parameters.single_earth_station.param_hdfss.bs_building_entry_loss_value, + 47) + + self.parameters.single_earth_station.geometry.azimuth.uniform_dist.max = None + # this should still not throw, since azimuth is using fixed type + self.parameters.single_earth_station.validate() + + # now it should throw: + with self.assertRaises(ValueError) as err_context: + self.parameters.single_earth_station.geometry.azimuth.type = "UNIFORM_DIST" + self.parameters.single_earth_station.validate() + + self.assertTrue( + 'azimuth.uniform_dist.max' in str( + err_context.exception)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_adjacent_channel.py b/tests/test_adjacent_channel.py index 2b785f714..40d062e18 100644 --- a/tests/test_adjacent_channel.py +++ b/tests/test_adjacent_channel.py @@ -16,6 +16,9 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology +from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS + class SimulationAdjacentTest(unittest.TestCase): @@ -28,88 +31,92 @@ def setUp(self): self.param.general.overwrite_output = True self.param.general.seed = 101 - self.param.imt.topology = "SINGLE_BS" - self.param.imt.wrap_around = False - self.param.imt.num_clusters = 2 - self.param.imt.intersite_distance = 150 + self.param.imt.topology = ParametersImtTopology( + type="SINGLE_BS", + single_bs=ParametersSingleBS( + num_clusters=2, + intersite_distance=150, + cell_radius=2 * 150 / 3 + ) + ) self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False - self.param.imt.frequency = 10000 + self.param.imt.frequency = 10000.0 self.param.imt.bandwidth = 100 self.param.imt.spectral_mask = "IMT-2020" self.param.imt.spurious_emissions = -13 self.param.imt.rb_bandwidth = 0.180 self.param.imt.guard_band_ratio = 0.1 self.param.imt.ho_margin = 3 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 10 - self.param.imt.bs_height = 6 - self.param.imt.bs_acs = 30 - self.param.imt.bs_noise_figure = 7 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 2 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 0 - self.param.imt.ue_distribution_distance = "RAYLEIGH" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 0.8 - self.param.imt.ue_p_cmax = 20 - self.param.imt.ue_conducted_power = 10 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_aclr = 20 - self.param.imt.ue_acs = 25 - self.param.imt.ue_noise_figure = 9 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 10 + self.param.imt.bs.height = 6 + self.param.imt.bs.acs = 30 + self.param.imt.bs.noise_figure = 7 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 2 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 0 + self.param.imt.ue.distribution_distance = "RAYLEIGH" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 0.8 + self.param.imt.ue.p_cmax = 20 + self.param.imt.ue.conducted_power = 10 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.aclr = 20 + self.param.imt.ue.acs = 25 + self.param.imt.ue.noise_figure = 9 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" - self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL) + # probability of line-of-sight (not for FSPL) + self.param.imt.line_of_sight_prob = 0.75 self.param.imt.shadowing = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.ue_normalization = False - - self.param.antenna_imt.bs_normalization_file = None - self.param.antenna_imt.bs_element_pattern = "M2101" - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_element_max_g = 10 - self.param.antenna_imt.bs_element_phi_3db = 80 - self.param.antenna_imt.bs_element_theta_3db = 80 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 16 - self.param.antenna_imt.bs_n_columns = 16 - self.param.antenna_imt.bs_element_horiz_spacing = 1 - self.param.antenna_imt.bs_element_vert_spacing = 1 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 10 - - self.param.antenna_imt.ue_element_pattern = "M2101" - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_normalization_file = None - self.param.antenna_imt.ue_element_max_g = 5 - self.param.antenna_imt.ue_element_phi_3db = 65 - self.param.antenna_imt.ue_element_theta_3db = 65 - self.param.antenna_imt.ue_element_am = 30 - self.param.antenna_imt.ue_element_sla_v = 30 - self.param.antenna_imt.ue_n_rows = 2 - self.param.antenna_imt.ue_n_columns = 1 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 + + self.param.imt.bs.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.ue.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.bs.antenna.normalization = False + self.param.imt.ue.antenna.normalization = False + + self.param.imt.bs.antenna.normalization_file = None + self.param.imt.bs.antenna.element_pattern = "M2101" + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.element_max_g = 10 + self.param.imt.bs.antenna.element_phi_3db = 80 + self.param.imt.bs.antenna.element_theta_3db = 80 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 16 + self.param.imt.bs.antenna.n_columns = 16 + self.param.imt.bs.antenna.element_horiz_spacing = 1 + self.param.imt.bs.antenna.element_vert_spacing = 1 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 10 + + self.param.imt.ue.antenna.element_pattern = "M2101" + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.normalization_file = None + self.param.imt.ue.antenna.element_max_g = 5 + self.param.imt.ue.antenna.element_phi_3db = 65 + self.param.imt.ue.antenna.element_theta_3db = 65 + self.param.imt.ue.antenna.element_am = 30 + self.param.imt.ue.antenna.element_sla_v = 30 + self.param.imt.ue.antenna.n_rows = 2 + self.param.imt.ue.antenna.n_columns = 1 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 self.param.fss_ss.frequency = 5000 self.param.fss_ss.bandwidth = 100 @@ -123,7 +130,7 @@ def setUp(self): self.param.fss_ss.antenna_pattern = "OMNI" self.param.fss_ss.imt_altitude = 1000 self.param.fss_ss.imt_lat_deg = -23.5629739 - self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75) + self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75) self.param.fss_ss.channel_model = "FSPL" self.param.fss_ss.line_of_sight_prob = 0.01 self.param.fss_ss.surf_water_vapour_density = 7.5 @@ -131,8 +138,6 @@ def setUp(self): self.param.fss_ss.time_ratio = 0.5 self.param.fss_ss.antenna_l_s = -20 self.param.fss_ss.acs = 10 - self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_ss.EARTH_RADIUS = 6371000 def test_simulation_2bs_4ue_downlink(self): self.param.general.imt_link = "DOWNLINK" @@ -148,85 +153,94 @@ def test_simulation_2bs_4ue_downlink(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) # test connection method self.simulation.connect_ue_to_bs() - self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]}) + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} # We do not test the selection method here because in this specific # scenario we do not want to change the order of the UE's self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) npt.assert_allclose(self.simulation.coupling_loss_imt, - np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23], - [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]]), + np.array([[88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23], + [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23]]), atol=1e-2) # test scheduler and bandwidth allocation self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + npt.assert_allclose( + self.simulation.ue.bandwidth, + bandwidth_per_ue * np.ones(4), + atol=1e-2) # there is no power control, so BS's will transmit at maximum power self.simulation.power_control() - tx_power = 10 - 10*math.log10(2) - npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2) - npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2) - - npt.assert_equal(self.simulation.bs.spectral_mask.mask_dbm,np.array([-50, -20, -10, -10, -10, -20, -50])) + tx_power = 10 - 10 * math.log10(2) + npt.assert_allclose(self.simulation.bs.tx_power[0], np.array( + [tx_power, tx_power]), atol=1e-2) + npt.assert_allclose(self.simulation.bs.tx_power[1], np.array( + [tx_power, tx_power]), atol=1e-2) # create system - self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss) - self.simulation.system.x = np.array([0.01]) # avoids zero-division + self.simulation.system = StationFactory.generate_fss_space_station( + self.param.fss_ss) + self.simulation.system.x = np.array([0.01]) # avoids zero-division self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.fss_ss.altitude]) - # test the method that calculates interference from IMT UE to FSS space station + # test the method that calculates interference from IMT UE to FSS space + # station self.simulation.calculate_external_interference() # check coupling loss - coupling_loss_imt_system_adj = np.array([209.52-51-1, 209.52-51-1, 209.52-51-2, 209.52-51-2]) + coupling_loss_imt_system_adj = np.array( + [209.52 - 51 - 1, 209.52 - 51 - 1, 209.52 - 51 - 2, 209.52 - 51 - 2]) npt.assert_allclose(self.simulation.coupling_loss_imt_system_adjacent, coupling_loss_imt_system_adj, atol=1e-2) # check interference generated by BS to FSS space station - interf_pow = np.power(10, 0.1*(-50))*100 - rx_interf_bs1 = 10*math.log10(interf_pow)\ - - coupling_loss_imt_system_adj[0] - rx_interf_bs2 = 10*math.log10(interf_pow)\ - - coupling_loss_imt_system_adj[2] - rx_interference = 10*math.log10(math.pow(10,0.1*rx_interf_bs1) + \ - math.pow(10,0.1*rx_interf_bs2)) + 3 + interf_pow = np.power(10, 0.1 * (self.param.imt.bs.conducted_power)) + rx_interf_bs1 = 10 * math.log10(interf_pow)\ + - coupling_loss_imt_system_adj[0] + rx_interf_bs2 = 10 * math.log10(interf_pow)\ + - coupling_loss_imt_system_adj[2] + rx_interference = 10 * math.log10(math.pow(10, 0.1 * rx_interf_bs1) + + math.pow(10, 0.1 * rx_interf_bs2)) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) - def test_simulation_2bs_4ue_uplink(self): self.param.general.imt_link = "UPLINK" @@ -241,82 +255,93 @@ def test_simulation_2bs_4ue_uplink(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) # test connection method self.simulation.connect_ue_to_bs() - self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]}) + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} # We do not test the selection method here because in this specific # scenario we do not want to change the order of the UE's self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) - coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23], - [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]]) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) + coupling_loss_imt = np.array([[88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23], + [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23]]) npt.assert_allclose(self.simulation.coupling_loss_imt, coupling_loss_imt, atol=1e-2) # test scheduler and bandwidth allocation self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + npt.assert_allclose( + self.simulation.ue.bandwidth, + bandwidth_per_ue * np.ones(4), + atol=1e-2) # there is no power control, so UE's will transmit at maximum power self.simulation.power_control() - npt.assert_equal(self.simulation.ue_power_diff,np.zeros(4)) + npt.assert_equal(self.simulation.ue_power_diff, np.zeros(4)) tx_power = 20 - npt.assert_allclose(self.simulation.ue.tx_power, tx_power*np.ones(4)) + npt.assert_allclose(self.simulation.ue.tx_power, tx_power * np.ones(4)) - npt.assert_equal(self.simulation.ue.spectral_mask.mask_dbm,np.array([-13, -13, -5, -20, -5, -13, -13])) + npt.assert_equal(self.simulation.ue.spectral_mask.mask_dbm, + np.array([-13, -13, -5, -20, -5, -13, -13])) # create system - self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss) + self.simulation.system = StationFactory.generate_fss_space_station( + self.param.fss_ss) self.simulation.system.x = np.array([0]) self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.fss_ss.altitude]) - # test the method that calculates interference from IMT UE to FSS space station + # test the method that calculates interference from IMT UE to FSS space + # station self.simulation.calculate_external_interference() # check coupling loss - coupling_loss_imt_system_adj = np.array([213.52-51-10, 213.52-51-11, 213.52-51-22, 213.52-51-23]) + coupling_loss_imt_system_adj = np.array( + [213.52 - 51 - 10, 213.52 - 51 - 11, 213.52 - 51 - 22, 213.52 - 51 - 23]) npt.assert_allclose(self.simulation.coupling_loss_imt_system_adjacent, coupling_loss_imt_system_adj, atol=1e-2) # check interference generated by UE to FSS space station - interf_pow = np.power(10, 0.1*(-13))*100 - interference = 10*math.log10(interf_pow) \ - - coupling_loss_imt_system_adj - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + 3 + interf_pow = np.power(10, 0.1 * (-13)) * 100 + interference = 10 * math.log10(interf_pow) \ + - coupling_loss_imt_system_adj + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) + 3 self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_beamforming_imt.py b/tests/test_antenna_beamforming_imt.py index c520e8a76..cfcdcb83b 100644 --- a/tests/test_antenna_beamforming_imt.py +++ b/tests/test_antenna_beamforming_imt.py @@ -10,107 +10,110 @@ import numpy.testing as npt from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.support.named_tuples import AntennaPar from sharc.support.enumerations import StationType + class AntennaBeamformingImtTest(unittest.TestCase): def setUp(self): - #Array parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_normalization = False - self.param.bs_normalization_file = None - self.param.bs_element_pattern = "M2101" - self.param.bs_minimum_array_gain = -200 - self.param.bs_downtilt = 0 - - self.param.bs_element_max_g = 5 - self.param.bs_element_phi_3db = 80 - self.param.bs_element_theta_3db = 60 - self.param.bs_element_am = 30 - self.param.bs_element_sla_v = 30 - self.param.bs_n_rows = 16 - self.param.bs_n_columns = 16 - self.param.bs_element_horiz_spacing = 1 - self.param.bs_element_vert_spacing = 1 - self.param.bs_multiplication_factor = 12 - - self.param.ue_element_pattern = "M2101" - self.param.ue_normalization = False - self.param.ue_normalization_file = None - self.param.ue_minimum_array_gain = -200 - - self.param.ue_element_max_g = 10 - self.param.ue_element_phi_3db = 75 - self.param.ue_element_theta_3db = 65 - self.param.ue_element_am = 25 - self.param.ue_element_sla_v = 35 - self.param.ue_n_rows = 2 - self.param.ue_n_columns = 2 - self.param.ue_element_horiz_spacing = 0.5 - self.param.ue_element_vert_spacing = 0.5 - self.param.ue_multiplication_factor = 12 + # Array parameters + self.bs_param = ParametersAntennaImt() + self.ue_param = ParametersAntennaImt() + + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.normalization = False + self.bs_param.normalization_file = None + self.bs_param.element_pattern = "M2101" + self.bs_param.minimum_array_gain = -200 + self.bs_param.downtilt = 0 + + self.bs_param.element_max_g = 5 + self.bs_param.element_phi_3db = 80 + self.bs_param.element_theta_3db = 60 + self.bs_param.element_am = 30 + self.bs_param.element_sla_v = 30 + self.bs_param.n_rows = 16 + self.bs_param.n_columns = 16 + self.bs_param.element_horiz_spacing = 1 + self.bs_param.element_vert_spacing = 1 + self.bs_param.multiplication_factor = 12 + + self.ue_param.element_pattern = "M2101" + self.ue_param.normalization = False + self.ue_param.normalization_file = None + self.ue_param.minimum_array_gain = -200 + + self.ue_param.element_max_g = 10 + self.ue_param.element_phi_3db = 75 + self.ue_param.element_theta_3db = 65 + self.ue_param.element_am = 25 + self.ue_param.element_sla_v = 35 + self.ue_param.n_rows = 2 + self.ue_param.n_columns = 2 + self.ue_param.element_horiz_spacing = 0.5 + self.ue_param.element_vert_spacing = 0.5 + self.ue_param.multiplication_factor = 12 # Create antenna objects - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.antenna1 = AntennaBeamformingImt(par,300,-10) - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.antenna2 = AntennaBeamformingImt(par,-33.21,-5.31) + par = self.bs_param.get_antenna_parameters() + self.antenna1 = AntennaBeamformingImt(par, 300, -10) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaBeamformingImt(par, -33.21, -5.31) def test_azimuth(self): - self.assertEqual(self.antenna1.azimuth,300) - self.assertEqual(self.antenna2.azimuth,-33.21) + self.assertEqual(self.antenna1.azimuth, 300) + self.assertEqual(self.antenna2.azimuth, -33.21) def test_elevation(self): - self.assertEqual(self.antenna1.elevation,-10) - self.assertEqual(self.antenna2.elevation,-5.31) + self.assertEqual(self.antenna1.elevation, -10) + self.assertEqual(self.antenna2.elevation, -5.31) def test_g_max(self): - self.assertEqual(self.antenna1.element.g_max,5) - self.assertEqual(self.antenna2.element.g_max,10) + self.assertEqual(self.antenna1.element.g_max, 5) + self.assertEqual(self.antenna2.element.g_max, 10) def test_phi_3db(self): - self.assertEqual(self.antenna1.element.phi_3db,80) - self.assertEqual(self.antenna2.element.phi_3db,75) + self.assertEqual(self.antenna1.element.phi_3db, 80) + self.assertEqual(self.antenna2.element.phi_3db, 75) def test_theta_3db(self): - self.assertEqual(self.antenna1.element.theta_3db,60) - self.assertEqual(self.antenna2.element.theta_3db,65) + self.assertEqual(self.antenna1.element.theta_3db, 60) + self.assertEqual(self.antenna2.element.theta_3db, 65) def test_am(self): - self.assertEqual(self.antenna1.element.am,30) - self.assertEqual(self.antenna2.element.am,25) + self.assertEqual(self.antenna1.element.am, 30) + self.assertEqual(self.antenna2.element.am, 25) def test_sla_v(self): - self.assertEqual(self.antenna1.element.sla_v,30) - self.assertEqual(self.antenna2.element.sla_v,35) + self.assertEqual(self.antenna1.element.sla_v, 30) + self.assertEqual(self.antenna2.element.sla_v, 35) def test_n_rows(self): - self.assertEqual(self.antenna1.n_rows,16) - self.assertEqual(self.antenna2.n_rows,2) + self.assertEqual(self.antenna1.n_rows, 16) + self.assertEqual(self.antenna2.n_rows, 2) def test_n_cols(self): - self.assertEqual(self.antenna1.n_cols,16) - self.assertEqual(self.antenna2.n_cols,2) + self.assertEqual(self.antenna1.n_cols, 16) + self.assertEqual(self.antenna2.n_cols, 2) def test_dh(self): - self.assertEqual(self.antenna1.dh,1) - self.assertEqual(self.antenna2.dh,0.5) + self.assertEqual(self.antenna1.dh, 1) + self.assertEqual(self.antenna2.dh, 0.5) def test_dv(self): - self.assertEqual(self.antenna1.dv,1) - self.assertEqual(self.antenna2.dv,0.5) + self.assertEqual(self.antenna1.dv, 1) + self.assertEqual(self.antenna2.dv, 0.5) def test_beams_list(self): - self.assertEqual(len(self.antenna1.beams_list),0) - self.assertEqual(len(self.antenna2.beams_list),0) + self.assertEqual(len(self.antenna1.beams_list), 0) + self.assertEqual(len(self.antenna2.beams_list), 0) def test_w_vec_list(self): - self.assertEqual(len(self.antenna1.w_vec_list),0) - self.assertEqual(len(self.antenna2.w_vec_list),0) + self.assertEqual(len(self.antenna1.w_vec_list), 0) + self.assertEqual(len(self.antenna2.w_vec_list), 0) def test_super_position_vector(self): # Error margin @@ -120,43 +123,43 @@ def test_super_position_vector(self): phi = 0 theta = 0 v_vec = self.antenna2._super_position_vector(phi, theta) - expected_v_vec = np.array([[1.0, 1.0],[-1.0, -1.0]]) - self.assertTrue(np.allclose(np.real(v_vec),\ - np.real(expected_v_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(v_vec),\ - np.imag(expected_v_vec),rtol = eps)) + expected_v_vec = np.array([[1.0, 1.0], [-1.0, -1.0]]) + self.assertTrue(np.allclose(np.real(v_vec), + np.real(expected_v_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(v_vec), + np.imag(expected_v_vec), rtol=eps)) # Test 2 phi = 90 theta = 90 v_vec = self.antenna2._super_position_vector(phi, theta) - expected_v_vec = np.array([[1.0, -1.0],[1.0, -1.0]]) - self.assertTrue(np.allclose(np.real(v_vec),\ - np.real(expected_v_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(v_vec),\ - np.imag(expected_v_vec),rtol = eps)) + expected_v_vec = np.array([[1.0, -1.0], [1.0, -1.0]]) + self.assertTrue(np.allclose(np.real(v_vec), + np.real(expected_v_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(v_vec), + np.imag(expected_v_vec), rtol=eps)) # Test 3 phi = 45 theta = 45 v_vec = self.antenna2._super_position_vector(phi, theta) - expected_v_vec = np.array([[1.0 + 0.0j, 0.0 + 1.0j],\ - [-0.6056998+0.7956932j, -0.7956932-0.6056998j]]) - self.assertTrue(np.allclose(np.real(v_vec),\ - np.real(expected_v_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(v_vec),\ - np.imag(expected_v_vec),rtol = eps)) + expected_v_vec = np.array([[1.0 + 0.0j, 0.0 + 1.0j], + [-0.6056998 + 0.7956932j, -0.7956932 - 0.6056998j]]) + self.assertTrue(np.allclose(np.real(v_vec), + np.real(expected_v_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(v_vec), + np.imag(expected_v_vec), rtol=eps)) # Test 4 phi = 60 theta = 90 v_vec = self.antenna2._super_position_vector(phi, theta) - expected_v_vec = np.array([[1.0 + 0.0j, -0.912724 + 0.408576j],\ - [1.0 + 0.0j, -0.912724 + 0.408576j]]) - self.assertTrue(np.allclose(np.real(v_vec),\ - np.real(expected_v_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(v_vec),\ - np.imag(expected_v_vec),rtol = eps)) + expected_v_vec = np.array([[1.0 + 0.0j, -0.912724 + 0.408576j], + [1.0 + 0.0j, -0.912724 + 0.408576j]]) + self.assertTrue(np.allclose(np.real(v_vec), + np.real(expected_v_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(v_vec), + np.imag(expected_v_vec), rtol=eps)) def test_weight_vector(self): # Error margin @@ -166,111 +169,110 @@ def test_weight_vector(self): phi_scan = 0 theta_tilt = 0 w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt) - expected_w_vec = np.array([[0.5, 0.5],[0.5, 0.5]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5, 0.5], [0.5, 0.5]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Test 2 phi_scan = 90 theta_tilt = 90 w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt) - expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Test 3 phi_scan = 45 theta_tilt = 45 w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt) - expected_w_vec = np.array([[0.5 + 0.0j, 0.0 - 0.5j],\ - [-0.3028499+0.3978466j, 0.3978466+0.3028499j]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5 + 0.0j, 0.0 - 0.5j], + [-0.3028499 + 0.3978466j, 0.3978466 + 0.3028499j]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Test 4 phi_scan = 0 theta_tilt = 90 w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt) - expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Test 5 phi_scan = 45 theta_tilt = 30 w_vec = self.antenna2._weight_vector(phi_scan, theta_tilt) - expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j],\ - [0.0 + 0.5j, 0.469165 - 0.172870j]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) - + expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j], + [0.0 + 0.5j, 0.469165 - 0.172870j]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) def test_add_beam(self): # Error margin and antenna object eps = 1e-5 - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.antenna2 = AntennaBeamformingImt(par,0,0) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaBeamformingImt(par, 0, 0) # Add first beam phi_scan = 45 theta_tilt = 120 - self.antenna2.add_beam(phi_scan,theta_tilt) + self.antenna2.add_beam(phi_scan, theta_tilt) - self.assertEqual(len(self.antenna2.beams_list),1) - self.assertEqual(len(self.antenna2.w_vec_list),1) + self.assertEqual(len(self.antenna2.beams_list), 1) + self.assertEqual(len(self.antenna2.w_vec_list), 1) # Add second beam phi_scan = 90 theta_tilt = 180.0 - self.antenna2.add_beam(phi_scan,theta_tilt) + self.antenna2.add_beam(phi_scan, theta_tilt) # Test beams_list - self.assertEqual(len(self.antenna2.beams_list),2) - self.assertEqual(len(self.antenna2.w_vec_list),2) + self.assertEqual(len(self.antenna2.beams_list), 2) + self.assertEqual(len(self.antenna2.w_vec_list), 2) # Test first beam - self.assertAlmostEqual(self.antenna2.beams_list[0][0],45,delta=eps) - self.assertAlmostEqual(self.antenna2.beams_list[0][1],30,delta=eps) + self.assertAlmostEqual(self.antenna2.beams_list[0][0], 45, delta=eps) + self.assertAlmostEqual(self.antenna2.beams_list[0][1], 30, delta=eps) w_vec = self.antenna2.w_vec_list[0] - expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j],\ - [0.0 + 0.5j, 0.469165 - 0.172870j]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5 + 0.0j, -0.172870 - 0.469169j], + [0.0 + 0.5j, 0.469165 - 0.172870j]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Test second beam - self.assertEqual(self.antenna2.beams_list[1][0],90) - self.assertEqual(self.antenna2.beams_list[1][1],90) + self.assertEqual(self.antenna2.beams_list[1][0], 90) + self.assertEqual(self.antenna2.beams_list[1][1], 90) w_vec = self.antenna2.w_vec_list[1] - expected_w_vec = np.array([[0.5, 0.5],[-0.5, -0.5]]) - self.assertTrue(np.allclose(np.real(w_vec),\ - np.real(expected_w_vec),rtol = eps)) - self.assertTrue(np.allclose(np.imag(w_vec),\ - np.imag(expected_w_vec),rtol = eps)) + expected_w_vec = np.array([[0.5, 0.5], [-0.5, -0.5]]) + self.assertTrue(np.allclose(np.real(w_vec), + np.real(expected_w_vec), rtol=eps)) + self.assertTrue(np.allclose(np.imag(w_vec), + np.imag(expected_w_vec), rtol=eps)) # Reset beams and test self.antenna2.reset_beams() - self.assertEqual(len(self.antenna2.beams_list),0) - self.assertEqual(len(self.antenna2.w_vec_list),0) + self.assertEqual(len(self.antenna2.beams_list), 0) + self.assertEqual(len(self.antenna2.w_vec_list), 0) def test_beam_gain(self): # Error margin and antenna eps = 1e-4 - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.antenna2 = AntennaBeamformingImt(par,0,0) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaBeamformingImt(par, 0, 0) # Test 1 phi = 45 @@ -278,9 +280,9 @@ def test_beam_gain(self): beam = 0 phi_scan = 45 theta_tilt = 135 - self.antenna2.add_beam(phi_scan,theta_tilt) - beam_g = self.antenna2._beam_gain(phi,theta,beam) - self.assertAlmostEqual(beam_g,1.594268,delta = eps) + self.antenna2.add_beam(phi_scan, theta_tilt) + beam_g = self.antenna2._beam_gain(phi, theta, beam) + self.assertAlmostEqual(beam_g, 1.594268, delta=eps) # Test 2 phi = 0 @@ -288,29 +290,30 @@ def test_beam_gain(self): beam = 1 phi_scan = 45 theta_tilt = 180 - self.antenna2.add_beam(phi_scan,theta_tilt) - beam_g = self.antenna2._beam_gain(phi,theta,beam) - self.assertAlmostEqual(beam_g,10.454087,delta = eps) + self.antenna2.add_beam(phi_scan, theta_tilt) + beam_g = self.antenna2._beam_gain(phi, theta, beam) + self.assertAlmostEqual(beam_g, 10.454087, delta=eps) # Test 3 phi = 32.5 theta = 115.2 - beam_g = self.antenna2._beam_gain(phi,theta) - self.assertAlmostEqual(beam_g,11.9636,delta = eps) + beam_g = self.antenna2._beam_gain(phi, theta) + self.assertAlmostEqual(beam_g, 11.9636, delta=eps) def test_calculate_gain(self): # Error margin and antenna eps = 1e-4 - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.antenna1 = AntennaBeamformingImt(par,0,0) - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.antenna2 = AntennaBeamformingImt(par,0,0) + par = self.bs_param.get_antenna_parameters() + self.antenna1 = AntennaBeamformingImt(par, 0, 0) + par = self.ue_param.get_antenna_parameters() + self.antenna2 = AntennaBeamformingImt(par, 0, 0) # Test 1 phi_vec = np.array([45.0, 32.5]) theta_vec = np.array([45.0, 115.2]) - gains = self.antenna2.calculate_gain(phi_vec=phi_vec, theta_vec=theta_vec) - npt.assert_allclose(gains,np.array([5.9491,11.9636]),atol=eps) + gains = self.antenna2.calculate_gain( + phi_vec=phi_vec, theta_vec=theta_vec) + npt.assert_allclose(gains, np.array([5.9491, 11.9636]), atol=eps) # Test 2 phi = 0.0 @@ -321,24 +324,24 @@ def test_calculate_gain(self): beams_l = np.zeros_like(phi_vec, dtype=int) gains = self.antenna2.calculate_gain(phi_vec=phi, theta_vec=theta, beams_l=beams_l) - npt.assert_allclose(gains,np.array([10.454087]),atol=eps) + 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) - + npt.assert_allclose(gains, np.array([1.6667]), atol=eps) + def test_normalization(self): # Create dummy normalization data adjacent_antenna_model = "SINGLE_ELEMENT" normalization = True norm_data = {'norm_file': 'dummy_file.npz', 'resolution': 1, - 'phi_range': (-180,+180), - 'theta_range': (0,180), - 'correction_factor_co_channel': np.ones((360,180)), + 'phi_range': (-180, +180), + 'theta_range': (0, 180), + 'correction_factor_co_channel': np.ones((360, 180)), 'error_co_channel': 0.0, 'correction_factor_adj_channel': 5, 'error_adj_channel': 0.0, @@ -372,20 +375,20 @@ def test_normalization(self): multiplication_factor, minimum_array_gain, downtilt) - - + # Create antenna objects - self.antenna3 = AntennaBeamformingImt(par,0.0,0.0) # Normalized - par = par._replace(normalization = False) - self.antenna4 = AntennaBeamformingImt(par,0.0,0.0) # Unormalized - + self.antenna3 = AntennaBeamformingImt(par, 0.0, 0.0) # Normalized + par = par._replace(normalization=False) + self.antenna4 = AntennaBeamformingImt(par, 0.0, 0.0) # Unormalized + # Test co-channel gain: no beam phi_v = np.array([11.79, -0.71]) theta_v = np.array([50.31, 120.51]) - gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v) + gain_ref = self.antenna4.calculate_gain( + phi_vec=phi_v, theta_vec=theta_v) gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v) - npt.assert_equal(gain,gain_ref + 1) - + npt.assert_equal(gain, gain_ref + 1) + # Test co-channel gain: add beam phi_scan = 11.79 theta_tilt = 185.31 @@ -393,126 +396,128 @@ def test_normalization(self): self.antenna4.add_beam(phi_scan, theta_tilt) beams_l = np.zeros_like(phi_v, dtype=int) gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, - beams_l=beams_l) + beams_l=beams_l) gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, - beams_l=beams_l) - npt.assert_equal(gain,gain_ref + 1) - + beams_l=beams_l) + npt.assert_equal(gain, gain_ref + 1) + # Test adjacent channel phi_v = np.array([11.79, -0.71]) theta_v = np.array([50.31, 120.51]) - gain_ref = self.antenna4.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, co_channel=False) - gain = self.antenna3.calculate_gain(phi_vec=phi_v, theta_vec=theta_v, co_channel=False) - npt.assert_equal(gain,gain_ref + 5) + gain_ref = self.antenna4.calculate_gain( + phi_vec=phi_v, theta_vec=theta_v, co_channel=False) + gain = self.antenna3.calculate_gain( + phi_vec=phi_v, theta_vec=theta_v, co_channel=False) + npt.assert_equal(gain, gain_ref + 5) def test_to_local_coord(self): # Test 1 # Create antenna object - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() azi = 0.0 ele = 90.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted: z, x and y axis - phi = np.array([ 0, 90, 0]) + phi = np.array([0, 90, 0]) theta = np.array([90, 90, 0]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) - exp_lo_phi = np.array([0,90,0]) - exp_lo_theta = np.array([180,90,90]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - + exp_lo_phi = np.array([0, 90, 0]) + exp_lo_theta = np.array([180, 90, 90]) + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + # Test 2 # Create antenna object azi = -15.0 ele = 90.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted - phi = np.array([ 0, 0, 90, 10]) + phi = np.array([0, 0, 90, 10]) theta = np.array([0, 90, 90, 90]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) exp_lo_phi = np.array([0, 90, 90, 90]) exp_lo_theta = np.array([90, 165, 75, 155]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + # Test 3 # Create antenna object azi = +15.0 ele = 90.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted: z, x and y axis - phi = np.array([ 0, 0, 90]) + phi = np.array([0, 0, 90]) theta = np.array([0, 90, 90]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) - exp_lo_phi = np.array([0,-90, 90]) + exp_lo_phi = np.array([0, -90, 90]) exp_lo_theta = np.array([90, 165, 105]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + # Test 4 # Create antenna object azi = 0.0 ele = 15.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted: z, x and y axis - phi = np.array([ 0, 0, 90]) + phi = np.array([0, 0, 90]) theta = np.array([0, 90, 90]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) exp_lo_phi = np.array([0, 0, 90]) exp_lo_theta = np.array([15, 105, 90]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + # Test 5 # Create antenna object azi = 0.0 ele = -15.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted: z, x and y axis - phi = np.array([ 0, 0, 90]) + phi = np.array([0, 0, 90]) theta = np.array([0, 90, 90]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) exp_lo_phi = np.array([180, 0, 90]) exp_lo_theta = np.array([15, 75, 90]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + # Test 6 # Create antenna object azi = 180.0 ele = 45.0 - self.antenna3 = AntennaBeamformingImt(par,azi,ele) - + self.antenna3 = AntennaBeamformingImt(par, azi, ele) + # Angles to be converted: z, x and y axis - phi = np.array([ 0, 0, 90]) + phi = np.array([0, 0, 90]) theta = np.array([0, 90, 90]) # Convert to local coordinates lo_phi, lo_theta = self.antenna3.to_local_coord(phi, theta) exp_lo_phi = np.array([0, -180, -90]) exp_lo_theta = np.array([45, 45, 90]) - npt.assert_array_almost_equal(lo_phi,exp_lo_phi,decimal=2) - npt.assert_array_almost_equal(lo_theta,exp_lo_theta,decimal=2) - - + npt.assert_array_almost_equal(lo_phi, exp_lo_phi, decimal=2) + npt.assert_array_almost_equal(lo_theta, exp_lo_theta, decimal=2) + + if __name__ == '__main__': unittest.main() -# +# # suite = unittest.TestSuite() # suite.addTest(AntennaBeamformingImtTest('test_calculate_gain')) # unittest.TextTestRunner().run(suite) diff --git a/tests/test_antenna_beamforming_imt_f1336.py b/tests/test_antenna_beamforming_imt_f1336.py index 5de5cc399..8f452ae9e 100644 --- a/tests/test_antenna_beamforming_imt_f1336.py +++ b/tests/test_antenna_beamforming_imt_f1336.py @@ -10,181 +10,171 @@ import numpy.testing as npt from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.support.named_tuples import AntennaPar from sharc.support.enumerations import StationType + class AntennaBeamformingImtF1336Test(unittest.TestCase): def setUp(self): - #Array parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_element_pattern = "F1336" - self.param.bs_minimum_array_gain = -200 - self.param.bs_normalization = False - self.param.bs_downtilt = 0 - - self.param.bs_normalization_file = None - self.param.bs_element_max_g = 18 - self.param.bs_element_phi_3db = 65 - self.param.bs_element_theta_3db = 0 - self.param.bs_n_rows = 1 - self.param.bs_n_columns = 1 - - self.param.bs_element_am = 30 - self.param.bs_element_sla_v = 30 - self.param.bs_element_horiz_spacing = 0.5 - self.param.bs_element_vert_spacing = 0.5 - self.param.bs_multiplication_factor = 12 + # Array parameters + self.bs_param = ParametersAntennaImt() + + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.element_pattern = "F1336" + self.bs_param.minimum_array_gain = -200 + self.bs_param.normalization = False + self.bs_param.downtilt = 0 + + self.bs_param.normalization_file = None + self.bs_param.element_max_g = 18 + self.bs_param.element_phi_3db = 65 + self.bs_param.element_theta_3db = 0 + self.bs_param.n_rows = 1 + self.bs_param.n_columns = 1 + + self.bs_param.element_am = 30 + self.bs_param.element_sla_v = 30 + self.bs_param.element_horiz_spacing = 0.5 + self.bs_param.element_vert_spacing = 0.5 + self.bs_param.multiplication_factor = 12 # Create antenna objects - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() self.antenna1 = AntennaBeamformingImt(par, 300, -10) - def test_azimuth(self): self.assertEqual(self.antenna1.azimuth, 300) - def test_elevation(self): self.assertEqual(self.antenna1.elevation, -10) - def test_g_max(self): self.assertEqual(self.antenna1.element.g_max, 18) - def test_phi_3db(self): self.assertEqual(self.antenna1.element.phi_3db, 65) - def test_theta_3db(self): - self.assertAlmostEqual(self.antenna1.element.theta_3db, 7.55, delta = 1e-2) - + self.assertAlmostEqual( + self.antenna1.element.theta_3db, 7.55, delta=1e-2) def test_n_rows(self): self.assertEqual(self.antenna1.n_rows, 1) - def test_n_cols(self): self.assertEqual(self.antenna1.n_cols, 1) - def test_beams_list(self): self.assertEqual(len(self.antenna1.beams_list), 0) - def test_w_vec_list(self): self.assertEqual(len(self.antenna1.w_vec_list), 0) - def test_super_position_vector(self): phi = 0 theta = 0 v_vec = self.antenna1._super_position_vector(phi, theta) expected_v_vec = np.array([[1]]) - npt.assert_allclose(np.real(v_vec), - np.real(expected_v_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(v_vec), - np.imag(expected_v_vec), - atol = 1e-2) + npt.assert_allclose(np.real(v_vec), + np.real(expected_v_vec), + atol=1e-2) + npt.assert_allclose(np.imag(v_vec), + np.imag(expected_v_vec), + atol=1e-2) phi = 90 theta = 90 v_vec = self.antenna1._super_position_vector(phi, theta) expected_v_vec = np.array([[1]]) - npt.assert_allclose(np.real(v_vec), - np.real(expected_v_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(v_vec), - np.imag(expected_v_vec), - atol = 1e-2) + npt.assert_allclose(np.real(v_vec), + np.real(expected_v_vec), + atol=1e-2) + npt.assert_allclose(np.imag(v_vec), + np.imag(expected_v_vec), + atol=1e-2) phi = 45 theta = 45 v_vec = self.antenna1._super_position_vector(phi, theta) expected_v_vec = np.array([[1]]) - npt.assert_allclose(np.real(v_vec), - np.real(expected_v_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(v_vec), - np.imag(expected_v_vec), - atol = 1e-2) + npt.assert_allclose(np.real(v_vec), + np.real(expected_v_vec), + atol=1e-2) + npt.assert_allclose(np.imag(v_vec), + np.imag(expected_v_vec), + atol=1e-2) phi = 60 theta = 90 v_vec = self.antenna1._super_position_vector(phi, theta) expected_v_vec = np.array([[1]]) - npt.assert_allclose(np.real(v_vec), - np.real(expected_v_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(v_vec), - np.imag(expected_v_vec), - atol = 1e-2) - + npt.assert_allclose(np.real(v_vec), + np.real(expected_v_vec), + atol=1e-2) + npt.assert_allclose(np.imag(v_vec), + np.imag(expected_v_vec), + atol=1e-2) def test_weight_vector(self): phi_scan = 0 theta_tilt = 0 w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt) expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) phi_scan = 90 theta_tilt = 90 w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt) expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) phi_scan = 45 theta_tilt = 45 w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt) expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) phi_scan = 0 theta_tilt = 90 w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt) expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) phi_scan = 45 theta_tilt = 30 w_vec = self.antenna1._weight_vector(phi_scan, theta_tilt) expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) - + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) def test_add_beam(self): eps = 1e-5 - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() self.antenna1 = AntennaBeamformingImt(par, 0, 0) # Add first beam @@ -205,17 +195,17 @@ def test_add_beam(self): self.assertEqual(len(self.antenna1.w_vec_list), 2) # Test first beam - self.assertAlmostEqual(self.antenna1.beams_list[0][0], 45, delta = eps) - self.assertAlmostEqual(self.antenna1.beams_list[0][1], 30, delta = eps) + self.assertAlmostEqual(self.antenna1.beams_list[0][0], 45, delta=eps) + self.assertAlmostEqual(self.antenna1.beams_list[0][1], 30, delta=eps) w_vec = self.antenna1.w_vec_list[0] expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) # Test second beam self.assertEqual(self.antenna1.beams_list[1][0], 90) @@ -223,12 +213,12 @@ def test_add_beam(self): w_vec = self.antenna1.w_vec_list[1] expected_w_vec = np.array([[1]]) - npt.assert_allclose(np.real(w_vec), - np.real(expected_w_vec), - atol = 1e-2) - npt.assert_allclose(np.imag(w_vec), - np.imag(expected_w_vec), - atol = 1e-2) + npt.assert_allclose(np.real(w_vec), + np.real(expected_w_vec), + atol=1e-2) + npt.assert_allclose(np.imag(w_vec), + np.imag(expected_w_vec), + atol=1e-2) # Reset beams and test self.antenna1.reset_beams() @@ -238,7 +228,7 @@ def test_add_beam(self): def test_beam_gain(self): # Error margin and antenna eps = 1e-2 - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() self.antenna1 = AntennaBeamformingImt(par, 0, 0) # Test 1 @@ -249,7 +239,7 @@ def test_beam_gain(self): theta_tilt = 135 self.antenna1.add_beam(phi_scan, theta_tilt) beam_g = self.antenna1._beam_gain(phi, theta, beam) - self.assertAlmostEqual(beam_g, 18, delta = eps) + self.assertAlmostEqual(beam_g, 18, delta=eps) # Test 2 phi = 30 @@ -259,26 +249,26 @@ def test_beam_gain(self): theta_tilt = 180 self.antenna1.add_beam(phi_scan, theta_tilt) beam_g = self.antenna1._beam_gain(phi, theta, beam) - self.assertAlmostEqual(beam_g, -1.48, delta = eps) + self.assertAlmostEqual(beam_g, -1.48, delta=eps) # Test 3 phi = 150 theta = 180 beam_g = self.antenna1._beam_gain(phi, theta) - self.assertAlmostEqual(beam_g, -6.45, delta = eps) + self.assertAlmostEqual(beam_g, -6.45, delta=eps) def test_calculate_gain(self): # Error margin and antenna eps = 1e-2 - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.antenna1 = AntennaBeamformingImt(par,0,0) + par = self.bs_param.get_antenna_parameters() + self.antenna1 = AntennaBeamformingImt(par, 0, 0) # Test 1 phi_vec = np.array([0, 30]) theta_vec = np.array([90, 100]) - gains = self.antenna1.calculate_gain(phi_vec = phi_vec, - theta_vec = theta_vec) - npt.assert_allclose(gains,np.array([18, 4.52]), atol=eps) + gains = self.antenna1.calculate_gain(phi_vec=phi_vec, + theta_vec=theta_vec) + npt.assert_allclose(gains, np.array([18, 4.52]), atol=eps) # Test 2 phi = 0 @@ -287,19 +277,19 @@ def test_calculate_gain(self): theta_tilt = 180 self.antenna1.add_beam(phi_scan, theta_tilt) beams_l = np.zeros_like(phi_vec, dtype=int) - gains = self.antenna1.calculate_gain(phi_vec = phi, - theta_vec = theta, - beams_l = beams_l) + gains = self.antenna1.calculate_gain(phi_vec=phi, + theta_vec=theta, + beams_l=beams_l) npt.assert_allclose(gains, np.array([12.74]), atol=eps) # Test 3 phi = 5 theta = 98 - gains = self.antenna1.calculate_gain(phi_vec = phi, - theta_vec = theta, - co_channel = False) - npt.assert_allclose(gains, np.array([6.81]), atol = eps) - - + gains = self.antenna1.calculate_gain(phi_vec=phi, + theta_vec=theta, + co_channel=False) + npt.assert_allclose(gains, np.array([6.81]), atol=eps) + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_element_imt.py b/tests/test_antenna_element_imt.py index 92aa90357..e05df5482 100644 --- a/tests/test_antenna_element_imt.py +++ b/tests/test_antenna_element_imt.py @@ -9,142 +9,147 @@ import unittest from sharc.antenna.antenna_element_imt_m2101 import AntennaElementImtM2101 -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.support.enumerations import StationType + class AntennaImtTest(unittest.TestCase): def setUp(self): - #Element parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_element_pattern = "M2101" - self.param.ue_element_pattern = "M2101" - self.param.bs_minimum_array_gain = -200 - self.param.ue_minimum_array_gain = -200 - self.param.bs_normalization = False - self.param.bs_downtilt = 0 - - self.param.bs_normalization_file = None - self.param.bs_element_max_g = 5 - self.param.bs_element_phi_3db = 80 - self.param.bs_element_theta_3db = 60 - self.param.bs_element_am = 30 - self.param.bs_element_sla_v = 30 - self.param.bs_n_rows = 8 - self.param.bs_n_columns = 8 - self.param.bs_element_horiz_spacing = 0.5 - self.param.bs_element_vert_spacing = 0.5 - self.param.bs_multiplication_factor = 12 - - self.param.ue_normalization_file = None - self.param.ue_normalization = False - self.param.ue_element_max_g = 10 - self.param.ue_element_phi_3db = 75 - self.param.ue_element_theta_3db = 65 - self.param.ue_element_am = 25 - self.param.ue_element_sla_v = 35 - self.param.ue_n_rows = 4 - self.param.ue_n_columns = 4 - self.param.ue_element_horiz_spacing = 0.5 - self.param.ue_element_vert_spacing = 0.5 - self.param.ue_multiplication_factor = 12 + # Element parameters + self.ue_param = ParametersAntennaImt() + self.bs_param = ParametersAntennaImt() + + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.element_pattern = "M2101" + self.ue_param.element_pattern = "M2101" + self.bs_param.minimum_array_gain = -200 + self.ue_param.minimum_array_gain = -200 + self.bs_param.normalization = False + self.bs_param.downtilt = 0 + + self.bs_param.normalization_file = None + self.bs_param.element_max_g = 5 + self.bs_param.element_phi_3db = 80 + self.bs_param.element_theta_3db = 60 + self.bs_param.element_am = 30 + self.bs_param.element_sla_v = 30 + self.bs_param.n_rows = 8 + self.bs_param.n_columns = 8 + self.bs_param.element_horiz_spacing = 0.5 + self.bs_param.element_vert_spacing = 0.5 + self.bs_param.multiplication_factor = 12 + + self.ue_param.normalization_file = None + self.ue_param.normalization = False + self.ue_param.element_max_g = 10 + self.ue_param.element_phi_3db = 75 + self.ue_param.element_theta_3db = 65 + self.ue_param.element_am = 25 + self.ue_param.element_sla_v = 35 + self.ue_param.n_rows = 4 + self.ue_param.n_columns = 4 + self.ue_param.element_horiz_spacing = 0.5 + self.ue_param.element_vert_spacing = 0.5 + self.ue_param.multiplication_factor = 12 # Create antenna IMT objects - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() self.antenna1 = AntennaElementImtM2101(par) - par = self.param.get_antenna_parameters(StationType.IMT_UE) + par = self.ue_param.get_antenna_parameters() self.antenna2 = AntennaElementImtM2101(par) def test_g_max(self): - self.assertEqual(self.antenna1.g_max,5) - self.assertEqual(self.antenna2.g_max,10) + self.assertEqual(self.antenna1.g_max, 5) + self.assertEqual(self.antenna2.g_max, 10) def test_phi_3db(self): - self.assertEqual(self.antenna1.phi_3db,80) - self.assertEqual(self.antenna2.phi_3db,75) + self.assertEqual(self.antenna1.phi_3db, 80) + self.assertEqual(self.antenna2.phi_3db, 75) def test_theta_3db(self): - self.assertEqual(self.antenna1.theta_3db,60) - self.assertEqual(self.antenna2.theta_3db,65) + self.assertEqual(self.antenna1.theta_3db, 60) + self.assertEqual(self.antenna2.theta_3db, 65) def test_am(self): - self.assertEqual(self.antenna1.am,30) - self.assertEqual(self.antenna2.am,25) + self.assertEqual(self.antenna1.am, 30) + self.assertEqual(self.antenna2.am, 25) def test_sla_v(self): - self.assertEqual(self.antenna1.sla_v,30) - self.assertEqual(self.antenna2.sla_v,35) + self.assertEqual(self.antenna1.sla_v, 30) + self.assertEqual(self.antenna2.sla_v, 35) def test_horizontal_pattern(self): # phi = 0 results in zero gain phi = 0 h_att = self.antenna1.horizontal_pattern(phi) - self.assertEqual(h_att,0.0) + self.assertEqual(h_att, 0.0) # phi = 120 implies horizontal gain of of -27 dB phi = 120 h_att = self.antenna1.horizontal_pattern(phi) - self.assertEqual(h_att,-27.0) + self.assertEqual(h_att, -27.0) # phi = 150, horizontal attenuation equals to the front-to-back ratio phi = 150 h_att = self.antenna1.horizontal_pattern(phi) - self.assertEqual(h_att,-30) - self.assertEqual(h_att,-1.0*self.antenna1.am) + self.assertEqual(h_att, -30) + self.assertEqual(h_att, -1.0 * self.antenna1.am) # Test vector phi = np.array([0, 120, 150]) h_att = self.antenna1.horizontal_pattern(phi) - self.assertTrue(np.all(h_att == np.array([0.0,-27.0,-30.0]))) + self.assertTrue(np.all(h_att == np.array([0.0, -27.0, -30.0]))) def test_vertical_pattern(self): # theta = 90 results in zero gain theta = 90 v_att = self.antenna1.vertical_pattern(theta) - self.assertEqual(v_att,0.0) + self.assertEqual(v_att, 0.0) # theta = 180 implies vertical gain of -27 dB theta = 180 v_att = self.antenna1.vertical_pattern(theta) - self.assertEqual(v_att,-27.0) + self.assertEqual(v_att, -27.0) - # theta = 210, vertical attenuation equals vertical sidelobe attenuation + # theta = 210, vertical attenuation equals vertical sidelobe + # attenuation theta = 210 v_att = self.antenna1.vertical_pattern(theta) - self.assertEqual(v_att,-30) - self.assertEqual(v_att,-1.0*self.antenna1.sla_v) + self.assertEqual(v_att, -30) + self.assertEqual(v_att, -1.0 * self.antenna1.sla_v) # Test vector theta = np.array([90, 180, 210]) v_att = self.antenna1.vertical_pattern(theta) - self.assertTrue(np.all(v_att == np.array([0.0,-27.0,-30.0]))) + self.assertTrue(np.all(v_att == np.array([0.0, -27.0, -30.0]))) def test_element_pattern(self): # theta = 0 and phi = 90 result in maximum gain phi = 0 theta = 90 - e_gain = self.antenna1.element_pattern(phi,theta) - self.assertEqual(e_gain,5.0) - self.assertEqual(e_gain,self.antenna1.g_max) + e_gain = self.antenna1.element_pattern(phi, theta) + self.assertEqual(e_gain, 5.0) + self.assertEqual(e_gain, self.antenna1.g_max) phi = 80 theta = 150 - e_gain = self.antenna1.element_pattern(phi,theta) - self.assertEqual(e_gain,-19.0) + e_gain = self.antenna1.element_pattern(phi, theta) + self.assertEqual(e_gain, -19.0) phi = 150 theta = 210 - e_gain = self.antenna1.element_pattern(phi,theta) - self.assertEqual(e_gain,-25.0) - self.assertEqual(e_gain,self.antenna1.g_max - self.antenna1.am) + e_gain = self.antenna1.element_pattern(phi, theta) + self.assertEqual(e_gain, -25.0) + self.assertEqual(e_gain, self.antenna1.g_max - self.antenna1.am) # Test vector - phi = np.array([0,80,150]) - theta = np.array([90,150,210]) - e_gain = self.antenna1.element_pattern(phi,theta) - self.assertTrue(np.all(e_gain == np.array([5.0,-19.0,-25.0]))) + phi = np.array([0, 80, 150]) + theta = np.array([90, 150, 210]) + e_gain = self.antenna1.element_pattern(phi, theta) + self.assertTrue(np.all(e_gain == np.array([5.0, -19.0, -25.0]))) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_element_imt_f1336.py b/tests/test_antenna_element_imt_f1336.py index e27b2beb1..49cda9954 100644 --- a/tests/test_antenna_element_imt_f1336.py +++ b/tests/test_antenna_element_imt_f1336.py @@ -10,36 +10,37 @@ import numpy.testing as npt from sharc.antenna.antenna_element_imt_f1336 import AntennaElementImtF1336 -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.support.enumerations import StationType + class AntennaElementImtF1336Test(unittest.TestCase): def setUp(self): - #Element parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_element_pattern = "F1336" - self.param.bs_minimum_array_gain = -200 - self.param.bs_normalization = False - self.param.bs_downtilt = 0 - - self.param.bs_normalization_file = None - self.param.bs_element_max_g = 18 - self.param.bs_element_phi_3db = 65 - self.param.bs_element_theta_3db = 0 - self.param.bs_n_rows = 1 - self.param.bs_n_columns = 1 - - self.param.bs_element_am = 30 - self.param.bs_element_sla_v = 30 - self.param.bs_element_horiz_spacing = 0.5 - self.param.bs_element_vert_spacing = 0.5 - self.param.bs_multiplication_factor = 12 + # Element parameters + self.bs_param = ParametersAntennaImt() + + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.element_pattern = "F1336" + self.bs_param.minimum_array_gain = -200 + self.bs_param.normalization = False + self.bs_param.downtilt = 0 + + self.bs_param.normalization_file = None + self.bs_param.element_max_g = 18 + self.bs_param.element_phi_3db = 65 + self.bs_param.element_theta_3db = 0 + self.bs_param.n_rows = 1 + self.bs_param.n_columns = 1 + + self.bs_param.element_am = 30 + self.bs_param.element_sla_v = 30 + self.bs_param.element_horiz_spacing = 0.5 + self.bs_param.element_vert_spacing = 0.5 + self.bs_param.multiplication_factor = 12 # Create antenna IMT objects - par = self.param.get_antenna_parameters(StationType.IMT_BS) + par = self.bs_param.get_antenna_parameters() self.antenna1 = AntennaElementImtF1336(par) def test_g_max(self): @@ -49,58 +50,55 @@ def test_phi_3db(self): self.assertEqual(self.antenna1.phi_3db, 65) def test_theta_3db(self): - self.assertAlmostEqual(self.antenna1.theta_3db, 7.55, delta = 1e-2) + self.assertAlmostEqual(self.antenna1.theta_3db, 7.55, delta=1e-2) def test_antenna_parameters(self): - self.assertAlmostEqual(self.antenna1.k_a, 0.7, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.k_p, 0.7, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.k_h, 0.7, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.lambda_k_h, -1.87, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.k_v, 0.3, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.incline_factor, 18.45, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.x_k, 0.94, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.lambda_k_v, 4.60, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.g_hr_180, -24.45, delta = 1e-2) - self.assertAlmostEqual(self.antenna1.g_hr_0, 0, delta = 1e-2) - - + self.assertAlmostEqual(self.antenna1.k_a, 0.7, delta=1e-2) + self.assertAlmostEqual(self.antenna1.k_p, 0.7, delta=1e-2) + self.assertAlmostEqual(self.antenna1.k_h, 0.7, delta=1e-2) + self.assertAlmostEqual(self.antenna1.lambda_k_h, -1.87, delta=1e-2) + self.assertAlmostEqual(self.antenna1.k_v, 0.3, delta=1e-2) + self.assertAlmostEqual(self.antenna1.incline_factor, 18.45, delta=1e-2) + self.assertAlmostEqual(self.antenna1.x_k, 0.94, delta=1e-2) + self.assertAlmostEqual(self.antenna1.lambda_k_v, 4.60, delta=1e-2) + self.assertAlmostEqual(self.antenna1.g_hr_180, -24.45, delta=1e-2) + self.assertAlmostEqual(self.antenna1.g_hr_0, 0, delta=1e-2) + def test_horizontal_pattern(self): phi = 0 h_att = self.antenna1.horizontal_pattern(phi) - self.assertAlmostEqual(h_att, 0, delta = 1e-2) + self.assertAlmostEqual(h_att, 0, delta=1e-2) phi = 30 h_att = self.antenna1.horizontal_pattern(phi) - self.assertAlmostEqual(h_att, -2.55, delta = 1e-2) + self.assertAlmostEqual(h_att, -2.55, delta=1e-2) phi = 150 h_att = self.antenna1.horizontal_pattern(phi) - self.assertAlmostEqual(h_att, -24.45, delta = 1e-2) + self.assertAlmostEqual(h_att, -24.45, delta=1e-2) # Test vector phi = np.array([0, 30, 150]) h_att = self.antenna1.horizontal_pattern(phi) - npt.assert_allclose(h_att, np.array([0, -2.55, -24.45]), atol = 1e-2) - + npt.assert_allclose(h_att, np.array([0, -2.55, -24.45]), atol=1e-2) def test_vertical_pattern(self): theta = 90 v_att = self.antenna1.vertical_pattern(theta) - self.assertAlmostEqual(v_att, 0, delta = 1e-2) + self.assertAlmostEqual(v_att, 0, delta=1e-2) theta = 135 v_att = self.antenna1.vertical_pattern(theta) - self.assertAlmostEqual(v_att, -18.90, delta = 1e-2) + self.assertAlmostEqual(v_att, -18.90, delta=1e-2) theta = 180 v_att = self.antenna1.vertical_pattern(theta) - self.assertAlmostEqual(v_att, -24.45, delta = 1e-2) + self.assertAlmostEqual(v_att, -24.45, delta=1e-2) # Test vector theta = np.array([90, 135, 180]) v_att = self.antenna1.vertical_pattern(theta) - npt.assert_allclose(v_att, np.array([0, -18.90, -24.45]), atol = 1e-2) - + npt.assert_allclose(v_att, np.array([0, -18.90, -24.45]), atol=1e-2) def test_element_pattern(self): phi = 0 @@ -112,18 +110,19 @@ def test_element_pattern(self): phi = 30 theta = 135 e_gain = self.antenna1.element_pattern(phi, theta) - self.assertAlmostEqual(e_gain, -1.48, delta = 1e-2) + self.assertAlmostEqual(e_gain, -1.48, delta=1e-2) phi = 150 theta = 180 e_gain = self.antenna1.element_pattern(phi, theta) - self.assertAlmostEqual(e_gain, -6.45, delta = 1e-2) + self.assertAlmostEqual(e_gain, -6.45, delta=1e-2) # Test vector phi = np.array([0, 30, 150]) theta = np.array([90, 135, 180]) e_gain = self.antenna1.element_pattern(phi, theta) - npt.assert_allclose(e_gain, np.array([18, -1.48, -6.45]), atol = 1e-2) + npt.assert_allclose(e_gain, np.array([18, -1.48, -6.45]), atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_fss_ss.py b/tests/test_antenna_fss_ss.py index 4b4976e40..c311fff0d 100644 --- a/tests/test_antenna_fss_ss.py +++ b/tests/test_antenna_fss_ss.py @@ -13,6 +13,7 @@ import numpy as np import numpy.testing as npt + class AntennaFssSsTest(unittest.TestCase): def setUp(self): @@ -27,16 +28,19 @@ def setUp(self): param.antenna_l_s = -30 self.antenna30 = AntennaFssSs(param) - def test_calculate_gain(self): psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]) - ref_gain25 = np.array([0, -3, -12, -28, -28, -28, -28, -29.12, -30.57, -31.85, -33, -53]) - gain25 = self.antenna25.calculate_gain(off_axis_angle_vec=psi) - self.antenna25.peak_gain + ref_gain25 = np.array( + [0, -3, -12, -28, -28, -28, -28, -29.12, -30.57, -31.85, -33, -53]) + gain25 = self.antenna25.calculate_gain( + off_axis_angle_vec=psi) - self.antenna25.peak_gain npt.assert_allclose(gain25, ref_gain25, atol=1e-2) - ref_gain30 = np.array([0, -3, -12, -27, -33, -33, -33, -34.12, -35.57, -36.85, -38, -53]) - gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain + ref_gain30 = np.array( + [0, -3, -12, -27, -33, -33, -33, -34.12, -35.57, -36.85, -38, -53]) + gain30 = self.antenna30.calculate_gain( + off_axis_angle_vec=psi) - self.antenna30.peak_gain npt.assert_allclose(gain30, ref_gain30, atol=1e-2) diff --git a/tests/test_antenna_omni.py b/tests/test_antenna_omni.py index 66d2f39b0..327318bd6 100644 --- a/tests/test_antenna_omni.py +++ b/tests/test_antenna_omni.py @@ -10,40 +10,41 @@ from sharc.antenna.antenna_omni import AntennaOmni + class AntennaOmniTest(unittest.TestCase): - + def setUp(self): self.antenna1 = AntennaOmni() self.antenna1.gain = 5 - + self.antenna2 = AntennaOmni() self.antenna2.gain = 8.0 - + self.antenna3 = AntennaOmni(10) - + def test_gain(self): self.assertEqual(self.antenna1.gain, 5) self.assertEqual(self.antenna2.gain, 8) self.assertEqual(self.antenna3.gain, 10) - + def test_calculate_gain(self): phi = [30, 60, 90, 45] - + # Test antenna1 gains1 = self.antenna1.calculate_gain(phi_vec=phi) self.assertEqual(len(gains1), len(phi)) npt.assert_allclose(gains1, self.antenna1.gain) - + # Test antenna2 gains2 = self.antenna2.calculate_gain(phi_vec=phi) self.assertEqual(len(gains2), len(phi)) npt.assert_allclose(gains2, self.antenna2.gain) - + # Test antenna3 gains3 = self.antenna3.calculate_gain(phi_vec=phi) self.assertEqual(len(gains3), len(phi)) npt.assert_allclose(gains3, self.antenna3.gain) - - + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_s1528.py b/tests/test_antenna_s1528.py index f2984a769..1ce09e3b9 100644 --- a/tests/test_antenna_s1528.py +++ b/tests/test_antenna_s1528.py @@ -13,6 +13,7 @@ import numpy as np import numpy.testing as npt + class AntennaS1528Test(unittest.TestCase): def setUp(self): @@ -30,13 +31,29 @@ def setUp(self): def test_calculate_gain(self): psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 80, 100]) - ref_gain20 = np.array([0, -3, -8.48, -20, -20, -20, -20, -21.10, -22.55, -23.83, -24.98, -39, -34.25]) - gain20 = self.antenna20.calculate_gain(off_axis_angle_vec=psi) - self.antenna20.peak_gain + ref_gain20 = np.array([0, - + 3, - + 8.48, - + 20, - + 20, - + 20, - + 20, - + 21.10, - + 22.55, - + 23.83, - + 24.98, - + 39, - + 34.25]) + gain20 = self.antenna20.calculate_gain( + off_axis_angle_vec=psi) - self.antenna20.peak_gain npt.assert_allclose(gain20, ref_gain20, atol=1e-2) - ref_gain30 = np.array([0, -3, -8.48, -30, -30, -30, -30, -31.10, -32.55, -33.83, -34.98, -39, -39]) - gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain + ref_gain30 = np.array( + [0, -3, -8.48, -30, -30, -30, -30, -31.10, -32.55, -33.83, -34.98, -39, -39]) + gain30 = self.antenna30.calculate_gain( + off_axis_angle_vec=psi) - self.antenna30.peak_gain npt.assert_allclose(gain30, ref_gain30, atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_s1855.py b/tests/test_antenna_s1855.py index a23c770b0..e4834e19d 100644 --- a/tests/test_antenna_s1855.py +++ b/tests/test_antenna_s1855.py @@ -13,10 +13,11 @@ from sharc.antenna.antenna_s1855 import AntennaS1855 from sharc.parameters.parameters_fss_es import ParametersFssEs + class AntennaS1855Test(unittest.TestCase): def setUp(self): - #Earth Station Antenna parameters + # Earth Station Antenna parameters params = ParametersFssEs() params.diameter = 9.1 params.frequency = 27200 @@ -31,10 +32,11 @@ def test_get_gain(self): off_axis_angle = np.array([7, 8, 15, 100]) theta = np.array([90, 45, 45, 45]) - expected_result = np.array([ 10.87, 8.71, 2.59, -10 ]) - gain = self.antenna.calculate_gain(off_axis_angle_vec = off_axis_angle, - theta_vec = theta) + expected_result = np.array([10.87, 8.71, 2.59, -10]) + gain = self.antenna.calculate_gain(off_axis_angle_vec=off_axis_angle, + theta_vec=theta) npt.assert_allclose(gain, expected_result, atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_s672.py b/tests/test_antenna_s672.py index e694e0cdb..22c73bf6a 100644 --- a/tests/test_antenna_s672.py +++ b/tests/test_antenna_s672.py @@ -13,6 +13,7 @@ import numpy as np import numpy.testing as npt + class AntennaS672Test(unittest.TestCase): def setUp(self): @@ -27,17 +28,21 @@ def setUp(self): param.antenna_l_s = -30 self.antenna30 = AntennaS672(param) - def test_calculate_gain(self): psi = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]) - ref_gain20 = np.array([0, -3, -12, -20, -20, -20, -20, -21.12, -22.57, -23.85, -25, -50]) - gain20 = self.antenna20.calculate_gain(off_axis_angle_vec=psi) - self.antenna20.peak_gain + ref_gain20 = np.array( + [0, -3, -12, -20, -20, -20, -20, -21.12, -22.57, -23.85, -25, -50]) + gain20 = self.antenna20.calculate_gain( + off_axis_angle_vec=psi) - self.antenna20.peak_gain npt.assert_allclose(gain20, ref_gain20, atol=1e-2) - ref_gain30 = np.array([0, -3, -12, -27, -30, -30, -30, -31.12, -32.57, -33.85, -35, -50]) - gain30 = self.antenna30.calculate_gain(off_axis_angle_vec=psi) - self.antenna30.peak_gain + ref_gain30 = np.array( + [0, -3, -12, -27, -30, -30, -30, -31.12, -32.57, -33.85, -35, -50]) + gain30 = self.antenna30.calculate_gain( + off_axis_angle_vec=psi) - self.antenna30.peak_gain npt.assert_allclose(gain30, ref_gain30, atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_antenna_sa509.py b/tests/test_antenna_sa509.py index 04586b5ac..d148b6442 100644 --- a/tests/test_antenna_sa509.py +++ b/tests/test_antenna_sa509.py @@ -12,29 +12,29 @@ import unittest import numpy.testing as npt + class AntennaSA509Test(unittest.TestCase): def setUp(self): - self.par = ParametersRas(); + self.par = ParametersRas() self.par.diameter = 10 self.par.antenna_efficiency = 1 self.par.frequency = 30000 - self.par.SPEED_OF_LIGHT = 3e8 self.antenna = AntennaSA509(self.par) def test_construction(self): - self.assertEqual(self.antenna.diameter,10) - self.assertEqual(self.antenna.efficiency,1) - self.assertEqual(self.antenna.wavelength,1e-2) + self.assertEqual(self.antenna.diameter, 10) + self.assertEqual(self.antenna.efficiency, 1) + self.assertAlmostEqual(self.antenna.wavelength, 1e-2, places=3) - self.assertAlmostEqual(self.antenna.effective_area,78.539,delta=1e-2) + self.assertAlmostEqual(self.antenna.effective_area, 78.539, delta=1e-2) - self.assertAlmostEqual(self.antenna.g_0,69.943,delta=1e-2) - self.assertAlmostEqual(self.antenna.phi_0,0.03464,delta=1e-4) + self.assertAlmostEqual(self.antenna.g_0, 69.943, delta=1e-2) + self.assertAlmostEqual(self.antenna.phi_0, 0.03464, delta=1e-4) - self.assertAlmostEqual(self.antenna.phi_1,0.08944,delta=1e-4) - self.assertAlmostEqual(self.antenna.phi_2,0.14531,delta=1e-4) + self.assertAlmostEqual(self.antenna.phi_1, 0.08944, delta=1e-4) + self.assertAlmostEqual(self.antenna.phi_2, 0.14531, delta=1e-4) def test_calculate_gain(self): phi = np.array([0.03464, 0.05, 0.1, 10, 25, 50, 100, 150]) diff --git a/tests/test_atmosphere.py b/tests/test_atmosphere.py index 5c3dfa1e0..6dbcb3820 100644 --- a/tests/test_atmosphere.py +++ b/tests/test_atmosphere.py @@ -10,14 +10,15 @@ import numpy.testing as npt from sharc.propagation.atmosphere import ReferenceAtmosphere + class TestAtmosphere(unittest.TestCase): def setUp(self): self.atmosphere = ReferenceAtmosphere() def test_specific_attenuation(self): - temperature = 15 + 273.15 # K - vapour_density = 7.5 # g/m**3 + temperature = 15 + 273.15 # K + vapour_density = 7.5 # g/m**3 pressure_hPa = 1013.25 vapour_pressure_hPa = vapour_density * temperature / 216.7 @@ -35,5 +36,6 @@ def test_specific_attenuation(self): npt.assert_array_less(specific_att_p676_lower, specific_att) npt.assert_array_less(specific_att, specific_att_p676_upper) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_footprint.py b/tests/test_footprint.py index a2710d4c8..ea8883b37 100644 --- a/tests/test_footprint.py +++ b/tests/test_footprint.py @@ -3,54 +3,142 @@ Created on Mon Nov 27 08:53:15 2017 @author: Calil + +Updated on Sun Jul 29 21:34:07 2024 + +Request for Issue 33 correction suggestion, adding the unitary tests for LEO type satellite in 600Km and 1200Km heights. + +@author: Thiago Ferreira """ +# Thiago Ferreira - 231025717 + +# from footprint import Footprint # Old importing for testing and debugging space +# Importing Footprint class from the given module in repository +from sharc.support.footprint import Footprint import unittest import numpy as np import numpy.testing as npt -from sharc.support.footprint import Footprint +# Added the matplotlib for plotting the footprint generated in the test +import matplotlib.pyplot as plt + + +class FootprintTest(unittest.TestCase): + """ + Unit testing class for Footprint calculations, focusing on different satellite configurations + such as beam width, elevation angle, and satellite height. + """ -class FootprintAreaTest(unittest.TestCase): - def setUp(self): - self.fa1 = Footprint(0.1,bore_lat_deg=0,bore_subsat_long_deg=0.0) - self.fa2 = Footprint(0.325,bore_lat_deg = 0) - self.fa3 = Footprint(0.325,elevation_deg = 20) - + """ + Set up the test environment by initializing Footprint instances with different parameters. + """ + # Geostationary (GEO type) satellite height (35786000 m) and different + # beam widths and elevations + self.fa1 = Footprint(0.1, bore_lat_deg=0, bore_subsat_long_deg=0.0) + self.fa2 = Footprint(0.325, bore_lat_deg=0) + self.fa3 = Footprint(0.325, elevation_deg=20) + self.fa4 = Footprint(0.325, elevation_deg=30, sat_height=1200000) + self.fa5 = Footprint(0.325, elevation_deg=30, sat_height=600000) + self.fa6 = Footprint( + 0.325, + sat_height=1200000, + bore_lat_deg=0, + bore_subsat_long_deg=17.744178387, + ) + + """ + Requested tests for Low Earth Orbit (LEO) Satellite at 1200Km and 600Km added bellow (Issue 33). + """ + + # New tests obtained for the LEO satellite type heights (1200km and + # 600km) + # Different satellite heights (1200 km and 600 km) + self.sat_heights = [1200000, 600000] + def test_construction(self): - self.assertEqual(self.fa1.bore_lat_deg,0) - self.assertEqual(self.fa1.bore_subsat_long_deg,0) - self.assertEqual(self.fa1.beam_width_deg,0.1) - self.assertEqual(self.fa1.bore_lat_rad,0) - self.assertEqual(self.fa1.bore_subsat_long_rad,0) - self.assertEqual(self.fa1.beam_width_rad,np.pi/1800) - self.assertEqual(self.fa1.beta,0) - self.assertEqual(self.fa1.bore_tilt,0) - - self.assertEqual(self.fa2.bore_lat_deg,0) - self.assertEqual(self.fa2.bore_subsat_long_deg,0) - self.assertEqual(self.fa2.bore_lat_rad,0) - self.assertEqual(self.fa2.bore_subsat_long_rad,0) - - self.assertEqual(self.fa3.bore_lat_deg,0) - self.assertAlmostEqual(self.fa3.bore_subsat_long_deg,61.84,delta=0.01) - + """ + Test the correct construction of Footprint instances with expected values. + """ + # Verify properties of fa1 + self.assertEqual(self.fa1.bore_lat_deg, 0) + self.assertEqual(self.fa1.bore_subsat_long_deg, 0) + self.assertEqual(self.fa1.beam_width_deg, 0.1) + self.assertEqual(self.fa1.bore_lat_rad, 0) + self.assertEqual(self.fa1.bore_subsat_long_rad, 0) + self.assertEqual(self.fa1.beam_width_rad, np.pi / 1800) + self.assertEqual(self.fa1.beta, 0) + self.assertEqual(self.fa1.bore_tilt, 0) + + # Verify properties of fa2 + self.assertEqual(self.fa2.bore_lat_deg, 0) + self.assertEqual(self.fa2.bore_subsat_long_deg, 0) + self.assertEqual(self.fa2.bore_lat_rad, 0) + self.assertEqual(self.fa2.bore_subsat_long_rad, 0) + + # Verify properties of fa3 + self.assertEqual(self.fa3.bore_lat_deg, 0) + self.assertAlmostEqual( + self.fa3.bore_subsat_long_deg, 61.84, delta=0.01) + + # Verify properties of fa4 + self.assertEqual(self.fa4.bore_lat_deg, 0) + self.assertAlmostEqual( + self.fa4.bore_subsat_long_deg, 13.22, delta=0.01) + + # Verify properties of fa5 + self.assertEqual(self.fa5.bore_lat_deg, 0) + self.assertAlmostEqual(self.fa5.bore_subsat_long_deg, 7.68, delta=0.01) + + self.assertEqual(self.fa6.sat_height, 1200000) + self.assertAlmostEqual(self.fa6.elevation_deg, 20, delta=0.01) + def test_set_elevation(self): + """ + Test the set_elevation method to ensure it correctly updates the elevation angle. + """ self.fa2.set_elevation(20) - self.assertEqual(self.fa2.bore_lat_deg,0) - self.assertAlmostEqual(self.fa2.bore_subsat_long_deg,61.84,delta=0.01) - + self.assertEqual(self.fa2.bore_lat_deg, 0) + self.assertAlmostEqual( + self.fa2.bore_subsat_long_deg, 61.84, delta=0.01) + def test_calc_footprint(self): + """ + Test the calc_footprint method to verify the coordinates of the generated footprint polygon. + """ fp_long, fp_lat = self.fa1.calc_footprint(4) - npt.assert_allclose(fp_long,np.array([0.0, 0.487, -0.487, 0.0]),atol=1e-2) - npt.assert_allclose(fp_lat,np.array([-0.562, 0.281, 0.281, -0.562]),atol=1e-2) - + npt.assert_allclose(fp_long, np.array( + [0.0, 0.487, -0.487, 0.0]), atol=1e-2) + npt.assert_allclose(fp_lat, np.array( + [-0.562, 0.281, 0.281, -0.562]), atol=1e-2) + def test_calc_area(self): + """ + Test the calc_area method to verify the calculation of the footprint area. + """ a1 = self.fa2.calc_area(1000) - self.assertAlmostEqual(a1,130000,delta=200) + self.assertAlmostEqual(a1, 130000, delta=130000 * 0.0025) a2 = self.fa3.calc_area(1000) - self.assertAlmostEqual(a2,486300,delta=200) - + self.assertAlmostEqual(a2, 486300, delta=486300 * 0.0025) + a3 = self.fa4.calc_area(1000) + self.assertAlmostEqual(a3, 810, delta=810 * 0.0025) + a4 = self.fa5.calc_area(1000) + self.assertAlmostEqual(a4, 234, delta=234 * 0.0025) + + for height in self.sat_heights: + beam_deg = 0.325 + footprint = Footprint( + beam_deg, elevation_deg=90, sat_height=height) + cone_radius_in_km = height * np.tan(np.deg2rad(beam_deg)) / 1000 + cone_base_area_in_km2 = np.pi * (cone_radius_in_km**2) + footprint_area_in_km2 = footprint.calc_area(1000) + self.assertAlmostEqual( + footprint_area_in_km2, + cone_base_area_in_km2, + delta=cone_base_area_in_km2 * 0.01, + ) + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_post_processor.py b/tests/test_post_processor.py new file mode 100644 index 000000000..24aa5ca70 --- /dev/null +++ b/tests/test_post_processor.py @@ -0,0 +1,36 @@ + +import unittest + +from sharc.results import Results +from sharc.post_processor import PostProcessor + + +class StationTest(unittest.TestCase): + def setUp(self): + self.post_processor = PostProcessor() + # We just prepare to write beacause Results class is not fully initialized + # before preparing to read or loading from previous results + self.results = Results().prepare_to_write( + None, + True + ) + + def test_generate_and_add_cdf_plots_from_results(self): + self.results.imt_coupling_loss.extend([0, 1, 2, 3, 4, 5]) + self.results.imt_dl_inr.extend([0, 1, 2, 3, 4, 5]) + + trace_legend = "any legendd. Lorem ipsum" + self.post_processor.add_plot_legend_pattern(dir_name_contains="output", legend=trace_legend) + + self.post_processor.add_plots( + self.post_processor.generate_cdf_plots_from_results( + [self.results] + ) + ) + + self.assertEqual(len(self.post_processor.plots), 2) + self.assertEqual(self.post_processor.plots[0].data[0].name, trace_legend) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_propagation_abg.py b/tests/test_propagation_abg.py index 887422176..093191253 100644 --- a/tests/test_propagation_abg.py +++ b/tests/test_propagation_abg.py @@ -13,46 +13,37 @@ from sharc.propagation.propagation_abg import PropagationABG + class PropagationABGTest(unittest.TestCase): def setUp(self): - self.abg = PropagationABG(np.random.RandomState()) + self.abg = PropagationABG( + random_number_gen=np.random.RandomState(), + alpha=3.4, + beta=19.2, + gamma=2.3, + building_loss=20, + shadowing_sigma_dB=6.5, + ) def test_loss(self): - d = np.array([[100, 500],[400, 60]]) - f = 27000 - indoor = np.zeros(d.shape, dtype=bool) - alpha = 3.4 - beta = 19.2 - gamma = 2.3 - shadowing = 0 - loss = np.array ([[120.121, 143.886347],[140.591406, 112.578509]]) - - npt.assert_allclose(self.abg.get_loss(distance_2D = d, - frequency = f, - indoor_stations = indoor, - line_of_sight_prob = 1, - alpha = alpha, - beta = beta, - gamma = gamma, - shadowing = shadowing), - loss,atol=1e-2) - - d = np.array([500, 3000]) - f = np.array([27000, 40000]) - indoor = np.zeros(d.shape, dtype=bool) - alpha = 3.4 - beta = 19.2 - gamma = 2.3 - shadowing = 0 - - loss = np.array ([143.886,174.269]) - npt.assert_allclose(self.abg.get_loss(distance_2D = d, - frequency = f, - indoor_stations = indoor, - line_of_sight_prob = 1, - alpha = alpha, - beta = beta, - gamma = gamma, - shadowing = shadowing), - loss ,atol=1e-2) + d = np.array([[100, 500], [400, 60]]) + f = np.ones(d.shape, dtype=float) * 27000.0 + indoor = np.zeros(d.shape[0], dtype=bool) + shadowing = False + loss = np.array([[120.121, 143.886347], [140.591406, 112.578509]]) + + npt.assert_allclose(self.abg.get_loss(d, f, indoor, shadowing), + loss, atol=1e-2) + + d = np.array([500, 3000])[:, np.newaxis] + f = np.array([27000, 40000])[:, np.newaxis] + indoor = np.zeros(d.shape[0], dtype=bool) + shadowing = False + loss = np.array([143.886, 174.269])[:, np.newaxis] + npt.assert_allclose(self.abg.get_loss(d, f, indoor, shadowing), + loss, atol=1e-2) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_propagation_building_entry_loss.py b/tests/test_propagation_building_entry_loss.py index c8fa6740e..6f10d41fb 100644 --- a/tests/test_propagation_building_entry_loss.py +++ b/tests/test_propagation_building_entry_loss.py @@ -14,7 +14,8 @@ class TestPropagationBuildingEntryLoss(unittest.TestCase): def setUp(self): - self.building_entry_loss = PropagationBuildingEntryLoss(np.random.RandomState()) + self.building_entry_loss = PropagationBuildingEntryLoss( + np.random.RandomState()) def test_building_entry_loss(self): # compare with benchmark from ITU-R P-2109-0 Fig. 1 @@ -25,8 +26,8 @@ def test_building_entry_loss(self): loss_lower = np.array([9, 10, 11, 15, 16, 19, 21]) loss_upper = np.array([10, 12, 15, 17, 18, 21, 25]) - - loss = self.building_entry_loss.get_loss(f_GHz_vec * 1000, 0, prob=.5, test=True) + loss = self.building_entry_loss.get_loss( + f_GHz_vec * 1000, 0, prob=.5, test=True) npt.assert_array_less(loss_lower, loss) npt.assert_array_less(loss, loss_upper) diff --git a/tests/test_propagation_clear_air.py b/tests/test_propagation_clear_air.py index 5061f311a..687009a48 100644 --- a/tests/test_propagation_clear_air.py +++ b/tests/test_propagation_clear_air.py @@ -3,75 +3,59 @@ import unittest import numpy as np -import matplotlib.pyplot as plt -from sharc.parameters.parameters_fss_es import ParametersFssEs - import numpy.testing as npt - +from sharc.parameters.parameters_p452 import ParametersP452 from sharc.propagation.propagation_clear_air_452 import PropagationClearAir + class PropagationClearAirTest(unittest.TestCase): def setUp(self): - self.__ClearAir = PropagationClearAir(np.random.RandomState()) - - def test_loss(self): - - params = ParametersFssEs() - - d = 10000 - f = 27000 - params.atmospheric_pressure = 1013 - params.air_temperature = 288 - params.water_vapour = 7.5 - params.Dlt = 30 - params.Dlr = 10 - params.Dct = 10 - params.Dcr = 10 - - params.Hts = 244 - params.Hrs = 280 - params.Hte = 50 - params.Hre = 50 - - params.theta_tx = 20 - params.theta_rx = 20 + param_p452 = ParametersP452() + # param_p452.atmospheric_pressure = 1013 + # param_p452.air_temperature = 288 + # param_p452.Dct = 10 + # param_p452.Dcr = 10 - params.N0 = 355 - params.delta_N = 60 - params.percentage_p = 40 + # param_p452.Hte = 50 + # param_p452.Hre = 50 - params.omega = 0 - params.phi = 60 - params.dtm = .8 - params.dlm = .8 + # param_p452.N0 = 355 + # param_p452.delta_N = 60 + # param_p452.percentage_p = 40 - params.epsilon = 3.5 + # param_p452.clutter_loss = False - params.hm = 15 - params.Hsr = 45 - params.Hst = 48 + self.prop_clear_air = PropagationClearAir( + np.random.RandomState(), param_p452) - params.H0 = 15 - params.Hn = 17 - - params.thetaJ = 0.3 - params.par_ep = 0.8 - - params.clutter_loss = False - - Gt = 10 - Gr = 10 - - di = [1,1,1] - hi = [2,4,6] - - -# Ld50, Ldbeta, Ldb = self.__Diffraction.get_loss(beta = Beta, distance=d, frequency=f, atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro, delta_N=deltaN, Hrs=hrs, Hts=hts, Hte=hte, Hre=hre, Hsr=hsr, Hst=hst, H0=h0, Hn=hn, dist_di=di, hight_hi=hi, omega=omega, Dlt=dlt ,Dlr=dlr, percentage_p=p) -# -# npt.assert_allclose(158.491,Ldb,atol=1e-3) + def test_loss(self): - #Grafico da perda de difraรงao em funรงao da distancia e da frequencia + # distance between stations in meters + distances = np.ones((1, 1), dtype=float) * 1000 + frequencies = np.ones((1, 1), dtype=float) * 27000 # frequency in MHz + indoor_stations = np.zeros((1, 1), dtype=bool) + # elevation between stations in degrees + elevations = np.zeros((1, 1), dtype=float) + tx_gain = np.ones((1, 1), dtype=float) * 0 + rx_gain = np.ones((1, 1), dtype=float) * 0 + + loss = self.prop_clear_air.get_loss( + distances, + frequencies, + indoor_stations, + elevations, + tx_gain, + rx_gain) + # npt.assert_allclose(158.491, loss, atol=1e-3) + + # Ld50, Ldbeta, Ldb = self.__Diffraction.get_loss(beta = Beta, distance=d, frequency=f, atmospheric_pressure=Ph, + # air_temperature=T, water_vapour=ro, delta_N=deltaN, Hrs=hrs, Hts=hts, Hte=hte, Hre=hre, Hsr=hsr, Hst=hst, H0=h0, + # Hn=hn, dist_di=di, hight_hi=hi, omega=omega, Dlt=dlt ,Dlr=dlr, percentage_p=p) + + # npt.assert_allclose(158.491,Ldb,atol=1e-3) + + # Grafico da perda de difraรงao em funรงao da distancia e da frequencia # data1 = [] # data2 = [] # data3 = [] diff --git a/tests/test_propagation_clutter.py b/tests/test_propagation_clutter.py index 257cd0af0..7b716e55c 100644 --- a/tests/test_propagation_clutter.py +++ b/tests/test_propagation_clutter.py @@ -1,63 +1,63 @@ -# -*- coding: utf-8 -*- +import unittest +import numpy as np +from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss +from sharc.support.enumerations import StationType -""" -Created on Tue Mai 02 15:02:31 2017 -@author: LeticiaValle_Mac -""" +class TestPropagationClutterLoss(unittest.TestCase): + def setUp(self): + self.clutter_loss = PropagationClutterLoss(np.random.RandomState(42)) -import unittest -import numpy as np + def test_spatial_clutter_loss(self): + frequency = np.array([27000]) # MHz + elevation = np.array([0, 45, 90]) + loc_percentage = np.array([0.1, 0.5, 0.9]) + distance = np.array([1000]) # meters, dummy value + loss = self.clutter_loss.get_loss( + distance=distance, + frequency=frequency, + elevation=elevation, + loc_percentage=loc_percentage, + station_type=StationType.FSS_SS + ) -from sharc.propagation.propagation_clutter_loss import PropagationClutterLoss + # Check the shape of the output + self.assertEqual(loss.shape, (3,)) -class PropagationClutterLossTest(unittest.TestCase): + # Check if loss decreases with increasing elevation + self.assertTrue(loss[0] >= loss[1] >= loss[2]) - def setUp(self): - self.__ClutterAtt = PropagationClutterLoss(np.random.RandomState()) - - def test_loss(self): - - f = 27000 #GHz - theta = 0 - per_p =50 - d = 10000 - P = 0.9 - dist = 10 - r = 12.64 - s = 3.72 - t = 0.96 - u = 9.6 - v = 2 - w = 9.1 - x = -3 - y = 4.5 - z = -2 - #npt.assert_allclose(73.150, - # self.__ClutterAtt.get_loss(frequency=f, distance = d,percentage_p = per_p, dist = dist, elevation_angle_facade=theta, probability_loss_notExceeded=P, coeff_r=r, coeff_s=s, coeff_t=t, coeff_u=u,coeff_v=v, coeff_w=w,coeff_x=x,coeff_y=y,coeff_z=z),atol=1e-3) - - -# f = [10,20] #GHz -# d = 10 -# Ph = 1013 -# T = 288 -# ro = 7.5 -# npt.assert_allclose([0.140, 1.088], -# self.__gasAtt.get_loss_Ag(distance=d, frequency=f, atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro),atol=1e-3) - - -# d = [[10, 20, 30],[40, 50, 60]] -# f = 10 -# Ph = 1013 -# T = 288 -# ro = 7.5 -# self.assertTrue(np.all(np.isclose([0.140, 0.280, 0.420],[0.560, 0.700, 0.840], -# self.__gasAtt.get_loss_Ag(distance=d, frequency=f,atmospheric_pressure=Ph, air_temperature=T, water_vapour=ro), atol=1e-3))) -# -# + def test_terrestrial_clutter_loss(self): + frequency = np.array([2000, 6000]) # MHz + distance = np.array([500, 2000]) # meters + # Using a single value for location percentage + loc_percentage = np.array([0.5]) + + loss = self.clutter_loss.get_loss( + frequency=frequency, + distance=distance, + loc_percentage=loc_percentage, + station_type=StationType.IMT_BS + ) + + self.assertEqual(loss.shape, (2,)) + + self.assertTrue(loss[1] >= loss[0]) + + def test_random_loc_percentage(self): + frequency = np.array([4000]) # MHz + distance = np.array([1000]) # meters + + loss = self.clutter_loss.get_loss( + frequency=frequency, + distance=distance, + loc_percentage="RANDOM", + station_type=StationType.IMT_UE + ) + + self.assertTrue(0 <= loss <= 100) if __name__ == '__main__': unittest.main() - diff --git a/tests/test_propagation_free_space.py b/tests/test_propagation_free_space.py index 7eb24fe71..8fc1484f5 100644 --- a/tests/test_propagation_free_space.py +++ b/tests/test_propagation_free_space.py @@ -20,27 +20,27 @@ def setUp(self): def test_loss(self): d = np.array([10]) f = np.array([10]) - loss = self.freeSpace.get_loss(distance_2D=d, frequency=f) + loss = self.freeSpace.get_loss(d, f) ref_loss = np.array([12.45]) npt.assert_allclose(ref_loss, loss, atol=1e-2) - d = np.array([ 10, 100 ]) - f = np.array([ 10, 100 ]) - loss = self.freeSpace.get_loss(distance_2D=d, frequency=f) + d = np.array([10, 100]) + f = np.array([10, 100]) + loss = self.freeSpace.get_loss(d, f) ref_loss = np.array([12.45, 52.45]) npt.assert_allclose(ref_loss, loss, atol=1e-2) - d = np.array([ 10, 100, 1000 ]) - f = np.array([ 10, 100, 1000 ]) - loss = self.freeSpace.get_loss(distance_2D=d, frequency=f) + d = np.array([10, 100, 1000]) + f = np.array([10, 100, 1000]) + loss = self.freeSpace.get_loss(d, f) ref_loss = np.array([12.45, 52.45, 92.45]) npt.assert_allclose(ref_loss, loss, atol=1e-2) d = np.array([[10, 20, 30], [40, 50, 60]]) - f = np.array([ 100 ]) - loss = self.freeSpace.get_loss(distance_2D=d, frequency=f) - ref_loss = np.array([[ 32.45, 38.47, 41.99], - [ 44.49, 46.42, 48.01]]) + f = np.array([100]) + loss = self.freeSpace.get_loss(d, f) + ref_loss = np.array([[32.45, 38.47, 41.99], + [44.49, 46.42, 48.01]]) npt.assert_allclose(ref_loss, loss, atol=1e-2) diff --git a/tests/test_propagation_hdfss_building_side.py b/tests/test_propagation_hdfss_building_side.py index 1ef143040..546076f7e 100644 --- a/tests/test_propagation_hdfss_building_side.py +++ b/tests/test_propagation_hdfss_building_side.py @@ -9,16 +9,17 @@ import numpy as np import numpy.testing as npt -from sharc.parameters.parameters_fss_es import ParametersFssEs +from sharc.parameters.parameters_hdfss import ParametersHDFSS from sharc.support.enumerations import StationType from sharc.propagation.propagation_hdfss_building_side import PropagationHDFSSBuildingSide + class PropagationHDFSSBuildingSideTest(unittest.TestCase): def setUp(self): # Basic propagation rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = False par.shadow_enabled = False par.same_building_enabled = True @@ -26,11 +27,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.5 par.bs_building_entry_loss_value = 50 - self.propagation = PropagationHDFSSBuildingSide(par,rnd) - + self.propagation = PropagationHDFSSBuildingSide(par, rnd) + # Propagation with fixed BEL rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -38,11 +39,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_fixed_value = PropagationHDFSSBuildingSide(par,rnd) - + self.propagation_fixed_value = PropagationHDFSSBuildingSide(par, rnd) + # Propagation with fixed probability rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -50,11 +51,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'P2109_FIXED' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_fixed_prob = PropagationHDFSSBuildingSide(par,rnd) - + self.propagation_fixed_prob = PropagationHDFSSBuildingSide(par, rnd) + # Propagation with random probability rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -62,20 +63,20 @@ def setUp(self): par.bs_building_entry_loss_type = 'P2109_RANDOM' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_random_prob = PropagationHDFSSBuildingSide(par,rnd) - + self.propagation_random_prob = PropagationHDFSSBuildingSide(par, rnd) + def test_get_loss(self): # On same building d = np.array([[10.0, 80.0, 200.0]]) - f = 40000*np.ones_like(d) + f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) es_x = np.array([0.0]) es_y = np.array([25.0]) es_z = np.array([10.0]) - imt_x = np.array([ 0.0, 0.0, -200.0]) - imt_y = np.array([15.0, 80.0, 25.0]) - imt_z = np.array([ 1.5, 6.0, 7.5]) - + imt_x = np.array([0.0, 0.0, -200.0]) + imt_y = np.array([15.0, 80.0, 25.0]) + imt_z = np.array([1.5, 6.0, 7.5]) + loss = self.propagation.get_loss(distance_3D=d, frequency=f, elevation=ele, @@ -87,83 +88,83 @@ def test_get_loss(self): es_y=es_y, es_z=es_z) loss = loss[0] - + expected_loss = np.array([[84.48, 103.35, 140.05]]) - - npt.assert_allclose(loss,expected_loss,atol=1e-1) - + + npt.assert_allclose(loss, expected_loss, atol=1e-1) + def test_get_build_loss(self): # Initialize variables - ele = np.array([[ 0.0, 45.0, 90.0]]) - f = 40000*np.ones_like(ele) + ele = np.array([[0.0, 45.0, 90.0]]) + f = 40000 * np.ones_like(ele) sta_type = StationType.IMT_BS - + # Test 1: fixed value expected_build_loss = 50.0 build_loss = self.propagation_fixed_value.get_building_loss(sta_type, f, ele) - self.assertEqual(build_loss,expected_build_loss) - + self.assertEqual(build_loss, expected_build_loss) + # Test 2: fixed probability expected_build_loss = np.array([[24.4, 33.9, 43.4]]) build_loss = self.propagation_fixed_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + # Test 3: random probability expected_build_loss = np.array([[21.7, 32.9, 15.9]]) build_loss = self.propagation_random_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + # Test 4: UE station sta_type = StationType.IMT_UE expected_build_loss = np.array([[21.7, 32.9, 15.9]]) build_loss = self.propagation_fixed_value.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) build_loss = self.propagation_fixed_prob.get_building_loss(sta_type, - f, - ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) + f, + ele) + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) expected_build_loss = np.array([[10.1, 36.8, 52.6]]) build_loss = self.propagation_random_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + def test_is_same_build(self): # Test is_same_building() es_x = np.array([0.0]) es_y = np.array([25.0]) - imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0]) - imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6]) - - expected_in_build = np.array([True,False,False,False,True]) + imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0]) + imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6]) + + expected_in_build = np.array([True, False, False, False, True]) in_build = self.propagation.is_same_building(imt_x, imt_y, es_x, es_y) - npt.assert_array_equal(in_build,expected_in_build) - + npt.assert_array_equal(in_build, expected_in_build) + def test_is_next_build(self): # Test is_same_building() es_x = np.array([0.0]) es_y = np.array([25.0]) - imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0]) - imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6]) + 80.0 - - expected_in_build = np.array([True,False,False,False,True]) + imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0]) + imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6]) + 80.0 + + expected_in_build = np.array([True, False, False, False, True]) in_build = self.propagation.is_next_building(imt_x, imt_y, es_x, es_y) - npt.assert_array_equal(in_build,expected_in_build) - - + npt.assert_array_equal(in_build, expected_in_build) + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_propagation_hdfss_roof_top.py b/tests/test_propagation_hdfss_roof_top.py index 5a92432da..15d9fa03a 100644 --- a/tests/test_propagation_hdfss_roof_top.py +++ b/tests/test_propagation_hdfss_roof_top.py @@ -9,15 +9,16 @@ import numpy as np import numpy.testing as npt -from sharc.parameters.parameters_fss_es import ParametersFssEs +from sharc.parameters.parameters_hdfss import ParametersHDFSS from sharc.support.enumerations import StationType from sharc.propagation.propagation_hdfss_roof_top import PropagationHDFSSRoofTop + class PropagationHDFSSRoofTopTest(unittest.TestCase): def setUp(self): rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = False par.shadow_enabled = False par.same_building_enabled = True @@ -25,11 +26,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.5 par.bs_building_entry_loss_value = 50 - self.propagation = PropagationHDFSSRoofTop(par,rnd) - + self.propagation = PropagationHDFSSRoofTop(par, rnd) + # Propagation with fixed BEL rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -37,11 +38,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_fixed_value = PropagationHDFSSRoofTop(par,rnd) - + self.propagation_fixed_value = PropagationHDFSSRoofTop(par, rnd) + # Propagation with fixed probability rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -49,11 +50,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'P2109_FIXED' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_fixed_prob = PropagationHDFSSRoofTop(par,rnd) - + self.propagation_fixed_prob = PropagationHDFSSRoofTop(par, rnd) + # Propagation with random probability rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = True par.shadow_enabled = False par.same_building_enabled = True @@ -61,11 +62,11 @@ def setUp(self): par.bs_building_entry_loss_type = 'P2109_RANDOM' par.bs_building_entry_loss_prob = 0.6 par.bs_building_entry_loss_value = 50 - self.propagation_random_prob = PropagationHDFSSRoofTop(par,rnd) - + self.propagation_random_prob = PropagationHDFSSRoofTop(par, rnd) + # Same building disabled rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = False par.shadow_enabled = False par.same_building_enabled = False @@ -73,11 +74,12 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.5 par.bs_building_entry_loss_value = 50 - self.propagation_same_build_disabled = PropagationHDFSSRoofTop(par,rnd) - + self.propagation_same_build_disabled = PropagationHDFSSRoofTop( + par, rnd) + # Diffraction loss enabled rnd = np.random.RandomState(101) - par = ParametersFssEs() + par = ParametersHDFSS() par.building_loss_enabled = False par.shadow_enabled = False par.same_building_enabled = True @@ -85,41 +87,42 @@ def setUp(self): par.bs_building_entry_loss_type = 'FIXED_VALUE' par.bs_building_entry_loss_prob = 0.5 par.bs_building_entry_loss_value = 50 - self.propagation_diff_enabled = PropagationHDFSSRoofTop(par,rnd) - + self.propagation_diff_enabled = PropagationHDFSSRoofTop(par, rnd) + def test_get_loss(self): # Not on same building d = np.array([[10.0, 20.0, 30.0, 60.0, 90.0, 300.0, 1000.0]]) - f = 40000*np.ones_like(d) + f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) - + loss = self.propagation.get_loss(distance_3D=d, frequency=f, elevation=ele, imt_sta_type=StationType.IMT_BS, - imt_x = 100.0*np.ones(7), - imt_y = 100.0*np.ones(7), - imt_z = 100.0*np.ones(7), - es_x = np.array([0.0]), - es_y = np.array([0.0]), - es_z = np.array([0.0])) + imt_x=100.0 * np.ones(7), + imt_y=100.0 * np.ones(7), + imt_z=100.0 * np.ones(7), + es_x=np.array([0.0]), + es_y=np.array([0.0]), + es_z=np.array([0.0])) loss = loss[0] - - expected_loss = np.array([[84.48, 90.50, 94.02, 100.72, 104.75, 139.33, 162.28]]) - - npt.assert_allclose(loss,expected_loss,atol=1e-1) - + + expected_loss = np.array( + [[84.48, 90.50, 94.02, 100.72, 104.75, 139.33, 162.28]]) + + npt.assert_allclose(loss, expected_loss, atol=1e-1) + # On same building d = np.array([[10.0, 20.0, 30.0]]) - f = 40000*np.ones_like(d) + f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) es_x = np.array([0.0]) es_y = np.array([0.0]) es_z = np.array([10.0]) - imt_x = np.array([ 0.0, 20.0, 30.0]) - imt_y = np.array([10.0, 0.0, 0.0]) - imt_z = np.array([ 1.5, 6.0, 7.5]) - + imt_x = np.array([0.0, 20.0, 30.0]) + imt_y = np.array([10.0, 0.0, 0.0]) + imt_z = np.array([1.5, 6.0, 7.5]) + loss = self.propagation.get_loss(distance_3D=d, frequency=f, elevation=ele, @@ -131,77 +134,77 @@ def test_get_loss(self): es_y=es_y, es_z=es_z) loss = loss[0] - + expected_loss = np.array([[150 + 84.48, 100 + 90.50, 50 + 94.02]]) - - npt.assert_allclose(loss,expected_loss,atol=1e-1) - + + npt.assert_allclose(loss, expected_loss, atol=1e-1) + def test_get_build_loss(self): # Initialize variables - ele = np.array([[ 0.0, 45.0, 90.0]]) - f = 40000*np.ones_like(ele) + ele = np.array([[0.0, 45.0, 90.0]]) + f = 40000 * np.ones_like(ele) sta_type = StationType.IMT_BS - + # Test 1: fixed value expected_build_loss = 50.0 build_loss = self.propagation_fixed_value.get_building_loss(sta_type, f, ele) - self.assertEqual(build_loss,expected_build_loss) - + self.assertEqual(build_loss, expected_build_loss) + # Test 2: fixed probability expected_build_loss = np.array([[24.4, 33.9, 43.4]]) build_loss = self.propagation_fixed_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + # Test 3: random probability expected_build_loss = np.array([[21.7, 32.9, 15.9]]) build_loss = self.propagation_random_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + # Test 4: UE station sta_type = StationType.IMT_UE expected_build_loss = np.array([[21.7, 32.9, 15.9]]) build_loss = self.propagation_fixed_value.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) build_loss = self.propagation_fixed_prob.get_building_loss(sta_type, - f, - ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) + f, + ele) + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) expected_build_loss = np.array([[10.1, 36.8, 52.6]]) build_loss = self.propagation_random_prob.get_building_loss(sta_type, f, ele) - npt.assert_allclose(build_loss,expected_build_loss,atol=1e-1) - + npt.assert_allclose(build_loss, expected_build_loss, atol=1e-1) + def test_same_building(self): # Test is_same_building() es_x = np.array([0.0]) es_y = np.array([0.0]) es_z = np.array([19.0]) - imt_x = np.array([1.0, 0.0,80.0,-70.0,12.0]) - imt_y = np.array([1.0,30.0, 0.0,-29.3,-3.6]) - imt_z = 3*np.ones_like(imt_x) - - expected_in_build = np.array([True,False,False,False,True]) + imt_x = np.array([1.0, 0.0, 80.0, -70.0, 12.0]) + imt_y = np.array([1.0, 30.0, 0.0, -29.3, -3.6]) + imt_z = 3 * np.ones_like(imt_x) + + expected_in_build = np.array([True, False, False, False, True]) in_build = self.propagation_same_build_disabled.is_same_building(imt_x, - imt_y, - es_x, - es_y) - npt.assert_array_equal(in_build,expected_in_build) - + imt_y, + es_x, + es_y) + npt.assert_array_equal(in_build, expected_in_build) + # Test loss - d = np.sqrt(np.power(imt_x,2) + np.power(imt_y,2)) + d = np.sqrt(np.power(imt_x, 2) + np.power(imt_y, 2)) d = np.array([list(d)]) - f = 40000*np.ones_like(d) + f = 40000 * np.ones_like(d) ele = np.transpose(np.zeros_like(d)) - + loss = self.propagation_same_build_disabled.get_loss(distance_3D=d, frequency=f, elevation=ele, @@ -213,54 +216,54 @@ def test_same_building(self): es_y=es_y, es_z=es_z) loss = loss[0] - expected_loss = np.array([[4067.5,94.0,103.6,103.1,4086.5]]) - - npt.assert_allclose(loss,expected_loss,atol=1e-1) - + expected_loss = np.array([[4067.5, 94.0, 103.6, 103.1, 4086.5]]) + + npt.assert_allclose(loss, expected_loss, atol=1e-1) + def test_get_diff_distances(self): es_x = np.array([10.0]) es_y = np.array([15.0]) es_z = np.array([19.0]) - imt_x = np.array([ 80.0, 50.0, 10.0,-80.0, 0.0]) - imt_y = np.array([ 15.0, 55.0, 95.0, 15.0,-40.0]) - imt_z = np.array([ 1.5, 3.0, 6.0, 7.5, 20.5]) - + imt_x = np.array([80.0, 50.0, 10.0, -80.0, 0.0]) + imt_y = np.array([15.0, 55.0, 95.0, 15.0, -40.0]) + imt_z = np.array([1.5, 3.0, 6.0, 7.5, 20.5]) + # 2D distances distances = self.propagation.get_diff_distances(imt_x, - imt_y, - imt_z, - es_x, - es_y, - es_z, + imt_y, + imt_z, + es_x, + es_y, + es_z, dist_2D=True) - expected_distances = (np.array([60.0,35.4,25.0,60.0,25.4]), - np.array([10.0,21.2,55.0,30.0,30.5])) - npt.assert_allclose(distances,expected_distances,atol=1e-1) - + expected_distances = (np.array([60.0, 35.4, 25.0, 60.0, 25.4]), + np.array([10.0, 21.2, 55.0, 30.0, 30.5])) + npt.assert_allclose(distances, expected_distances, atol=1e-1) + # 3D distances distances = self.propagation.get_diff_distances(imt_x, - imt_y, - imt_z, - es_x, - es_y, + imt_y, + imt_z, + es_x, + es_y, es_z) - expected_distances = (np.array([ 14.0, 9.0, 3.0, 6.7, -1.7]), - np.array([ 60.0, 35.4, 25.0, 60.0, 25.4]), - np.array([ 19.3, 25.9, 56.3, 31.8, 30.6])) - npt.assert_allclose(distances,expected_distances,atol=1e-1) - + expected_distances = (np.array([14.0, 9.0, 3.0, 6.7, -1.7]), + np.array([60.0, 35.4, 25.0, 60.0, 25.4]), + np.array([19.3, 25.9, 56.3, 31.8, 30.6])) + npt.assert_allclose(distances, expected_distances, atol=1e-1) + def test_diffration_loss(self): # Test diffraction loss - h = np.array([7.64,-0.56,-1.2,-0.1]) - d1 = np.array([34.99,1060.15,5.0,120.0]) - d2 = np.array([25.02,25.02,2.33,245.0]) - f = 40000*np.ones_like(h) - - loss = self.propagation_diff_enabled.get_diffraction_loss(h,d1,d2,f) - expected_loss = np.array([43.17,0.0,0.0,4.48]) - - npt.assert_allclose(loss,expected_loss,atol=1e-1) - - + h = np.array([7.64, -0.56, -1.2, -0.1]) + d1 = np.array([34.99, 1060.15, 5.0, 120.0]) + d2 = np.array([25.02, 25.02, 2.33, 245.0]) + f = 40000 * np.ones_like(h) + + loss = self.propagation_diff_enabled.get_diffraction_loss(h, d1, d2, f) + expected_loss = np.array([43.17, 0.0, 0.0, 4.48]) + + npt.assert_allclose(loss, expected_loss, atol=1e-1) + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_propagation_indoor.py b/tests/test_propagation_indoor.py index 8740ef1e5..f77b8b36c 100644 --- a/tests/test_propagation_indoor.py +++ b/tests/test_propagation_indoor.py @@ -6,43 +6,49 @@ """ import unittest -import numpy as np -import numpy.testing as npt +# import numpy as np -from sharc.propagation.propagation_indoor import PropagationIndoor -from sharc.parameters.parameters_indoor import ParametersIndoor +# from sharc.propagation.propagation_indoor import PropagationIndoor +# from sharc.parameters.parameters_indoor import ParametersIndoor class PropagationIndoorTest(unittest.TestCase): + """Tests PropagationIndoor class + """ def setUp(self): pass def test_loss(self): - params = ParametersIndoor() - params.basic_path_loss = "INH_OFFICE" - params.n_rows = 3 - params.n_colums = 1 - # params.street_width = 30 - params.ue_indoor_percent = .95 - params.building_class = "TRADITIONAL" - params.num_cells = 3 - - bs_per_building = 3 - ue_per_bs = 3 - - num_bs = bs_per_building*params.n_rows*params.n_colums - num_ue = num_bs*ue_per_bs - distance_2D = 150*np.random.random((num_bs, num_ue)) - frequency = 27000*np.ones(distance_2D.shape) - indoor = np.random.rand(num_bs) < params.ue_indoor_percent - h_bs = 3*np.ones(num_bs) - h_ue = 1.5*np.ones(num_ue) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1)) - elevation = np.degrees(np.arctan(height_diff/distance_2D)) - - propagation_indoor = PropagationIndoor(np.random.RandomState(), params, ue_per_bs) + """Tests the get_loss method + """ + pass + # params = ParametersIndoor() + # params.basic_path_loss = "INH_OFFICE" + # params.n_rows = 3 + # params.n_colums = 1 + # # params.street_width = 30 + # params.ue_indoor_percent = .95 + # params.building_class = "TRADITIONAL" + # params.num_cells = 3 + + # bs_per_building = 3 + # ue_per_bs = 3 + + # num_bs = bs_per_building * params.n_rows * params.n_colums + # num_ue = num_bs * ue_per_bs + # distance_2D = 150 * np.random.random((num_bs, num_ue)) + # frequency = 27000 * np.ones(distance_2D.shape) + # indoor = np.random.rand(num_bs) < params.ue_indoor_percent + # h_bs = 3 * np.ones(num_bs) + # h_ue = 1.5 * np.ones(num_ue) + # distance_3D = np.sqrt(distance_2D**2 + (h_bs[:, np.newaxis] - h_ue)**2) + # height_diff = np.tile(h_bs, (num_bs, 3)) - np.tile(h_ue, (num_bs, 1)) + # elevation = np.degrees(np.arctan(height_diff / distance_2D)) + + # propagation_indoor = PropagationIndoor( + # np.random.RandomState(), params, ue_per_bs) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_propagation_sat_simple.py b/tests/test_propagation_sat_simple.py index 715d71c7f..fcb8e7315 100644 --- a/tests/test_propagation_sat_simple.py +++ b/tests/test_propagation_sat_simple.py @@ -15,64 +15,69 @@ class PropagationSatSimpleTest(unittest.TestCase): def setUp(self): - self.propagation = PropagationSatSimple(np.random.RandomState()) + self.propagation = PropagationSatSimple( + random_number_gen=np.random.RandomState(), + enable_clutter_loss=False) def test_loss(self): d = np.array(10) f = np.array(10) - loc_percentage = 0 - elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)} + elevation = { + 'free_space': 90 * + np.ones( + d.shape), + 'apparent': 90 * + np.ones( + d.shape)} indoor_stations = np.zeros(d.shape, dtype=bool) self.assertAlmostEqual(13.19, - self.propagation.get_loss(distance_3D=d, - frequency=f, - loc_percentage=loc_percentage, - indoor_stations=indoor_stations, - elevation=elevation, - enable_clutter_loss=False), - delta = 1e-2) + self.propagation.get_loss( + d, f, indoor_stations, elevation), + delta=1e-2) - d = np.array([ 10, 100 ]) - f = np.array([ 10, 100 ]) - loc_percentage = 0 - elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)} + d = np.array([10, 100]) + f = np.array([10, 100]) + elevation = { + 'free_space': 90 * + np.ones( + d.shape), + 'apparent': 90 * + np.ones( + d.shape)} indoor_stations = np.zeros(d.shape, dtype=bool) npt.assert_allclose([13.2, 53.2], - self.propagation.get_loss(distance_3D=d, - frequency=f, - loc_percentage=loc_percentage, - indoor_stations=indoor_stations, - elevation=elevation, - enable_clutter_loss=False), + self.propagation.get_loss( + d, f, indoor_stations, elevation), atol=1e-2) - d = np.array([ 10, 100, 1000 ]) - f = np.array([ 10, 100, 1000 ]) - loc_percentage = 0 - elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)} + d = np.array([10, 100, 1000]) + f = np.array([10, 100, 1000]) + elevation = { + 'free_space': 90 * + np.ones( + d.shape), + 'apparent': 90 * + np.ones( + d.shape)} indoor_stations = np.array([0, 0, 0], dtype=bool) npt.assert_allclose([13.2, 53.2, 93.2], - self.propagation.get_loss(distance_3D=d, - frequency=f, - loc_percentage=loc_percentage, - indoor_stations=indoor_stations, - elevation=elevation, - enable_clutter_loss=False), + self.propagation.get_loss( + d, f, indoor_stations, elevation), atol=1e-2) - d = np.array([[10, 20, 30],[40, 50, 60]]) - f = np.array([ 100 ]) - loc_percentage = 0 - elevation = {'free_space': 90*np.ones(d.shape), 'apparent': 90*np.ones(d.shape)} + d = np.array([[10, 20, 30], [40, 50, 60]]) + f = np.array([100]) + elevation = { + 'free_space': 90 * + np.ones( + d.shape), + 'apparent': 90 * + np.ones( + d.shape)} indoor_stations = np.zeros(d.shape, dtype=bool) - ref_loss = [[ 33.2, 39.2, 42.7], - [ 45.2, 47.1, 48.7]] - loss = self.propagation.get_loss(distance_3D=d, - frequency=f, - loc_percentage=loc_percentage, - indoor_stations=indoor_stations, - elevation=elevation, - enable_clutter_loss=False) + ref_loss = [[33.2, 39.2, 42.7], + [45.2, 47.1, 48.7]] + loss = self.propagation.get_loss(d, f, indoor_stations, elevation) npt.assert_allclose(ref_loss, loss, atol=1e-1) diff --git a/tests/test_propagation_uma.py b/tests/test_propagation_uma.py index 2132ece60..40ff78084 100644 --- a/tests/test_propagation_uma.py +++ b/tests/test_propagation_uma.py @@ -11,6 +11,7 @@ from sharc.propagation.propagation_uma import PropagationUMa + class PropagationUMaTest(unittest.TestCase): def setUp(self): @@ -20,88 +21,97 @@ def test_los_probability(self): distance_2D = np.array([[10, 15, 40], [17, 60, 80]]) h_ue = np.array([1.5, 8, 15]) - los_probability = np.array([[1, 1, 0.74], + los_probability = np.array([[1, 1, 0.74], [1, 0.57, 0.45]]) npt.assert_allclose(self.uma.get_los_probability(distance_2D, h_ue), los_probability, atol=1e-2) - def test_breakpoint_distance(self): h_bs = np.array([15, 20, 25, 30]) h_ue = np.array([3, 4]) - h_e = np.ones((len(h_bs), len(h_ue))) - frequency = 30000*np.ones((len(h_bs), len(h_ue))) - breakpoint_distance = np.array([[ 11200, 16800], - [ 15200, 22800], - [ 19200, 28800], - [ 23200, 34800]]) + h_e = np.ones((h_ue.size, h_bs.size)) + frequency = 30000 * np.ones(h_e.shape) + breakpoint_distance = np.array([[11200, 15200, 19200, 23200], + [16800, 22800, 28800, 34800]]) npt.assert_array_equal(self.uma.get_breakpoint_distance(frequency, h_bs, h_ue, h_e), breakpoint_distance) - def test_loss_los(self): - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 30000*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[102.32, 108.09, 111.56, 114.05], - [115.99, 117.56, 118.90, 120.06]]) + loss = np.array([[102.32, 115.99], + [108.09, 117.56], + [111.56, 118.90], + [114.05, 120.06]]) npt.assert_allclose(self.uma.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 300*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[62.32, 68.09, 71.56, 74.05], - [87.06, 84.39, 83.57, 83.40]]) + loss = np.array([[62.32, 87.06], + [68.09, 84.39], + [71.56, 83.57], + [74.05, 83.40]]) npt.assert_allclose(self.uma.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - def test_loss_nlos(self): - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 30000*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[121.58, 132.25, 138.45, 142.70], - [148.29, 150.77, 152.78, 154.44]]) + loss = np.array([[121.58, 148.29], + [132.25, 150.77], + [138.45, 152.78], + [142.70, 154.44]]) npt.assert_allclose(self.uma.get_loss_nlos(distance_2D, distance_3D, frequency, - h_bs, h_ue, h_e, shadowing_std), + h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - - distance_2D = np.array([[1000, 2000, 5000, 4000], - [3000, 6000, 7000, 8000]]) + distance_2D = np.array([[1000, 3000], + [2000, 6000], + [5000, 7000], + [4000, 8000]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 300*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[120.02, 131.18, 146.13, 141.75], - [138.66, 149.83, 151.84, 153.51]]) + loss = np.array([[120.02, 138.66], + [131.18, 149.83], + [146.13, 151.84], + [141.75, 153.51]]) npt.assert_allclose(self.uma.get_loss_nlos(distance_2D, distance_3D, frequency, - h_bs, h_ue, h_e, shadowing_std), + h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) diff --git a/tests/test_propagation_umi.py b/tests/test_propagation_umi.py index fb3ac1371..2eadd31f2 100644 --- a/tests/test_propagation_umi.py +++ b/tests/test_propagation_umi.py @@ -12,98 +12,110 @@ from sharc.propagation.propagation_umi import PropagationUMi + class PropagationUMiTest(unittest.TestCase): def setUp(self): los_adjustment_factor = 18 - self.umi = PropagationUMi(np.random.RandomState(), los_adjustment_factor) + self.umi = PropagationUMi( + np.random.RandomState(), + los_adjustment_factor) def test_los_probability(self): distance_2D = np.array([[10, 15, 40], [17, 60, 80]]) - los_probability = np.array([[1, 1, 0.631], + los_probability = np.array([[1, 1, 0.631], [1, 0.432, 0.308]]) npt.assert_allclose(self.umi.get_los_probability(distance_2D, - self.umi.los_adjustment_factor), + self.umi.los_adjustment_factor), los_probability, atol=1e-2) - def test_breakpoint_distance(self): h_bs = np.array([15, 20, 25, 30]) h_ue = np.array([3, 4]) - h_e = np.ones((len(h_bs), len(h_ue))) - frequency = 30000*np.ones((len(h_bs), len(h_ue))) - breakpoint_distance = np.array([[ 11200, 16800], - [ 15200, 22800], - [ 19200, 28800], - [ 23200, 34800]]) + h_e = np.ones((h_ue.size, h_bs.size)) + frequency = 30000 * np.ones(h_e.shape) + breakpoint_distance = np.array([[11200, 15200, 19200, 23200], + [16800, 22800, 28800, 34800]]) npt.assert_array_equal(self.umi.get_breakpoint_distance(frequency, h_bs, h_ue, h_e), breakpoint_distance) - def test_loss_los(self): - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 30000*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[104.336, 110.396, 114.046, 116.653], - [118.690, 120.346, 121.748, 122.963]]) + loss = np.array([[104.336, 118.690], + [110.396, 120.346], + [114.046, 121.748], + [116.653, 122.963]]) npt.assert_allclose(self.umi.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 300*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[64.336, 70.396, 74.046, 76.653], - [89.215, 86.829, 86.187, 86.139]]) + loss = np.array([[64.336, 89.215], + [70.396, 86.829], + [74.046, 86.187], + [76.653, 86.139]]) npt.assert_allclose(self.umi.get_loss_los(distance_2D, distance_3D, frequency, h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - def test_loss_nlos(self): - distance_2D = np.array([[100, 200, 300, 400], - [500, 600, 700, 800]]) + distance_2D = np.array([[100, 500], + [200, 600], + [300, 700], + [400, 800]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 30000*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 30000 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[128.84, 138.72, 144.56, 148.64], - [152.96, 155.45, 157.50, 159.25]]) + loss = np.array([[128.84, 152.96], + [138.72, 155.45], + [144.56, 157.50], + [148.64, 159.25]]) npt.assert_allclose(self.umi.get_loss_nlos(distance_2D, distance_3D, frequency, - h_bs, h_ue, h_e, shadowing_std), + h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) - - distance_2D = np.array([[1000, 2000, 5000, 4000], - [3000, 6000, 7000, 8000]]) + distance_2D = np.array([[1000, 3000], + [2000, 6000], + [5000, 7000], + [4000, 8000]]) h_bs = np.array([30, 35]) h_ue = np.array([2, 3, 4, 5]) h_e = np.ones(distance_2D.shape) - distance_3D = np.sqrt(distance_2D**2 + (h_bs[:,np.newaxis] - h_ue)**2) - frequency = 300*np.ones(distance_2D.shape) + distance_3D = np.sqrt(distance_2D**2 + (h_bs - h_ue[:, np.newaxis])**2) + frequency = 300 * np.ones(distance_2D.shape) shadowing_std = 0 - loss = np.array([[ 120.96 , 131.29, 145.03, 141.31], - [ 137.80 , 148.13 , 150.19, 151.94]]) + loss = np.array([[120.96, 137.80], + [131.29, 148.13], + [145.03, 150.19], + [141.31, 151.94]]) npt.assert_allclose(self.umi.get_loss_nlos(distance_2D, distance_3D, frequency, - h_bs, h_ue, h_e, shadowing_std), + h_bs, h_ue, h_e, shadowing_std), loss, atol=1e-2) diff --git a/tests/test_results.py b/tests/test_results.py new file mode 100644 index 000000000..94b004298 --- /dev/null +++ b/tests/test_results.py @@ -0,0 +1,87 @@ +import unittest + +from sharc.results import Results + + +class StationTest(unittest.TestCase): + def setUp(self): + self.results = Results().prepare_to_write( + None, + True, + output_dir="output", + output_dir_prefix="out" + ) + + def test_flush_to_and_load_from_file(self): + arr1 = [1., 2., 3., 4., 5., 6., 7., 8., 9., 100.] + self.results.imt_coupling_loss.extend(arr1) + self.assertGreater(len(self.results.imt_coupling_loss), 0) + # Results should flush + self.results.write_files(1) + # check that no results are left in arr + self.assertEqual(len(self.results.imt_coupling_loss), 0) + + arr2 = [101., 102., 103., 104., 105., 106., 107., 108., 109.] + self.results.imt_coupling_loss.extend(arr2) + self.assertGreater(len(self.results.imt_coupling_loss), 0) + self.results.write_files(2) + # check that no results are left in arr + self.assertEqual(len(self.results.imt_coupling_loss), 0) + + results_recuperated_from_file = Results().load_from_dir(self.results.output_directory) + + results_arr = arr1 + results_arr.extend(arr2) + + self.assertEqual(results_recuperated_from_file.imt_coupling_loss, results_arr) + + def test_get_most_recent_dirs(self): + dir_2024_01_01_04 = "caminho_abs/prefixo_2024-01-01_04" + dir_2024_01_01_10 = "caminho_abs/prefixo_2024-01-01_10" + + dir_2024_01_02_01 = "caminho_abs/prefixo_2024-01-02_01" + dir_2024_10_01_01 = "caminho_abs/prefixo_2024-10-01_01" + another_dir = "caminho_abs/prefixo2_2024-10-01_01" + + dirs = self.results.get_most_recent_outputs_for_each_prefix([ + dir_2024_01_01_04, + dir_2024_01_01_10, + dir_2024_01_02_01, + dir_2024_10_01_01, + another_dir, + ]) + + self.assertEqual(len(dirs), 2) + + self.assertIn(dir_2024_10_01_01, dirs) + self.assertIn(another_dir, dirs) + + dirs = self.results.get_most_recent_outputs_for_each_prefix([ + dir_2024_01_01_04, + dir_2024_01_01_10, + dir_2024_01_02_01, + ]) + + self.assertEqual(len(dirs), 1) + + self.assertIn(dir_2024_01_02_01, dirs) + + dirs = self.results.get_most_recent_outputs_for_each_prefix([ + dir_2024_01_01_04, + dir_2024_01_01_10, + ]) + + self.assertEqual(len(dirs), 1) + + self.assertIn(dir_2024_01_01_10, dirs) + + def test_get_prefix_date_and_id(self): + dir_2024_01_01_04 = "caminho_abs/prefixo_2024-01-01_04" + prefix, date, id = Results.get_prefix_date_and_id(dir_2024_01_01_04) + self.assertEqual(prefix, "caminho_abs/prefixo_") + self.assertEqual(date, "2024-01-01") + self.assertEqual(id, "04") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_scintillation.py b/tests/test_scintillation.py index cf7566c8a..1bf1c52a8 100644 --- a/tests/test_scintillation.py +++ b/tests/test_scintillation.py @@ -22,8 +22,10 @@ def test_tropo_scintillation_attenuation(self): frequency_MHz = 30000. wet_refractivity = 42.5 - elevation_vec = np.array([5., 10., 20., 90., 35., 5., 10., 20., 35., 90.]) - percentage_gain_exceeded = np.array([.01, .1, 1, 3, 10, 90, 98, 99, 99.9, 99.99]) + elevation_vec = np.array( + [5., 10., 20., 90., 35., 5., 10., 20., 35., 90.]) + percentage_gain_exceeded = np.array( + [.01, .1, 1, 3, 10, 90, 98, 99, 99.9, 99.99]) attenuation_lower = [5, 1, .5, .1, .1, 1, 1, .5, .4, .3] attenuation_upper = [7, 2, .6, .2, .2, 2, 2, .7, .6, .5] sign = [-1, -1, -1, -1, -1, +1, +1, +1, +1, +1] diff --git a/tests/test_simulation_downlink.py b/tests/test_simulation_downlink.py index b590216ee..468948ee1 100644 --- a/tests/test_simulation_downlink.py +++ b/tests/test_simulation_downlink.py @@ -15,6 +15,9 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology +from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS + class SimulationDownlinkTest(unittest.TestCase): @@ -27,88 +30,92 @@ def setUp(self): self.param.general.enable_adjacent_channel = False self.param.general.overwrite_output = True - self.param.imt.topology = "SINGLE_BS" - self.param.imt.wrap_around = False - self.param.imt.num_clusters = 2 - self.param.imt.intersite_distance = 150 + self.param.imt.topology = ParametersImtTopology( + type="SINGLE_BS", + single_bs=ParametersSingleBS( + num_clusters=2, + intersite_distance=150, + cell_radius=2 * 150 / 3 + ) + ) self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False - self.param.imt.frequency = 10000 + self.param.imt.frequency = 10000.0 self.param.imt.bandwidth = 100 self.param.imt.rb_bandwidth = 0.180 self.param.imt.spectral_mask = "IMT-2020" self.param.imt.spurious_emissions = -13 self.param.imt.guard_band_ratio = 0.1 self.param.imt.ho_margin = 3 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 10 - self.param.imt.bs_height = 6 - self.param.imt.bs_acs = 30 - self.param.imt.bs_noise_figure = 7 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 2 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 0 - self.param.imt.ue_distribution_distance = "RAYLEIGH" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 0.8 - self.param.imt.ue_p_cmax = 20 - self.param.imt.ue_conducted_power = 10 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_acs = 25 - self.param.imt.ue_noise_figure = 9 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 10 + self.param.imt.bs.height = 6 + self.param.imt.bs.acs = 30 + self.param.imt.bs.noise_figure = 7 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 2 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 0 + self.param.imt.ue.distribution_distance = "RAYLEIGH" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 0.8 + self.param.imt.ue.p_cmax = 20 + self.param.imt.ue.conducted_power = 10 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.acs = 25 + self.param.imt.ue.noise_figure = 9 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" - self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL) + # probability of line-of-sight (not for FSPL) + self.param.imt.line_of_sight_prob = 0.75 self.param.imt.shadowing = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.bs_element_pattern = "M2101" - self.param.antenna_imt.bs_normalization_file = None - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_element_max_g = 10 - self.param.antenna_imt.bs_element_phi_3db = 80 - self.param.antenna_imt.bs_element_theta_3db = 80 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 16 - self.param.antenna_imt.bs_n_columns = 16 - self.param.antenna_imt.bs_element_horiz_spacing = 1 - self.param.antenna_imt.bs_element_vert_spacing = 1 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 10 - - self.param.antenna_imt.ue_element_pattern = "M2101" - self.param.antenna_imt.ue_normalization = False - self.param.antenna_imt.ue_normalization_file = None - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_element_max_g = 5 - self.param.antenna_imt.ue_element_phi_3db = 65 - self.param.antenna_imt.ue_element_theta_3db = 65 - self.param.antenna_imt.ue_element_am = 30 - self.param.antenna_imt.ue_element_sla_v = 30 - self.param.antenna_imt.ue_n_rows = 2 - self.param.antenna_imt.ue_n_columns = 1 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 - - self.param.fss_ss.frequency = 10000 + + self.param.imt.bs.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.ue.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.bs.antenna.normalization = False + self.param.imt.bs.antenna.element_pattern = "M2101" + self.param.imt.bs.antenna.normalization_file = None + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.element_max_g = 10 + self.param.imt.bs.antenna.element_phi_3db = 80 + self.param.imt.bs.antenna.element_theta_3db = 80 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 16 + self.param.imt.bs.antenna.n_columns = 16 + self.param.imt.bs.antenna.element_horiz_spacing = 1 + self.param.imt.bs.antenna.element_vert_spacing = 1 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 10 + + self.param.imt.ue.antenna.element_pattern = "M2101" + self.param.imt.ue.antenna.normalization = False + self.param.imt.ue.antenna.normalization_file = None + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.element_max_g = 5 + self.param.imt.ue.antenna.element_phi_3db = 65 + self.param.imt.ue.antenna.element_theta_3db = 65 + self.param.imt.ue.antenna.element_am = 30 + self.param.imt.ue.antenna.element_sla_v = 30 + self.param.imt.ue.antenna.n_rows = 2 + self.param.imt.ue.antenna.n_columns = 1 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 + + self.param.fss_ss.frequency = 10000.0 self.param.fss_ss.bandwidth = 100 self.param.fss_ss.acs = 0 self.param.fss_ss.altitude = 35786000 @@ -121,7 +128,7 @@ def setUp(self): self.param.fss_ss.antenna_pattern = "OMNI" self.param.fss_ss.imt_altitude = 1000 self.param.fss_ss.imt_lat_deg = -23.5629739 - self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75) + self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75) self.param.fss_ss.channel_model = "FSPL" self.param.fss_ss.line_of_sight_prob = 0.01 self.param.fss_ss.surf_water_vapour_density = 7.5 @@ -129,8 +136,6 @@ def setUp(self): self.param.fss_ss.time_ratio = 0.5 self.param.fss_ss.antenna_l_s = -20 self.param.fss_ss.acs = 0 - self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_ss.EARTH_RADIUS = 6371000 self.param.fss_es.x = -5000 self.param.fss_es.y = 0 @@ -139,7 +144,7 @@ def setUp(self): self.param.fss_es.elevation_min = 20 self.param.fss_es.elevation_max = 20 self.param.fss_es.azimuth = "0" - self.param.fss_es.frequency = 10000 + self.param.fss_es.frequency = 10000.0 self.param.fss_es.bandwidth = 100 self.param.fss_es.noise_temperature = 100 self.param.fss_es.tx_power_density = -60 @@ -148,29 +153,25 @@ def setUp(self): self.param.fss_es.channel_model = "FSPL" self.param.fss_es.line_of_sight_prob = 1 self.param.fss_es.acs = 0 - self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_es.EARTH_RADIUS = 6371000 - self.param.ras.x = -5000 - self.param.ras.y = 0 + self.param.ras.geometry.location.type = "FIXED" + self.param.ras.geometry.location.x = -5000 + self.param.ras.geometry.location.y = 0 self.param.ras.height = 10 - self.param.ras.elevation = 20 - self.param.ras.azimuth = 0 - self.param.ras.frequency = 10000 + self.param.ras.geometry.elevation.type = "FIXED" + self.param.ras.geometry.elevation.fixed = 20 + self.param.ras.geometry.azimuth.fixed = 0 + self.param.ras.geometry.azimuth.type = "FIXED" + self.param.ras.frequency = 10000.0 self.param.ras.bandwidth = 100 - self.param.ras.antenna_noise_temperature = 50 - self.param.ras.receiver_noise_temperature = 50 - self.param.ras.antenna_gain = 50 + self.param.ras.noise_temperature = 100 + self.param.ras.antenna.gain = 50 self.param.ras.antenna_efficiency = 0.7 - self.param.ras.diameter = 10 self.param.ras.acs = 0 - self.param.ras.antenna_pattern = "OMNI" + self.param.ras.antenna.pattern = "OMNI" self.param.ras.channel_model = "FSPL" self.param.ras.line_of_sight_prob = 1 - self.param.ras.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.ras.EARTH_RADIUS = 6371000 - self.param.ras.SPEED_OF_LIGHT = 299792458 - + self.param.ras.tx_power_density = -500 def test_simulation_2bs_4ue_fss_ss(self): self.param.general.system = "FSS_SS" @@ -186,47 +187,51 @@ def test_simulation_2bs_4ue_fss_ss(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) # test connection method self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} - self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]}) + self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) # We do not test the selection method here because in this specific # scenario we do not want to change the order of the UE's self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) - path_loss_imt = np.array([[78.47, 89.35, 93.27, 97.05], - [97.55, 94.72, 91.53, 81.99]]) - bs_antenna_gains = np.array([[ 1, 1, 1, 1], [ 2, 2, 2, 2]]) - ue_antenna_gains = np.array([[ 10, 11, 22, 23], [ 10, 11, 22, 23]]) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) + path_loss_imt = np.array([[78.68, 89.36, 93.28, 97.06], + [97.55, 94.73, 91.54, 82.08]]) + bs_antenna_gains = np.array([[1, 1, 1, 1], [2, 2, 2, 2]]) + ue_antenna_gains = np.array([[10, 11, 22, 23], [10, 11, 22, 23]]) coupling_loss_imt = path_loss_imt - bs_antenna_gains - ue_antenna_gains \ - + self.param.imt.bs_ohmic_loss \ - + self.param.imt.ue_ohmic_loss \ - + self.param.imt.ue_body_loss + + self.param.imt.bs.ohmic_loss \ + + self.param.imt.ue.ohmic_loss \ + + self.param.imt.ue.body_loss npt.assert_allclose(self.simulation.coupling_loss_imt, coupling_loss_imt, @@ -234,74 +239,92 @@ def test_simulation_2bs_4ue_fss_ss(self): # test scheduler and bandwidth allocation self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + npt.assert_allclose(self.simulation.ue.bandwidth, + bandwidth_per_ue * np.ones(4), atol=1e-2) # there is no power control, so BS's will transmit at maximum power self.simulation.power_control() - tx_power = 10 - 10*math.log10(2) - npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2) - npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2) + tx_power = 10 - 10 * math.log10(2) + npt.assert_allclose(self.simulation.bs.tx_power[0], np.array( + [tx_power, tx_power]), atol=1e-2) + npt.assert_allclose(self.simulation.bs.tx_power[1], np.array( + [tx_power, tx_power]), atol=1e-2) # test method that calculates SINR self.simulation.calculate_sinr() # check UE received power - rx_power = tx_power - np.concatenate((coupling_loss_imt[0][:2], coupling_loss_imt[1][2:])) + rx_power = tx_power - \ + np.concatenate( + (coupling_loss_imt[0][:2], coupling_loss_imt[1][2:])) npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2) # check UE received interference - rx_interference = tx_power - np.concatenate((coupling_loss_imt[1][:2], coupling_loss_imt[0][2:])) - npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2) + rx_interference = tx_power - \ + np.concatenate( + (coupling_loss_imt[1][:2], coupling_loss_imt[0][2:])) + npt.assert_allclose(self.simulation.ue.rx_interference, + rx_interference, atol=1e-2) # check UE thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 - npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 + npt.assert_allclose(self.simulation.ue.thermal_noise, + thermal_noise, atol=1e-2) # check UE thermal noise + interference - total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise)) - npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2) + total_interference = 10 * \ + np.log10(np.power(10, 0.1 * rx_interference) + + np.power(10, 0.1 * thermal_noise)) + npt.assert_allclose( + self.simulation.ue.total_interference, total_interference, atol=1e-2) # check SNR - npt.assert_allclose(self.simulation.ue.snr, rx_power - thermal_noise, atol=1e-2) + npt.assert_allclose(self.simulation.ue.snr, + rx_power - thermal_noise, atol=1e-2) # check SINR - npt.assert_allclose(self.simulation.ue.sinr, rx_power - total_interference, atol=1e-2) + npt.assert_allclose(self.simulation.ue.sinr, + rx_power - total_interference, atol=1e-2) - self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss) - self.simulation.system.x = np.array([0.01]) # avoids zero-division + self.simulation.system = 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.height = np.array([self.param.fss_ss.altitude]) - # test the method that calculates interference from IMT UE to FSS space station + # test the method that calculates interference from IMT UE to FSS space + # station self.simulation.calculate_external_interference() # check coupling loss - # 4 values because we have 2 BS * 2 beams for each base station. + # 4 values because we have 2 BS * 2 beams for each base station. path_loss_imt_system = 203.52 polarization_loss = 3 sat_antenna_gain = 51 bs_antenna_gain = np.array([1, 2]) coupling_loss_imt_system = path_loss_imt_system - sat_antenna_gain \ - - np.array([bs_antenna_gain[0], bs_antenna_gain[0], bs_antenna_gain[1], bs_antenna_gain[1]]) \ - + polarization_loss \ - + self.param.imt.bs_ohmic_loss - + - np.array([bs_antenna_gain[0], bs_antenna_gain[0], bs_antenna_gain[1], bs_antenna_gain[1]]) \ + + polarization_loss \ + + self.param.imt.bs.ohmic_loss + npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) # check interference generated by BS to FSS space station interference = tx_power - coupling_loss_imt_system - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # check FSS space station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*950*1e3*100*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 950 * 1e3 * 100 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) @@ -311,7 +334,7 @@ def test_simulation_2bs_4ue_fss_ss(self): # np.array([ -147.448 - (-88.821) ]), # delta=.01) self.assertAlmostEqual(self.simulation.system.inr, - np.array([ rx_interference - thermal_noise ]), + np.array([rx_interference - thermal_noise]), delta=.01) def test_simulation_2bs_4ue_fss_es(self): @@ -320,90 +343,109 @@ def test_simulation_2bs_4ue_fss_es(self): self.simulation = SimulationDownlink(self.param, "") self.simulation.initialize() - self.simulation.bs_power_gain = 0 self.simulation.ue_power_gain = 0 random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) self.simulation.scheduler() self.simulation.power_control() self.simulation.calculate_sinr() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) - tx_power = 10 - 10*math.log10(2) - npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2) - npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2) + tx_power = 10 - 10 * math.log10(2) + npt.assert_allclose(self.simulation.bs.tx_power[0], np.array( + [tx_power, tx_power]), atol=1e-2) + npt.assert_allclose(self.simulation.bs.tx_power[1], np.array( + [tx_power, tx_power]), atol=1e-2) # check UE received power - rx_power = np.array([tx_power-3-(78.47-1-10)-4-3, tx_power-3-(89.35-1-11)-4-3, tx_power-3-(91.53-2-22)-4-3, tx_power-3-(81.99-2-23)-4-3]) + path_loss_imt = np.array([78.68, 89.37, 91.54, 82.09]) + rx_power = np.array([tx_power - 3 + 1 + 10 - 4 - 3, tx_power - 3 + 1 + 11 - + 4 - 3, tx_power - 3 + 2 + 22 - 4 - 3, tx_power - 3 + 2 + 23 - 4 - 3]) - path_loss_imt npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2) # check UE received interference - rx_interference = np.array([tx_power-3-(97.55-2-10)-4-3, tx_power-3-(94.72-2-11)-4-3, tx_power-3-(93.27-1-22)-4-3, tx_power-3-(97.05-1-23)-4-3]) - npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2) + rx_interference = np.array([tx_power - 3 - (97.55 - 2 - 10) - 4 - 3, tx_power - 3 - ( + 94.73 - 2 - 11) - 4 - 3, tx_power - 3 - (93.28 - 1 - 22) - 4 - 3, tx_power - 3 - (97.06 - 1 - 23) - 4 - 3]) + npt.assert_allclose(self.simulation.ue.rx_interference, + rx_interference, atol=1e-2) # check UE thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 - npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 + npt.assert_allclose(self.simulation.ue.thermal_noise, + thermal_noise, atol=1e-2) # check UE thermal noise + interference - total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise)) - npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2) - - self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen) + total_interference = 10 * \ + np.log10(np.power(10, 0.1 * rx_interference) + + np.power(10, 0.1 * thermal_noise)) + npt.assert_allclose( + self.simulation.ue.total_interference, total_interference, atol=1e-2) + + self.simulation.system = StationFactory.generate_fss_earth_station( + self.param.fss_es, random_number_gen) self.simulation.system.x = np.array([-2000]) self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.fss_es.height]) self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) + self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_es.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # what if FSS ES is the interferer? self.simulation.calculate_sinr_ext() # check coupling loss between FSS_ES and IMT_UE - coupling_loss_imt_system = np.array([128.55-50-10, 128.76-50-11, 128.93-50-22, 129.17-50-23]) + coupling_loss_imt_system = np.array( + [128.55 - 50 - 10, 128.77 - 50 - 11, 128.93 - 50 - 22, 129.18 - 50 - 23]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) # check interference from FSS_ES to IMT_UE - system_tx_power = -60 + 10*math.log10(bandwidth_per_ue*1e6) + 30 + system_tx_power = -60 + 10 * math.log10(bandwidth_per_ue * 1e6) + 30 ext_interference = system_tx_power - coupling_loss_imt_system npt.assert_allclose(self.simulation.ue.ext_interference, ext_interference, atol=1e-2) - ext_interference_total = 10*np.log10(np.power(10, 0.1*total_interference) \ - + np.power(10, 0.1*ext_interference)) + ext_interference_total = 10 * np.log10(np.power(10, 0.1 * total_interference) + + np.power(10, 0.1 * ext_interference)) npt.assert_allclose(self.simulation.ue.sinr_ext, rx_power - ext_interference_total, @@ -417,125 +459,137 @@ def test_simulation_2bs_4ue_fss_es(self): self.simulation.calculate_external_interference() # check coupling loss from IMT_BS to FSS_ES - coupling_loss_imt_system = np.array([124.47-50-1, 124.47-50-1, 125.29-50-2, 125.29-50-2]) + coupling_loss_imt_system = np.array( + [124.47 - 50 - 1, 124.47 - 50 - 1, 125.29 - 50 - 2, 125.29 - 50 - 2]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) interference = tx_power - coupling_loss_imt_system - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # check FSS Earth station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) # check INR at FSS Earth station self.assertAlmostEqual(self.simulation.system.inr, - np.array([ rx_interference - thermal_noise ]), + np.array([rx_interference - thermal_noise]), delta=.01) - def test_simulation_2bs_4ue_ras(self): self.param.general.system = "RAS" self.simulation = SimulationDownlink(self.param, "") self.simulation.initialize() - self.simulation.bs_power_gain = 0 self.simulation.ue_power_gain = 0 random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.ras.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) self.simulation.scheduler() self.simulation.power_control() self.simulation.calculate_sinr() # check UE thermal noise - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2) # check SINR npt.assert_allclose(self.simulation.ue.sinr, - np.array([-70.48 - (-85.49), -80.36 - (-83.19), -70.54 - (-73.15), -60.00 - (-75.82)]), + np.array( + [-70.70 - (-85.49), -80.37 - (-83.19), -70.55 - (-73.15), -60.10 - (-75.82)]), atol=1e-2) - self.simulation.system = StationFactory.generate_ras_station(self.param.ras) + self.simulation.system = StationFactory.generate_ras_station( + self.param.ras, random_number_gen, topology=None + ) self.simulation.system.x = np.array([-2000]) self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.ras.height]) self.simulation.system.antenna[0].effective_area = 54.9779 # Test gain calculation - gains = self.simulation.calculate_gains(self.simulation.system,self.simulation.bs) - npt.assert_equal(gains,np.array([[50, 50]])) + gains = self.simulation.calculate_gains( + self.simulation.system, self.simulation.bs) + npt.assert_equal(gains, np.array([[50, 50]])) self.simulation.calculate_external_interference() polarization_loss = 3 npt.assert_allclose(self.simulation.coupling_loss_imt_system, - np.array([118.47-50-1, 118.47-50-1, 119.29-50-2, 119.29-50-2]) + polarization_loss, + np.array([118.47 - 50 - 1, 118.47 - 50 - 1, 119.29 - + 50 - 2, 119.29 - 50 - 2]) + polarization_loss, atol=1e-2) # Test RAS interference - interference = self.param.imt.bs_conducted_power - 10*np.log10(self.param.imt.ue_k) \ - - np.array([118.47-50-1, 118.47-50-1, 119.29-50-2, 119.29-50-2]) - polarization_loss - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + interference = self.param.imt.bs.conducted_power - 10 * np.log10(self.param.imt.ue.k) \ + - np.array([118.47 - 50 - 1, 118.47 - 50 - 1, 119.29 - + 50 - 2, 119.29 - 50 - 2]) - polarization_loss + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # Test RAS PFD - pfd = 10*np.log10(10**(rx_interference/10)/54.9779) + pfd = 10 * np.log10(10**(rx_interference / 10) / 54.9779) self.assertAlmostEqual(self.simulation.system.pfd, - pfd, - delta=.01) + pfd, + delta=.01) # check RAS station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) # check INR at RAS station self.assertAlmostEqual(self.simulation.system.inr, - np.array([ rx_interference - (-98.599) ]), + np.array([rx_interference - (-98.599)]), delta=.01) def test_calculate_bw_weights(self): @@ -545,93 +599,94 @@ def test_calculate_bw_weights(self): bw_imt = 200 bw_sys = 33.33 ue_k = 3 - ref_weights = np.array([ 0.5, 0, 0]) + ref_weights = np.array([0.5, 0, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 100 bw_sys = 25 ue_k = 3 - ref_weights = np.array([ 0.75, 0, 0]) + ref_weights = np.array([0.75, 0, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 66.67 ue_k = 3 - ref_weights = np.array([ 1, 0, 0]) + ref_weights = np.array([1, 0, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 400 bw_sys = 200 ue_k = 3 - ref_weights = np.array([ 1, 0.49, 0]) + ref_weights = np.array([1, 0.49, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 133.33 ue_k = 3 - ref_weights = np.array([ 1, 1, 0]) + ref_weights = np.array([1, 1, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 150 ue_k = 3 - ref_weights = np.array([ 1, 1, 0.25]) + ref_weights = np.array([1, 1, 0.25]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 150 bw_sys = 150 ue_k = 3 - ref_weights = np.array([ 1, 1, 1]) + ref_weights = np.array([1, 1, 1]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 300 ue_k = 3 - ref_weights = np.array([ 1, 1, 1]) + ref_weights = np.array([1, 1, 1]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 50 ue_k = 2 - ref_weights = np.array([ 0.5, 0]) + ref_weights = np.array([0.5, 0]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 100 bw_sys = 60 ue_k = 2 - ref_weights = np.array([ 1, 0.2]) + ref_weights = np.array([1, 0.2]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 300 bw_sys = 300 ue_k = 2 - ref_weights = np.array([ 1, 1]) + ref_weights = np.array([1, 1]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 100 bw_sys = 50 ue_k = 1 - ref_weights = np.array([ 0.5 ]) + ref_weights = np.array([0.5]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) bw_imt = 200 bw_sys = 180 ue_k = 1 - ref_weights = np.array([ 0.9]) + ref_weights = np.array([0.9]) weights = self.simulation.calculate_bw_weights(bw_imt, bw_sys, ue_k) npt.assert_allclose(ref_weights, weights, atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_simulation_downlink_haps.py b/tests/test_simulation_downlink_haps.py index ae3cf381d..5a5345c5b 100644 --- a/tests/test_simulation_downlink_haps.py +++ b/tests/test_simulation_downlink_haps.py @@ -15,6 +15,9 @@ from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory +from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology +from sharc.parameters.imt.parameters_single_bs import ParametersSingleBS + class SimulationDownlinkHapsTest(unittest.TestCase): @@ -27,88 +30,92 @@ def setUp(self): self.param.general.enable_adjacent_channel = False self.param.general.overwrite_output = True - self.param.imt.topology = "SINGLE_BS" - self.param.imt.wrap_around = False - self.param.imt.num_clusters = 2 - self.param.imt.intersite_distance = 150 + self.param.imt.topology = ParametersImtTopology( + type="SINGLE_BS", + single_bs=ParametersSingleBS( + num_clusters=2, + intersite_distance=150, + cell_radius=2 * 150 / 3 + ) + ) self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False - self.param.imt.frequency = 10000 + self.param.imt.frequency = 10000.0 self.param.imt.bandwidth = 100 self.param.imt.rb_bandwidth = 0.180 self.param.imt.spectral_mask = "IMT-2020" self.param.imt.spurious_emissions = -13 self.param.imt.guard_band_ratio = 0.1 self.param.imt.ho_margin = 3 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 10 - self.param.imt.bs_height = 6 - self.param.imt.bs_acs = 30 - self.param.imt.bs_noise_figure = 7 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 2 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 0 - self.param.imt.ue_distribution_distance = "RAYLEIGH" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 0.8 - self.param.imt.ue_p_cmax = 20 - self.param.imt.ue_conducted_power = 10 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_aclr = 20 - self.param.imt.ue_acs = 25 - self.param.imt.ue_noise_figure = 9 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 10 + self.param.imt.bs.height = 6 + self.param.imt.bs.acs = 30 + self.param.imt.bs.noise_figure = 7 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 2 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 0 + self.param.imt.ue.distribution_distance = "RAYLEIGH" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 0.8 + self.param.imt.ue.p_cmax = 20 + self.param.imt.ue.conducted_power = 10 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.aclr = 20 + self.param.imt.ue.acs = 25 + self.param.imt.ue.noise_figure = 9 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" - self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL) + # probability of line-of-sight (not for FSPL) + self.param.imt.line_of_sight_prob = 0.75 self.param.imt.shadowing = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.bs_normalization_file = None - self.param.antenna_imt.bs_element_pattern = "M2101" - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_element_max_g = 10 - self.param.antenna_imt.bs_element_phi_3db = 80 - self.param.antenna_imt.bs_element_theta_3db = 80 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 16 - self.param.antenna_imt.bs_n_columns = 16 - self.param.antenna_imt.bs_element_horiz_spacing = 1 - self.param.antenna_imt.bs_element_vert_spacing = 1 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 10 - - self.param.antenna_imt.ue_normalization_file = None - self.param.antenna_imt.ue_normalization = False - self.param.antenna_imt.ue_element_pattern = "M2101" - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_element_max_g = 5 - self.param.antenna_imt.ue_element_phi_3db = 65 - self.param.antenna_imt.ue_element_theta_3db = 65 - self.param.antenna_imt.ue_element_am = 30 - self.param.antenna_imt.ue_element_sla_v = 30 - self.param.antenna_imt.ue_n_rows = 2 - self.param.antenna_imt.ue_n_columns = 1 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 - + + self.param.imt.bs.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.ue.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.bs.antenna.normalization = False + self.param.imt.bs.antenna.normalization_file = None + self.param.imt.bs.antenna.element_pattern = "M2101" + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.element_max_g = 10 + self.param.imt.bs.antenna.element_phi_3db = 80 + self.param.imt.bs.antenna.element_theta_3db = 80 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 16 + self.param.imt.bs.antenna.n_columns = 16 + self.param.imt.bs.antenna.element_horiz_spacing = 1 + self.param.imt.bs.antenna.element_vert_spacing = 1 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 10 + + self.param.imt.ue.antenna.normalization_file = None + self.param.imt.ue.antenna.normalization = False + self.param.imt.ue.antenna.element_pattern = "M2101" + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.element_max_g = 5 + self.param.imt.ue.antenna.element_phi_3db = 65 + self.param.imt.ue.antenna.element_theta_3db = 65 + self.param.imt.ue.antenna.element_am = 30 + self.param.imt.ue.antenna.element_sla_v = 30 + self.param.imt.ue.antenna.n_rows = 2 + self.param.imt.ue.antenna.n_columns = 1 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 + self.param.haps.frequency = 10000 self.param.haps.bandwidth = 200 self.param.haps.altitude = 20000 @@ -117,7 +124,8 @@ def setUp(self): self.param.haps.azimuth = 0 self.param.haps.eirp_density = 4.4 self.param.haps.antenna_gain = 28 - self.param.haps.tx_power_density = self.param.haps.eirp_density - self.param.haps.antenna_gain - 60 + self.param.haps.tx_power_density = self.param.haps.eirp_density - \ + self.param.haps.antenna_gain - 60 self.param.haps.antenna_pattern = "OMNI" self.param.haps.imt_altitude = 0 self.param.haps.imt_lat_deg = 0 @@ -125,10 +133,6 @@ def setUp(self): self.param.haps.season = "SUMMER" self.param.haps.channel_model = "FSPL" self.param.haps.antenna_l_n = -25 - self.param.haps.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.haps.EARTH_RADIUS = 6371000 - - def test_simulation_2bs_4ue_1haps(self): """ @@ -146,78 +150,135 @@ def test_simulation_2bs_4ue_1haps(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.haps.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.simulation.coupling_loss_imt = \ + self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) self.simulation.scheduler() self.simulation.power_control() self.simulation.calculate_sinr() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 - tx_power = 10 - 10*math.log10(2) - npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2) - npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2) + tx_power = 10 - 10 * math.log10(2) + npt.assert_allclose(self.simulation.bs.tx_power[0], np.array( + [tx_power, tx_power]), atol=1e-2) + npt.assert_allclose(self.simulation.bs.tx_power[1], np.array( + [tx_power, tx_power]), atol=1e-2) # check UE received power - rx_power = np.array([tx_power-3-(78.47-1-10)-4-3, tx_power-3-(89.35-1-11)-4-3, tx_power-3-(91.53-2-22)-4-3, tx_power-3-(81.99-2-23)-4-3]) + rx_power = np.array([tx_power - 3 - (78.68 - 1 - 10) - 4 - 3, + tx_power - 3 - (89.37 - 1 - 11) - 4 - 3, + tx_power - 3 - (91.54 - 2 - 22) - 4 - 3, + tx_power - 3 - (82.09 - 2 - 23) - 4 - 3]) npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-2) # check UE received interference - rx_interference = np.array([tx_power-3-(97.55-2-10)-4-3, tx_power-3-(94.72-2-11)-4-3, tx_power-3-(93.27-1-22)-4-3, tx_power-3-(97.05-1-23)-4-3]) - npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-2) + rx_interference = np.array([tx_power - + 3 - + (97.55 - + 2 - + 10) - + 4 - + 3, tx_power - + 3 - + (94.73 - + 2 - + 11) - + 4 - + 3, tx_power - + 3 - + (93.28 - + 1 - + 22) - + 4 - + 3, tx_power - + 3 - + (97.07 - + 1 - + 23) - + 4 - + 3]) + npt.assert_allclose( + self.simulation.ue.rx_interference, + rx_interference, + atol=1e-2) # check UE thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 - npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-2) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 + npt.assert_allclose( + self.simulation.ue.thermal_noise, + thermal_noise, + atol=1e-2) # check UE thermal noise + interference - total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise)) - npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-2) - - self.simulation.system = StationFactory.generate_haps(self.param.haps, 0, random_number_gen) + total_interference = 10 * \ + np.log10( + np.power( + 10, + 0.1 * + rx_interference) + + np.power( + 10, + 0.1 * + thermal_noise)) + npt.assert_allclose( + self.simulation.ue.total_interference, + total_interference, + atol=1e-2) + + self.simulation.system = StationFactory.generate_haps( + self.param.haps, 0, random_number_gen) # now we evaluate interference from HAPS to IMT UE self.simulation.calculate_sinr_ext() # check coupling loss between FSS_ES and IMT_UE - coupling_loss_imt_system = np.array([148.47-28-10, 148.47-28-11, 148.47-28-22, 148.47-28-23]) + coupling_loss_imt_system = np.array( + [148.47 - 28 - 10, 148.47 - 28 - 11, 148.47 - 28 - 22, 148.47 - 28 - 23]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) - system_tx_power = (4.4 - 28 - 60) + 10*math.log10(bandwidth_per_ue*1e6) + 30 + system_tx_power = (4.4 - 28 - 60) + 10 * \ + math.log10(bandwidth_per_ue * 1e6) + 30 ext_interference = system_tx_power - coupling_loss_imt_system npt.assert_allclose(self.simulation.ue.ext_interference, ext_interference, atol=1e-2) - ext_interference_total = 10*np.log10(np.power(10, 0.1*total_interference) \ - + np.power(10, 0.1*ext_interference)) + ext_interference_total = 10 * np.log10(np.power(10, 0.1 * total_interference) + + np.power(10, 0.1 * ext_interference)) npt.assert_allclose(self.simulation.ue.sinr_ext, rx_power - ext_interference_total, @@ -228,8 +289,5 @@ def test_simulation_2bs_4ue_1haps(self): atol=1e-2) - - - if __name__ == '__main__': unittest.main() diff --git a/tests/test_simulation_downlink_tvro.py b/tests/test_simulation_downlink_tvro.py index 6b0485174..dbce8f18d 100644 --- a/tests/test_simulation_downlink_tvro.py +++ b/tests/test_simulation_downlink_tvro.py @@ -15,6 +15,7 @@ from sharc.station_factory import StationFactory from sharc.propagation.propagation_factory import PropagationFactory + class SimulationDownlinkTvroTest(unittest.TestCase): def setUp(self): @@ -27,91 +28,91 @@ def setUp(self): self.param.general.seed = 101 self.param.general.overwrite_output = True - self.param.imt.topology = "SINGLE_BS" - self.param.imt.wrap_around = False - self.param.imt.num_clusters = 2 - self.param.imt.intersite_distance = 150 + self.param.imt.topology.type = "SINGLE_BS" + self.param.imt.topology.single_bs.num_clusters = 2 + self.param.imt.topology.single_bs.intersite_distance = 150 + self.param.imt.topology.single_bs.cell_radius = 100 self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False - self.param.imt.frequency = 3590 + self.param.imt.frequency = 3590.0 self.param.imt.bandwidth = 20 self.param.imt.rb_bandwidth = 0.180 self.param.imt.spectral_mask = "3GPP E-UTRA" self.param.imt.spurious_emissions = -13 self.param.imt.guard_band_ratio = 0.1 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 46 - self.param.imt.bs_height = 20 - self.param.imt.bs_noise_figure = 5 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 2 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 0 - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_distribution_distance = "UNIFORM" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 1 - self.param.imt.ue_p_cmax = 23 - self.param.imt.ue_power_dynamic_range = 63 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_acs = 25 - self.param.imt.ue_noise_figure = 9 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 46 + self.param.imt.bs.height = 20 + self.param.imt.bs.noise_figure = 5 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 2 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 0 + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.distribution_distance = "UNIFORM" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 1 + self.param.imt.ue.p_cmax = 23 + self.param.imt.ue.power_dynamic_range = 63 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.acs = 25 + self.param.imt.ue.noise_figure = 9 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" self.param.imt.los_adjustment_factor = 29 - self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL) + # probability of line-of-sight (not for FSPL) + self.param.imt.line_of_sight_prob = 0.75 self.param.imt.shadowing = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "BEAMFORMING" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.bs_element_pattern = "F1336" - self.param.antenna_imt.bs_normalization_file = None - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_element_max_g = 18 - self.param.antenna_imt.bs_element_phi_3db = 65 - self.param.antenna_imt.bs_element_theta_3db = 0 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 1 - self.param.antenna_imt.bs_n_columns = 1 - self.param.antenna_imt.bs_element_horiz_spacing = 1 - self.param.antenna_imt.bs_element_vert_spacing = 1 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 10 - - self.param.antenna_imt.ue_element_pattern = "FIXED" - self.param.antenna_imt.ue_normalization = False - self.param.antenna_imt.ue_normalization_file = None - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_element_max_g = -4 - self.param.antenna_imt.ue_element_phi_3db = 0 - self.param.antenna_imt.ue_element_theta_3db = 0 - self.param.antenna_imt.ue_element_am = 0 - self.param.antenna_imt.ue_element_sla_v = 0 - self.param.antenna_imt.ue_n_rows = 1 - self.param.antenna_imt.ue_n_columns = 1 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 + + self.param.imt.bs.antenna.adjacent_antenna_model = "BEAMFORMING" + self.param.imt.ue.antenna.adjacent_antenna_model = "BEAMFORMING" + self.param.imt.bs.antenna.normalization = False + self.param.imt.bs.antenna.element_pattern = "F1336" + self.param.imt.bs.antenna.normalization_file = None + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.element_max_g = 18 + self.param.imt.bs.antenna.element_phi_3db = 65 + self.param.imt.bs.antenna.element_theta_3db = 0 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 1 + self.param.imt.bs.antenna.n_columns = 1 + self.param.imt.bs.antenna.element_horiz_spacing = 1 + self.param.imt.bs.antenna.element_vert_spacing = 1 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 10 + + self.param.imt.ue.antenna.element_pattern = "FIXED" + self.param.imt.ue.antenna.normalization = False + self.param.imt.ue.antenna.normalization_file = None + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.element_max_g = -4 + self.param.imt.ue.antenna.element_phi_3db = 0 + self.param.imt.ue.antenna.element_theta_3db = 0 + self.param.imt.ue.antenna.element_am = 0 + self.param.imt.ue.antenna.element_sla_v = 0 + self.param.imt.ue.antenna.n_rows = 1 + self.param.imt.ue.antenna.n_columns = 1 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 self.param.fss_es.location = "FIXED" self.param.fss_es.x = 100 self.param.fss_es.y = 0 self.param.fss_es.min_dist_to_bs = 10 - self.param.fss_es.max_dist_to_bs = 600 + self.param.fss_es.max_dist_to_bs = 600 self.param.fss_es.height = 6 self.param.fss_es.elevation_min = 49.8 self.param.fss_es.elevation_max = 49.8 @@ -127,9 +128,6 @@ def setUp(self): self.param.fss_es.antenna_envelope_gain = 0 self.param.fss_es.channel_model = "FSPL" self.param.fss_es.line_of_sight_prob = 1 - self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_es.EARTH_RADIUS = 6371000 - def test_simulation_1bs_1ue_tvro(self): self.param.general.system = "FSS_ES" @@ -137,20 +135,20 @@ def test_simulation_1bs_1ue_tvro(self): self.simulation = SimulationDownlink(self.param, "") self.simulation.initialize() random_number_gen = np.random.RandomState(self.param.general.seed) - + self.assertTrue(self.simulation.co_channel) self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.x = np.array([0, -200]) self.simulation.bs.y = np.array([0, 0]) self.simulation.bs.azimuth = np.array([0, 180]) self.simulation.bs.elevation = np.array([-10, -10]) - + self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([30, 60, -220, -300]) @@ -159,98 +157,117 @@ def test_simulation_1bs_1ue_tvro(self): # test connection method self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1], 1:[2,3]} - self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]}) - + self.simulation.link = {0: [0, 1], 1: [2, 3]} + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) + self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_es.channel_model, + self.simulation.param_system, self.param, random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) - path_loss_imt = np.array([[73.09, 79.11, 90.40, 93.09], - [90.78, 91.85, 69.57, 83.55]]) - bs_antenna_gains = np.array([[ 3.04, 7.30, -6.45, -6.45], - [ -6.45, -6.45, 1.63, 17.95]]) - ue_antenna_gains = np.array([[ -4, -4, -4, -4], [ -4, -4, -4, -4]]) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) + path_loss_imt = np.array([[74.49, 79.50, 90.43, 93.11], + [90.81, 91.87, 72.25, 83.69]]) + bs_antenna_gains = np.array([[3.04, 7.30, -6.45, -6.45], + [-6.45, -6.45, 1.63, 17.95]]) + ue_antenna_gains = np.array([[-4, -4, -4, -4], [-4, -4, -4, -4]]) coupling_loss_imt = path_loss_imt - bs_antenna_gains - ue_antenna_gains \ - + self.param.imt.bs_ohmic_loss \ - + self.param.imt.ue_ohmic_loss \ - + self.param.imt.ue_body_loss + + self.param.imt.bs.ohmic_loss \ + + self.param.imt.ue.ohmic_loss \ + + self.param.imt.ue.body_loss npt.assert_allclose(self.simulation.coupling_loss_imt, coupling_loss_imt, atol=1e-1) # test scheduler and bandwidth allocation self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*20/2) - npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2) - + bandwidth_per_ue = math.trunc((1 - 0.1) * 20 / 2) + npt.assert_allclose(self.simulation.ue.bandwidth, + bandwidth_per_ue * np.ones(4), atol=1e-2) + # there is no power control, so BS's will transmit at maximum power self.simulation.power_control() - tx_power = 46 - 10*np.log10(2) - npt.assert_allclose(self.simulation.bs.tx_power[0], np.array([tx_power, tx_power]), atol=1e-2) - npt.assert_allclose(self.simulation.bs.tx_power[1], np.array([tx_power, tx_power]), atol=1e-2) + tx_power = 46 - 10 * np.log10(2) + npt.assert_allclose(self.simulation.bs.tx_power[0], np.array( + [tx_power, tx_power]), atol=1e-2) + npt.assert_allclose(self.simulation.bs.tx_power[1], np.array( + [tx_power, tx_power]), atol=1e-2) # test method that calculates SINR self.simulation.calculate_sinr() - + # check UE received power - rx_power = tx_power - np.concatenate((coupling_loss_imt[0][:2], coupling_loss_imt[1][2:])) + rx_power = tx_power - \ + np.concatenate( + (coupling_loss_imt[0][:2], coupling_loss_imt[1][2:])) npt.assert_allclose(self.simulation.ue.rx_power, rx_power, atol=1e-1) # check UE received interference - rx_interference = tx_power - np.concatenate((coupling_loss_imt[1][:2], coupling_loss_imt[0][2:])) - npt.assert_allclose(self.simulation.ue.rx_interference, rx_interference, atol=1e-1) + rx_interference = tx_power - \ + np.concatenate( + (coupling_loss_imt[1][:2], coupling_loss_imt[0][2:])) + npt.assert_allclose(self.simulation.ue.rx_interference, + rx_interference, atol=1e-1) # check UE thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 9 - npt.assert_allclose(self.simulation.ue.thermal_noise, thermal_noise, atol=1e-1) + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 9 + npt.assert_allclose(self.simulation.ue.thermal_noise, + thermal_noise, atol=1e-1) # check UE thermal noise + interference - total_interference = 10*np.log10(np.power(10, 0.1*rx_interference) + np.power(10, 0.1*thermal_noise)) - npt.assert_allclose(self.simulation.ue.total_interference, total_interference, atol=1e-1) + total_interference = 10 * \ + np.log10(np.power(10, 0.1 * rx_interference) + + np.power(10, 0.1 * thermal_noise)) + npt.assert_allclose( + self.simulation.ue.total_interference, total_interference, atol=1e-1) # check SNR - npt.assert_allclose(self.simulation.ue.snr, rx_power - thermal_noise, atol=1e-1) + npt.assert_allclose(self.simulation.ue.snr, + rx_power - thermal_noise, atol=1e-1) # check SINR - npt.assert_allclose(self.simulation.ue.sinr, rx_power - total_interference, atol=1e-1) + npt.assert_allclose(self.simulation.ue.sinr, + rx_power - total_interference, atol=1e-1) ####################################################################### - self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen) + self.simulation.system = StationFactory.generate_fss_earth_station( + self.param.fss_es, random_number_gen) self.simulation.system.x = np.array([600]) self.simulation.system.y = np.array([0]) - - # test the method that calculates interference from IMT UE to FSS space station - self.simulation.calculate_external_interference() - + + # test the method that calculates interference from IMT UE to FSS space + # station + self.simulation.calculate_external_interference() + # check coupling loss from IMT_BS to FSS_ES - # 4 values because we have 2 BS * 2 beams for each base station. - path_loss_imt_system = np.array([99.11, 99.11, 101.61, 101.61]) + # 4 values because we have 2 BS * 2 beams for each base station. + path_loss_imt_system = np.array([99.11, 99.11, 101.61, 101.61]) polarization_loss = 3 - tvro_antenna_gain = np.array([ 0, 0, 0, 0]) - bs_antenna_gain = np.array([6.47, 6.47, -6.45, -6.45]) + tvro_antenna_gain = np.array([0, 0, 0, 0]) + bs_antenna_gain = np.array([6.47, 6.47, -6.45, -6.45]) coupling_loss_imt_system = path_loss_imt_system - tvro_antenna_gain \ - - bs_antenna_gain \ - + polarization_loss \ - + self.param.imt.bs_ohmic_loss - + - bs_antenna_gain \ + + polarization_loss \ + + self.param.imt.bs.ohmic_loss + npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-1) - + # check blocking signal interference = tx_power - coupling_loss_imt_system - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) - if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_simulation_indoor.py b/tests/test_simulation_indoor.py index fc38f16da..63a2d2b58 100644 --- a/tests/test_simulation_indoor.py +++ b/tests/test_simulation_indoor.py @@ -15,7 +15,9 @@ from sharc.parameters.parameters import Parameters from sharc.antenna.antenna_omni import AntennaOmni from sharc.station_factory import StationFactory -from sharc.propagation.propagation_factory import PropagationFactory +from sharc.parameters.imt.parameters_imt_topology import ParametersImtTopology +from sharc.parameters.imt.parameters_indoor import ParametersIndoor + class SimulationIndoorTest(unittest.TestCase): @@ -28,9 +30,7 @@ def setUp(self): self.param.general.enable_adjacent_channel = False self.param.general.overwrite_output = True - self.param.imt.topology = "INDOOR" - self.param.imt.num_clusters = 1 - self.param.imt.intersite_distance = 339 + self.param.imt.topology.type = "INDOOR" self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False self.param.imt.frequency = 40000 @@ -39,81 +39,81 @@ def setUp(self): self.param.imt.spectral_mask = "IMT-2020" self.param.imt.spurious_emissions = -13 self.param.imt.guard_band_ratio = 0.1 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 2 - self.param.imt.bs_height = 3 - self.param.imt.bs_noise_figure = 12 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 1 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 95 - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_distribution_distance = "RAYLEIGH" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 1 - self.param.imt.ue_p_cmax = 22 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_noise_figure = 12 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 2 + self.param.imt.bs.height = 3 + self.param.imt.bs.noise_figure = 12 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 1 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 95 + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.distribution_distance = "RAYLEIGH" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 1 + self.param.imt.ue.p_cmax = 22 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.noise_figure = 12 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" self.param.imt.shadowing = False - self.param.imt.wrap_around = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.bs_normalization_file = path.join('..','sharc','antenna','beamforming_normalization','bs_indoor_norm.npz') - self.param.antenna_imt.ue_normalization_file = path.join('..','sharc','antenna','beamforming_normalization','ue_norm.npz') - self.param.antenna_imt.bs_element_pattern = "M2101" - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_element_max_g = 5 - self.param.antenna_imt.bs_element_phi_3db = 90 - self.param.antenna_imt.bs_element_theta_3db = 90 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 8 - self.param.antenna_imt.bs_n_columns = 16 - self.param.antenna_imt.bs_element_horiz_spacing = 0.5 - self.param.antenna_imt.bs_element_vert_spacing = 0.5 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 90 - - self.param.antenna_imt.ue_element_pattern = "M2101" - self.param.antenna_imt.ue_normalization = False - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_element_max_g = 5 - self.param.antenna_imt.ue_element_phi_3db = 90 - self.param.antenna_imt.ue_element_theta_3db = 90 - self.param.antenna_imt.ue_element_am = 25 - self.param.antenna_imt.ue_element_sla_v = 25 - self.param.antenna_imt.ue_n_rows = 4 - self.param.antenna_imt.ue_n_columns = 4 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 - - self.param.indoor.basic_path_loss = "FSPL" - self.param.indoor.n_rows = 1 - self.param.indoor.n_colums = 1 - self.param.indoor.num_imt_buildings = 'ALL' - self.param.indoor.street_width = 30 - self.param.indoor.ue_indoor_percent = 0.95 - self.param.indoor.building_class = "TRADITIONAL" - self.param.indoor.intersite_distance = 30 - self.param.indoor.num_cells = 4 - self.param.indoor.num_floors = 1 + + self.param.imt.bs.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.ue.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.bs.antenna.normalization = False + self.param.imt.bs.antenna.normalization_file = path.join( + '..', 'sharc', 'antenna', 'beamforming_normalization', 'bs_indoor_norm.npz') + self.param.imt.ue.antenna.normalization_file = path.join( + '..', 'sharc', 'antenna', 'beamforming_normalization', 'ue_norm.npz') + self.param.imt.bs.antenna.element_pattern = "M2101" + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.element_max_g = 5 + self.param.imt.bs.antenna.element_phi_3db = 90 + self.param.imt.bs.antenna.element_theta_3db = 90 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 8 + self.param.imt.bs.antenna.n_columns = 16 + self.param.imt.bs.antenna.element_horiz_spacing = 0.5 + self.param.imt.bs.antenna.element_vert_spacing = 0.5 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 90 + + self.param.imt.ue.antenna.element_pattern = "M2101" + self.param.imt.ue.antenna.normalization = False + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.element_max_g = 5 + self.param.imt.ue.antenna.element_phi_3db = 90 + self.param.imt.ue.antenna.element_theta_3db = 90 + self.param.imt.ue.antenna.element_am = 25 + self.param.imt.ue.antenna.element_sla_v = 25 + self.param.imt.ue.antenna.n_rows = 4 + self.param.imt.ue.antenna.n_columns = 4 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 + + self.param.imt.topology.indoor.basic_path_loss = "FSPL" + self.param.imt.topology.indoor.n_rows = 1 + self.param.imt.topology.indoor.n_colums = 1 + self.param.imt.topology.indoor.num_imt_buildings = 'ALL' + self.param.imt.topology.indoor.street_width = 30 + self.param.imt.topology.indoor.ue_indoor_percent = 0.95 + self.param.imt.topology.indoor.building_class = "TRADITIONAL" + self.param.imt.topology.indoor.intersite_distance = 30 + self.param.imt.topology.indoor.num_cells = 4 + self.param.imt.topology.indoor.num_floors = 1 self.param.fss_es.x = 135 self.param.fss_es.y = 65 @@ -132,9 +132,6 @@ def setUp(self): self.param.fss_es.line_of_sight_prob = 1 self.param.fss_es.adjacent_ch_selectivity = 0 self.param.fss_es.diameter = 0.74 - self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_es.EARTH_RADIUS = 6371000 - def test_simulation_fss_es(self): # Initialize stations @@ -146,94 +143,104 @@ def test_simulation_fss_es(self): random_number_gen = np.random.RandomState(101) self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.assertTrue(np.all(self.simulation.bs.active)) - + self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) - + # print("Random position:") # self.simulation.plot_scenario() - self.simulation.ue.x = np.array([0.0, 45.0, 75.0,120.0]) - self.simulation.ue.y = np.array([0.0, 50.0, 0.0, 50.0]) + self.simulation.ue.x = np.array([0.0, 45.0, 75.0, 120.0]) + self.simulation.ue.y = np.array([0.0, 50.0, 0.0, 50.0]) # print("Forced position:") # self.simulation.plot_scenario() - + # Connect and select UEs self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) self.assertTrue(np.all(self.simulation.ue.active)) - self.assertDictEqual(self.simulation.link,{0:[0],1:[1],2:[2],3:[3]}) - + self.assertDictEqual( + self.simulation.link, { + 0: [0], 1: [1], 2: [2], 3: [3]}) + # Test BS-to-UE angles in the IMT coord system - expected_azi = np.array([[-120.96, 39.80, -22.62, 13.39], - [-150.95, 90.00, -39.81, 18.43], - [-161.57, 140.19, -90.00, 29.06], - [-166.61, 157.38,-140.19, 59.03]]) + expected_azi = np.array([[-120.96, 39.80, -22.62, 13.39], + [-150.95, 90.00, -39.81, 18.43], + [-161.57, 140.19, -90.00, 29.06], + [-166.61, 157.38, -140.19, 59.03]]) npt.assert_allclose(self.simulation.bs_to_ue_phi, expected_azi, atol=1e-2) - expected_ele = np.array([[92.95, 92.20, 91.32, 90.79], - [91.67, 93.43, 92.20, 91.09], - [91.09, 92.20, 93.43, 91.67], - [90.79, 91.32, 92.20, 92.95]]) + expected_ele = np.array([[92.95, 92.20, 91.32, 90.79], + [91.67, 93.43, 92.20, 91.09], + [91.09, 92.20, 93.43, 91.67], + [90.79, 91.32, 92.20, 92.95]]) npt.assert_allclose(self.simulation.bs_to_ue_theta, expected_ele, atol=1e-2) - + # Test BS-to-UE angles in the local coord system - expected_loc = [(np.array([-86.57]),np.array([120.92])), - (np.array([ 86.57]),np.array([ 90.00])), - (np.array([-86.57]),np.array([ 90.00])), - (np.array([ 86.57]),np.array([ 59.08]))] - expected_beam = [(-86.57,30.92), - ( 86.57, 0.00), + expected_loc = [(np.array([-86.57]), np.array([120.92])), + (np.array([86.57]), np.array([90.00])), + (np.array([-86.57]), np.array([90.00])), + (np.array([86.57]), np.array([59.08]))] + expected_beam = [(-86.57, 30.92), + (86.57, 0.00), (-86.57, 0.00), - ( 86.57,-30.92)] + (86.57, -30.92)] for k in range(self.simulation.bs.num_stations): - - self.assertEqual(self.simulation.bs.antenna[k].azimuth,0.0) - self.assertEqual(self.simulation.bs.antenna[k].elevation,-90.0) - - lo_angles = self.simulation.bs.antenna[k].to_local_coord(expected_azi[k,k], - expected_ele[k,k]) - npt.assert_array_almost_equal(lo_angles,expected_loc[k],decimal=2) + + self.assertEqual(self.simulation.bs.antenna[k].azimuth, 0.0) + self.assertEqual(self.simulation.bs.antenna[k].elevation, -90.0) + + lo_angles = self.simulation.bs.antenna[k].to_local_coord(expected_azi[k, k], + expected_ele[k, k]) + npt.assert_array_almost_equal( + lo_angles, expected_loc[k], decimal=2) npt.assert_array_almost_equal(self.simulation.bs.antenna[k].beams_list[0], - expected_beam[k],decimal=2) - + expected_beam[k], decimal=2) + # Test angle to ES in the IMT coord system - phi_es, theta_es = self.simulation.bs.get_pointing_vector_to(self.simulation.system) - expected_phi_es = np.array([[18.44],[23.96],[33.69],[53.13]]) - npt.assert_array_almost_equal(phi_es,expected_phi_es,decimal=2) - expected_theta_es = np.array([[86.83],[85.94],[84.46],[82.03]]) - npt.assert_array_almost_equal(theta_es,expected_theta_es,decimal=2) - + phi_es, theta_es = self.simulation.bs.get_pointing_vector_to( + self.simulation.system) + expected_phi_es = np.array([[18.44], [23.96], [33.69], [53.13]]) + npt.assert_array_almost_equal(phi_es, expected_phi_es, decimal=2) + expected_theta_es = np.array([[86.83], [85.94], [84.46], [82.03]]) + npt.assert_array_almost_equal(theta_es, expected_theta_es, decimal=2) + # Test angle to ES in the local coord system - expected_es_loc = [(np.array([99.92]),np.array([18.70])), - (np.array([99.92]),np.array([24.28])), - (np.array([99.92]),np.array([34.09])), - (np.array([99.92]),np.array([53.54]))] + expected_es_loc = [(np.array([99.92]), np.array([18.70])), + (np.array([99.92]), np.array([24.28])), + (np.array([99.92]), np.array([34.09])), + (np.array([99.92]), np.array([53.54]))] for k in range(self.simulation.bs.num_stations): lo_angles = self.simulation.bs.antenna[k].to_local_coord(expected_phi_es[k], expected_theta_es[k]) - npt.assert_array_almost_equal(lo_angles,expected_es_loc[k],decimal=2) - + npt.assert_array_almost_equal( + lo_angles, expected_es_loc[k], decimal=2) + # Test gain to ES calc_gain = self.simulation.calculate_gains(self.simulation.bs, - self.simulation.system) + self.simulation.system) for k in range(self.simulation.bs.num_stations): beam = 0 exp_gain = self.simulation.bs.antenna[k]._beam_gain(expected_es_loc[k][0], expected_es_loc[k][1], beam) - self.assertAlmostEqual(np.asscalar(calc_gain[k]),np.asscalar(exp_gain),places=1) - + self.assertAlmostEqual( + np.ndarray.item( + calc_gain[k]), + np.ndarray.item(exp_gain), + places=1) + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_simulation_uplink.py b/tests/test_simulation_uplink.py index 2a38a0861..0e6d3da2f 100644 --- a/tests/test_simulation_uplink.py +++ b/tests/test_simulation_uplink.py @@ -17,6 +17,7 @@ from sharc.propagation.propagation_factory import PropagationFactory from sharc.support.enumerations import StationType + class SimulationUplinkTest(unittest.TestCase): def setUp(self): @@ -28,87 +29,87 @@ def setUp(self): self.param.general.enable_adjacent_channel = False self.param.general.overwrite_output = True - self.param.imt.topology = "SINGLE_BS" - self.param.imt.wrap_around = True - self.param.imt.num_clusters = 2 - self.param.imt.intersite_distance = 150 + self.param.imt.topology.type = "SINGLE_BS" + self.param.imt.topology.single_bs.num_clusters = 2 + self.param.imt.topology.single_bs.intersite_distance = 150 + self.param.imt.topology.single_bs.cell_radius = 100 self.param.imt.minimum_separation_distance_bs_ue = 10 self.param.imt.interfered_with = False - self.param.imt.frequency = 10000 - self.param.imt.bandwidth = 100 + self.param.imt.frequency = 10000.0 + self.param.imt.bandwidth = 100.0 self.param.imt.rb_bandwidth = 0.180 self.param.imt.spectral_mask = "IMT-2020" self.param.imt.spurious_emissions = -13 self.param.imt.guard_band_ratio = 0.1 self.param.imt.ho_margin = 3 - self.param.imt.bs_load_probability = 1 - self.param.imt.num_resource_blocks = 10 - self.param.imt.bs_conducted_power = 10 - self.param.imt.bs_height = 6 - self.param.imt.bs_acs = 30 - self.param.imt.bs_noise_figure = 7 - self.param.imt.bs_noise_temperature = 290 - self.param.imt.bs_ohmic_loss = 3 - self.param.imt.ul_attenuation_factor = 0.4 - self.param.imt.ul_sinr_min = -10 - self.param.imt.ul_sinr_max = 22 - self.param.imt.ue_k = 2 - self.param.imt.ue_k_m = 1 - self.param.imt.ue_indoor_percent = 0 - self.param.imt.ue_distribution_distance = "RAYLEIGH" - self.param.imt.ue_distribution_azimuth = "UNIFORM" - self.param.imt.ue_distribution_type = "ANGLE_AND_DISTANCE" - self.param.imt.ue_tx_power_control = "OFF" - self.param.imt.ue_p_o_pusch = -95 - self.param.imt.ue_alpha = 0.8 - self.param.imt.ue_p_cmax = 20 - self.param.imt.ue_conducted_power = 10 - self.param.imt.ue_height = 1.5 - self.param.imt.ue_aclr = 20 - self.param.imt.ue_acs = 25 - self.param.imt.ue_noise_figure = 9 - self.param.imt.ue_ohmic_loss = 3 - self.param.imt.ue_body_loss = 4 - self.param.imt.dl_attenuation_factor = 0.6 - self.param.imt.dl_sinr_min = -10 - self.param.imt.dl_sinr_max = 30 + self.param.imt.bs.load_probability = 1 + + self.param.imt.bs.conducted_power = 10 + self.param.imt.bs.height = 6 + self.param.imt.bs.acs = 30 + self.param.imt.bs.noise_figure = 7 + self.param.imt.bs.ohmic_loss = 3 + self.param.imt.uplink.attenuation_factor = 0.4 + self.param.imt.uplink.sinr_min = -10 + self.param.imt.uplink.sinr_max = 22 + self.param.imt.ue.k = 2 + self.param.imt.ue.k_m = 1 + self.param.imt.ue.indoor_percent = 0 + self.param.imt.ue.distribution_distance = "RAYLEIGH" + self.param.imt.ue.distribution_azimuth = "UNIFORM" + self.param.imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + self.param.imt.ue.tx_power_control = "OFF" + self.param.imt.ue.p_o_pusch = -95 + self.param.imt.ue.alpha = 0.8 + self.param.imt.ue.p_cmax = 20 + self.param.imt.ue.conducted_power = 10 + self.param.imt.ue.height = 1.5 + self.param.imt.ue.aclr = 20 + self.param.imt.ue.acs = 25 + self.param.imt.ue.noise_figure = 9 + self.param.imt.ue.ohmic_loss = 3 + self.param.imt.ue.body_loss = 4 + self.param.imt.downlink.attenuation_factor = 0.6 + self.param.imt.downlink.sinr_min = -10 + self.param.imt.downlink.sinr_max = 30 self.param.imt.channel_model = "FSPL" - self.param.imt.line_of_sight_prob = 0.75 # probability of line-of-sight (not for FSPL) + # probability of line-of-sight (not for FSPL) + self.param.imt.line_of_sight_prob = 0.75 self.param.imt.shadowing = False self.param.imt.noise_temperature = 290 - self.param.imt.BOLTZMANN_CONSTANT = 1.38064852e-23 - - self.param.antenna_imt.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.antenna_imt.bs_normalization = False - self.param.antenna_imt.bs_element_pattern = "M2101" - self.param.antenna_imt.bs_minimum_array_gain = -200 - self.param.antenna_imt.bs_normalization_file = None - self.param.antenna_imt.bs_element_max_g = 10 - self.param.antenna_imt.bs_element_phi_3db = 80 - self.param.antenna_imt.bs_element_theta_3db = 80 - self.param.antenna_imt.bs_element_am = 25 - self.param.antenna_imt.bs_element_sla_v = 25 - self.param.antenna_imt.bs_n_rows = 16 - self.param.antenna_imt.bs_n_columns = 16 - self.param.antenna_imt.bs_element_horiz_spacing = 1 - self.param.antenna_imt.bs_element_vert_spacing = 1 - self.param.antenna_imt.bs_multiplication_factor = 12 - self.param.antenna_imt.bs_downtilt = 10 - - self.param.antenna_imt.ue_element_pattern = "M2101" - self.param.antenna_imt.ue_normalization = False - self.param.antenna_imt.ue_minimum_array_gain = -200 - self.param.antenna_imt.ue_normalization_file = None - self.param.antenna_imt.ue_element_max_g = 5 - self.param.antenna_imt.ue_element_phi_3db = 65 - self.param.antenna_imt.ue_element_theta_3db = 65 - self.param.antenna_imt.ue_element_am = 30 - self.param.antenna_imt.ue_element_sla_v = 30 - self.param.antenna_imt.ue_n_rows = 2 - self.param.antenna_imt.ue_n_columns = 1 - self.param.antenna_imt.ue_element_horiz_spacing = 0.5 - self.param.antenna_imt.ue_element_vert_spacing = 0.5 - self.param.antenna_imt.ue_multiplication_factor = 12 + + self.param.imt.bs.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.ue.antenna.adjacent_antenna_model = "SINGLE_ELEMENT" + self.param.imt.bs.antenna.normalization = False + self.param.imt.bs.antenna.element_pattern = "M2101" + self.param.imt.bs.antenna.minimum_array_gain = -200 + self.param.imt.bs.antenna.normalization_file = None + self.param.imt.bs.antenna.element_max_g = 10 + self.param.imt.bs.antenna.element_phi_3db = 80 + self.param.imt.bs.antenna.element_theta_3db = 80 + self.param.imt.bs.antenna.element_am = 25 + self.param.imt.bs.antenna.element_sla_v = 25 + self.param.imt.bs.antenna.n_rows = 16 + self.param.imt.bs.antenna.n_columns = 16 + self.param.imt.bs.antenna.element_horiz_spacing = 1 + self.param.imt.bs.antenna.element_vert_spacing = 1 + self.param.imt.bs.antenna.multiplication_factor = 12 + self.param.imt.bs.antenna.downtilt = 10 + + self.param.imt.ue.antenna.element_pattern = "M2101" + self.param.imt.ue.antenna.normalization = False + self.param.imt.ue.antenna.minimum_array_gain = -200 + self.param.imt.ue.antenna.normalization_file = None + self.param.imt.ue.antenna.element_max_g = 5 + self.param.imt.ue.antenna.element_phi_3db = 65 + self.param.imt.ue.antenna.element_theta_3db = 65 + self.param.imt.ue.antenna.element_am = 30 + self.param.imt.ue.antenna.element_sla_v = 30 + self.param.imt.ue.antenna.n_rows = 2 + self.param.imt.ue.antenna.n_columns = 1 + self.param.imt.ue.antenna.element_horiz_spacing = 0.5 + self.param.imt.ue.antenna.element_vert_spacing = 0.5 + self.param.imt.ue.antenna.multiplication_factor = 12 self.param.fss_ss.frequency = 10000 self.param.fss_ss.bandwidth = 100 @@ -122,7 +123,7 @@ def setUp(self): self.param.fss_ss.antenna_pattern = "OMNI" self.param.fss_ss.imt_altitude = 1000 self.param.fss_ss.imt_lat_deg = -23.5629739 - self.param.fss_ss.imt_long_diff_deg = (-46.6555132-75) + self.param.fss_ss.imt_long_diff_deg = (-46.6555132 - 75) self.param.fss_ss.channel_model = "FSPL" self.param.fss_ss.line_of_sight_prob = 0.01 self.param.fss_ss.surf_water_vapour_density = 7.5 @@ -130,8 +131,6 @@ def setUp(self): self.param.fss_ss.time_ratio = 0.5 self.param.fss_ss.antenna_l_s = -20 self.param.fss_ss.acs = 0 - self.param.fss_ss.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_ss.EARTH_RADIUS = 6371000 self.param.fss_es.x = -5000 self.param.fss_es.y = 0 @@ -149,30 +148,29 @@ def setUp(self): self.param.fss_es.channel_model = "FSPL" self.param.fss_es.line_of_sight_prob = 1 self.param.fss_es.acs = 0 - self.param.fss_es.BOLTZMANN_CONSTANT = 1.38064852e-23 - self.param.fss_es.EARTH_RADIUS = 6371000 - self.param.ras.x = -5000 - self.param.ras.y = 0 + self.param.ras.geometry.location.type = "FIXED" + self.param.ras.geometry.location.fixed.x = -5000 + self.param.ras.geometry.location.fixed.y = 0 self.param.ras.height = 10 - self.param.ras.elevation = 20 - self.param.ras.azimuth = 0 - self.param.ras.frequency = 10000 + self.param.ras.geometry.elevation.fixed = 20 + self.param.ras.geometry.azimuth.fixed = 0 + self.param.ras.geometry.elevation.type = "FIXED" + self.param.ras.geometry.azimuth.type = "FIXED" + self.param.ras.frequency = 1000 self.param.ras.bandwidth = 100 - self.param.ras.antenna_noise_temperature = 50 - self.param.ras.receiver_noise_temperature = 50 - self.param.ras.antenna_gain = 50 + self.param.ras.noise_temperature = 100 + self.param.ras.antenna.gain = 50 self.param.ras.antenna_efficiency = 0.7 - self.param.ras.diameter = 10 - self.param.ras.acs = 0 - self.param.ras.antenna_pattern = "OMNI" + self.param.ras.adjacent_ch_selectivity = 0 + self.param.ras.tx_power_density = -500 + self.param.ras.antenna.pattern = "OMNI" self.param.ras.channel_model = "FSPL" self.param.ras.line_of_sight_prob = 1 self.param.ras.BOLTZMANN_CONSTANT = 1.38064852e-23 self.param.ras.EARTH_RADIUS = 6371000 self.param.ras.SPEED_OF_LIGHT = 299792458 - def test_simulation_2bs_4ue_ss(self): self.param.general.system = "FSS_SS" @@ -187,61 +185,66 @@ def test_simulation_2bs_4ue_ss(self): self.assertTrue(self.simulation.co_channel) self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) # test connection method self.simulation.connect_ue_to_bs() - self.assertEqual(self.simulation.link, {0: [0,1], 1: [2,3]}) + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} # We do not test the selection method here because in this specific # scenario we do not want to change the order of the UE's self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) - coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23], - [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]]) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) + coupling_loss_imt = np.array([[88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23], + [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23]]) npt.assert_allclose(self.simulation.coupling_loss_imt, coupling_loss_imt, atol=1e-2) # test scheduler and bandwidth allocation self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) - npt.assert_allclose(self.simulation.ue.bandwidth, bandwidth_per_ue*np.ones(4), atol=1e-2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) + npt.assert_allclose(self.simulation.ue.bandwidth, + bandwidth_per_ue * np.ones(4), atol=1e-2) # there is no power control, so UE's will transmit at maximum power self.simulation.power_control() tx_power = 20 - npt.assert_allclose(self.simulation.ue.tx_power, tx_power*np.ones(4)) + npt.assert_allclose(self.simulation.ue.tx_power, tx_power * np.ones(4)) # test method that calculates SINR self.simulation.calculate_sinr() # check BS received power - rx_power = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,0:2]), - 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,2:4])} + rx_power = {0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 0:2]), + 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 2:4])} npt.assert_allclose(self.simulation.bs.rx_power[0], rx_power[0], atol=1e-2) @@ -250,8 +253,8 @@ def test_simulation_2bs_4ue_ss(self): atol=1e-2) # check BS received interference - rx_interference = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,2:4]), - 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,0:2])} + rx_interference = {0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 2:4]), + 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 0:2])} npt.assert_allclose(self.simulation.bs.rx_interference[0], rx_interference[0], @@ -261,14 +264,15 @@ def test_simulation_2bs_4ue_ss(self): atol=1e-2) # check BS thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7 + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7 npt.assert_allclose(self.simulation.bs.thermal_noise, thermal_noise, atol=1e-2) # check BS thermal noise + interference - total_interference = { 0: 10*np.log10(np.power(10, 0.1*rx_interference[0]) + np.power(10, 0.1*thermal_noise)), - 1: 10*np.log10(np.power(10, 0.1*rx_interference[1]) + np.power(10, 0.1*thermal_noise))} + total_interference = {0: 10 * np.log10(np.power(10, 0.1 * rx_interference[0]) + np.power(10, 0.1 * thermal_noise)), + 1: 10 * np.log10(np.power(10, 0.1 * rx_interference[1]) + np.power(10, 0.1 * thermal_noise))} npt.assert_allclose(self.simulation.bs.total_interference[0], total_interference[0], atol=1e-2) @@ -292,28 +296,32 @@ def test_simulation_2bs_4ue_ss(self): rx_power[1] - total_interference[1], atol=1e-2) - self.simulation.system = StationFactory.generate_fss_space_station(self.param.fss_ss) + self.simulation.system = StationFactory.generate_fss_space_station( + self.param.fss_ss) self.simulation.system.x = np.array([0]) self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.fss_ss.altitude]) - # test the method that calculates interference from IMT UE to FSS space station + # test the method that calculates interference from IMT UE to FSS space + # station self.simulation.calculate_external_interference() # check coupling loss - coupling_loss_imt_system = np.array([213.52-51-10, 213.52-51-11, 213.52-51-22, 213.52-51-23]) + coupling_loss_imt_system = np.array( + [213.52 - 51 - 10, 213.52 - 51 - 11, 213.52 - 51 - 22, 213.52 - 51 - 23]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) # check interference generated by UE to FSS space station interference_ue = tx_power - coupling_loss_imt_system - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference_ue))) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference_ue))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # check FSS space station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*950*100*1e3*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 950 * 100 * 1e3 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) @@ -323,7 +331,6 @@ def test_simulation_2bs_4ue_ss(self): rx_interference - thermal_noise, delta=.01) - def test_simulation_2bs_4ue_es(self): self.param.general.system = "FSS_ES" @@ -336,39 +343,42 @@ def test_simulation_2bs_4ue_es(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} # We do not test the selection method here because in this specific # scenario we do not want to change the order of the UE's self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, self.simulation.param_system, + random_number_gen) self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) self.simulation.power_control() self.simulation.calculate_sinr() @@ -376,15 +386,15 @@ def test_simulation_2bs_4ue_es(self): tx_power = 20 # check coupling loss IMT - coupling_loss_imt = np.array([[88.47-1-10, 99.35-1-11, 103.27-1-22, 107.05-1-23], - [107.55-2-10, 104.72-2-11, 101.53-2-22, 91.99-2-23]]) + coupling_loss_imt = np.array([[88.68 - 1 - 10, 99.36 - 1 - 11, 103.28 - 1 - 22, 107.06 - 1 - 23], + [107.55 - 2 - 10, 104.73 - 2 - 11, 101.54 - 2 - 22, 92.08 - 2 - 23]]) npt.assert_allclose(self.simulation.coupling_loss_imt, coupling_loss_imt, atol=1e-2) # check BS received power - rx_power = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,0:2]), - 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,2:4])} + rx_power = {0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 0:2]), + 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 2:4])} npt.assert_allclose(self.simulation.bs.rx_power[0], rx_power[0], atol=1e-2) @@ -393,8 +403,8 @@ def test_simulation_2bs_4ue_es(self): atol=1e-2) # check BS received interference - rx_interference = { 0: np.array([tx_power, tx_power] - coupling_loss_imt[0,2:4]), - 1: np.array([tx_power, tx_power] - coupling_loss_imt[1,0:2])} + rx_interference = {0: np.array([tx_power, tx_power] - coupling_loss_imt[0, 2:4]), + 1: np.array([tx_power, tx_power] - coupling_loss_imt[1, 0:2])} npt.assert_allclose(self.simulation.bs.rx_interference[0], rx_interference[0], @@ -404,14 +414,15 @@ def test_simulation_2bs_4ue_es(self): atol=1e-2) # check BS thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7 + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7 npt.assert_allclose(self.simulation.bs.thermal_noise, thermal_noise, atol=1e-2) # check BS thermal noise + interference - total_interference = { 0: 10*np.log10(np.power(10, 0.1*rx_interference[0]) + np.power(10, 0.1*thermal_noise)), - 1: 10*np.log10(np.power(10, 0.1*rx_interference[1]) + np.power(10, 0.1*thermal_noise))} + total_interference = {0: 10 * np.log10(np.power(10, 0.1 * rx_interference[0]) + np.power(10, 0.1 * thermal_noise)), + 1: 10 * np.log10(np.power(10, 0.1 * rx_interference[1]) + np.power(10, 0.1 * thermal_noise))} npt.assert_allclose(self.simulation.bs.total_interference[0], total_interference[0], atol=1e-2) @@ -435,7 +446,8 @@ def test_simulation_2bs_4ue_es(self): rx_power[1] - total_interference[1], atol=1e-2) - self.simulation.system = StationFactory.generate_fss_earth_station(self.param.fss_es, random_number_gen) + self.simulation.system = 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]) @@ -444,15 +456,16 @@ def test_simulation_2bs_4ue_es(self): self.simulation.calculate_sinr_ext() # coupling loss FSS_ES <-> IMT BS - coupling_loss_imt_system = np.array([124.47-50-1, 124.47-50-1, 125.29-50-2, 125.29-50-2]) + coupling_loss_imt_system = np.array( + [124.47 - 50 - 1, 124.47 - 50 - 1, 125.29 - 50 - 2, 125.29 - 50 - 2]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) # external interference - system_tx_power = -60 + 10*math.log10(bandwidth_per_ue*1e6) + 30 - ext_interference = { 0: system_tx_power - coupling_loss_imt_system[0:2], - 1: system_tx_power - coupling_loss_imt_system[2:4]} + system_tx_power = -60 + 10 * math.log10(bandwidth_per_ue * 1e6) + 30 + ext_interference = {0: system_tx_power - coupling_loss_imt_system[0:2], + 1: system_tx_power - coupling_loss_imt_system[2:4]} npt.assert_allclose(self.simulation.bs.ext_interference[0], ext_interference[0], atol=1e-2) @@ -461,10 +474,10 @@ def test_simulation_2bs_4ue_es(self): atol=1e-2) # SINR with external interference - interference = { 0: 10*np.log10(np.power(10, 0.1*total_interference[0]) \ - + np.power(10, 0.1*ext_interference[0])), - 1: 10*np.log10(np.power(10, 0.1*total_interference[1]) \ - + np.power(10, 0.1*ext_interference[1]))} + interference = {0: 10 * np.log10(np.power(10, 0.1 * total_interference[0]) + + np.power(10, 0.1 * ext_interference[0])), + 1: 10 * np.log10(np.power(10, 0.1 * total_interference[1]) + + np.power(10, 0.1 * ext_interference[1]))} npt.assert_allclose(self.simulation.bs.sinr_ext[0], rx_power[0] - interference[0], @@ -485,27 +498,29 @@ def test_simulation_2bs_4ue_es(self): self.simulation.calculate_external_interference() # coupling loss - coupling_loss_imt_system = np.array([128.55-50-10, 128.76-50-11, 128.93-50-22, 129.17-50-23]) + coupling_loss_imt_system = np.array( + [128.55 - 50 - 10, 128.76 - 50 - 11, 128.93 - 50 - 22, 129.17 - 50 - 23]) npt.assert_allclose(self.simulation.coupling_loss_imt_system, coupling_loss_imt_system, atol=1e-2) # interference interference = tx_power - coupling_loss_imt_system - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # check FSS Earth station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) # check INR at FSS Earth station self.assertAlmostEqual(self.simulation.system.inr, - np.array([ rx_interference - thermal_noise ]), + np.array([rx_interference - thermal_noise]), delta=.01) def test_simulation_2bs_4ue_ras(self): @@ -520,92 +535,105 @@ def test_simulation_2bs_4ue_ras(self): random_number_gen = np.random.RandomState() self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.bs.antenna = np.array([AntennaOmni(1), AntennaOmni(2)]) self.simulation.bs.active = np.ones(2, dtype=bool) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([20, 70, 110, 170]) - self.simulation.ue.y = np.array([ 0, 0, 0, 0]) - self.simulation.ue.antenna = np.array([AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) + self.simulation.ue.y = np.array([0, 0, 0, 0]) + self.simulation.ue.antenna = np.array( + [AntennaOmni(10), AntennaOmni(11), AntennaOmni(22), AntennaOmni(23)]) self.simulation.ue.active = np.ones(4, dtype=bool) self.simulation.connect_ue_to_bs() self.simulation.select_ue(random_number_gen) - self.simulation.link = {0:[0,1],1:[2,3]} + self.simulation.link = {0: [0, 1], 1: [2, 3]} self.simulation.propagation_imt = PropagationFactory.create_propagation(self.param.imt.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) + self.simulation.propagation_system = PropagationFactory.create_propagation(self.param.fss_ss.channel_model, - self.param, random_number_gen) + self.param, + self.simulation.param_system, + random_number_gen) # test coupling loss method - self.simulation.coupling_loss_imt = self.simulation.calculate_coupling_loss(self.simulation.bs, - self.simulation.ue, - self.simulation.propagation_imt) + self.simulation.coupling_loss_imt = self.simulation.calculate_intra_imt_coupling_loss(self.simulation.ue, + self.simulation.bs) self.simulation.scheduler() - bandwidth_per_ue = math.trunc((1 - 0.1)*100/2) + bandwidth_per_ue = math.trunc((1 - 0.1) * 100 / 2) self.simulation.power_control() self.simulation.calculate_sinr() # check BS thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*290*bandwidth_per_ue*1e3*1e6) + 7 + thermal_noise = 10 * \ + np.log10(1.38064852e-23 * 290 * bandwidth_per_ue * 1e3 * 1e6) + 7 npt.assert_allclose(self.simulation.bs.thermal_noise, thermal_noise, atol=1e-2) # check SINR npt.assert_allclose(self.simulation.bs.sinr[0], - np.array([-57.47 - (-60.27), -67.35 - (-63.05)]), + np.array([-57.47 - (-60.06), -67.35 - (-63.04)]), atol=1e-2) npt.assert_allclose(self.simulation.bs.sinr[1], - np.array([-57.53 - (-75.41), -46.99 - (-71.67)]), + np.array([-57.53 - (-75.40), -46.99 - (-71.57)]), atol=1e-2) # Create system - self.simulation.system = StationFactory.generate_ras_station(self.param.ras) + self.simulation.system = StationFactory.generate_ras_station( + self.param.ras, random_number_gen, None + ) self.simulation.system.x = np.array([-2000]) self.simulation.system.y = np.array([0]) self.simulation.system.height = np.array([self.param.ras.height]) self.simulation.system.antenna[0].effective_area = 54.9779 # Test gain calculation - gains = self.simulation.calculate_gains(self.simulation.system,self.simulation.ue) - npt.assert_equal(gains,np.array([[50, 50, 50, 50]])) + gains = self.simulation.calculate_gains( + self.simulation.system, self.simulation.ue) + npt.assert_equal(gains, np.array([[50, 50, 50, 50]])) # Test external interference self.simulation.calculate_external_interference() npt.assert_allclose(self.simulation.coupling_loss_imt_system, - np.array([125.55-50-10, 125.76-50-11, 125.93-50-22, 126.17-50-23]), + np.array([125.55 - 50 - 10, 125.76 - 50 - 11, + 125.93 - 50 - 22, 126.17 - 50 - 23]), atol=1e-2) # Test RAS PFD - interference = 20 - np.array([125.55-50-10, 125.76-50-11, 125.93-50-22, 126.17-50-23]) - rx_interference = 10*math.log10(np.sum(np.power(10, 0.1*interference))) + interference = 20 - \ + np.array([125.55 - 50 - 10, 125.76 - 50 - 11, + 125.93 - 50 - 22, 126.17 - 50 - 23]) + rx_interference = 10 * \ + math.log10(np.sum(np.power(10, 0.1 * interference))) self.assertAlmostEqual(self.simulation.system.rx_interference, rx_interference, delta=.01) # Test RAS PFD - pfd = 10*np.log10(10**(rx_interference/10)/54.9779) + pfd = 10 * np.log10(10**(rx_interference / 10) / 54.9779) self.assertAlmostEqual(self.simulation.system.pfd, - pfd, - delta=.01) + pfd, + delta=.01) # check RAS station thermal noise - thermal_noise = 10*np.log10(1.38064852e-23*100*1e3*100*1e6) + thermal_noise = 10 * np.log10(1.38064852e-23 * 100 * 1e3 * 100 * 1e6) self.assertAlmostEqual(self.simulation.system.thermal_noise, thermal_noise, delta=.01) # check INR at RAS station self.assertAlmostEqual(self.simulation.system.inr, - np.array([ rx_interference - (-98.599) ]), + np.array([rx_interference - (-98.599)]), delta=.01) def test_beamforming_gains(self): @@ -619,50 +647,51 @@ def test_beamforming_gains(self): # Set scenario self.simulation.bs = StationFactory.generate_imt_base_stations(self.param.imt, - self.param.antenna_imt, + self.param.imt.bs.antenna, self.simulation.topology, random_number_gen) self.simulation.ue = StationFactory.generate_imt_ue(self.param.imt, - self.param.antenna_imt, + self.param.imt.ue.antenna, self.simulation.topology, random_number_gen) self.simulation.ue.x = np.array([50.000, 43.301, 150.000, 175.000]) - self.simulation.ue.y = np.array([ 0.000, 25.000, 0.000, 43.301]) + self.simulation.ue.y = np.array([0.000, 25.000, 0.000, 43.301]) # Physical pointing angles - self.assertEqual(self.simulation.bs.antenna[0].azimuth,0) - self.assertEqual(self.simulation.bs.antenna[0].elevation,-10) - self.assertEqual(self.simulation.bs.antenna[1].azimuth,180) - self.assertEqual(self.simulation.bs.antenna[0].elevation,-10) + self.assertEqual(self.simulation.bs.antenna[0].azimuth, 0) + self.assertEqual(self.simulation.bs.antenna[0].elevation, -10) + self.assertEqual(self.simulation.bs.antenna[1].azimuth, 180) + self.assertEqual(self.simulation.bs.antenna[0].elevation, -10) # Change UE pointing self.simulation.ue.azimuth = np.array([180, -90, 90, -90]) self.simulation.ue.elevation = np.array([-30, -15, 15, 30]) - par = self.param.antenna_imt.get_antenna_parameters(StationType.IMT_UE) + par = self.param.imt.ue.antenna.get_antenna_parameters() for i in range(self.simulation.ue.num_stations): self.simulation.ue.antenna[i] = AntennaBeamformingImt(par, self.simulation.ue.azimuth[i], self.simulation.ue.elevation[i]) - self.assertEqual(self.simulation.ue.antenna[0].azimuth,180) - self.assertEqual(self.simulation.ue.antenna[0].elevation,-30) - self.assertEqual(self.simulation.ue.antenna[1].azimuth,-90) - self.assertEqual(self.simulation.ue.antenna[1].elevation,-15) - self.assertEqual(self.simulation.ue.antenna[2].azimuth,90) - self.assertEqual(self.simulation.ue.antenna[2].elevation,15) - self.assertEqual(self.simulation.ue.antenna[3].azimuth,-90) - self.assertEqual(self.simulation.ue.antenna[3].elevation,30) + self.assertEqual(self.simulation.ue.antenna[0].azimuth, 180) + self.assertEqual(self.simulation.ue.antenna[0].elevation, -30) + self.assertEqual(self.simulation.ue.antenna[1].azimuth, -90) + self.assertEqual(self.simulation.ue.antenna[1].elevation, -15) + self.assertEqual(self.simulation.ue.antenna[2].azimuth, 90) + self.assertEqual(self.simulation.ue.antenna[2].elevation, 15) + self.assertEqual(self.simulation.ue.antenna[3].azimuth, -90) + self.assertEqual(self.simulation.ue.antenna[3].elevation, 30) # Simulate connection and selection self.simulation.connect_ue_to_bs() - self.assertEqual(self.simulation.link,{0:[0,1],1:[2,3]}) + self.assertEqual(self.simulation.link, {0: [0, 1], 1: [2, 3]}) # Test BS gains # Test pointing vector - phi, theta = self.simulation.bs.get_pointing_vector_to(self.simulation.ue) - npt.assert_allclose(phi,np.array([[0.0, 30.0, 0.0, 13.898], - [180.0, 170.935, 180.0, 120.0 ]]),atol=eps) - npt.assert_allclose(theta,np.array([[95.143, 95.143, 91.718, 91.430], - [91.718, 91.624, 95.143, 95.143]]),atol=eps) + phi, theta = self.simulation.bs.get_pointing_vector_to( + self.simulation.ue) + npt.assert_allclose(phi, np.array([[0.0, 30.0, 0.0, 13.898], + [180.0, 170.935, 180.0, 120.0]]), atol=eps) + npt.assert_allclose(theta, np.array([[95.143, 95.143, 91.718, 91.430], + [91.718, 91.624, 95.143, 95.143]]), atol=eps) self.simulation.bs_to_ue_phi = phi self.simulation.bs_to_ue_theta = theta @@ -670,47 +699,53 @@ def test_beamforming_gains(self): # method shufles the link dictionary, the order of the beams cannot be # predicted. Thus, the beams need to be added outside of the function self.simulation.ue.active = np.ones(4, dtype=bool) - self.simulation.bs.antenna[0].add_beam(phi[0,0],theta[0,0]) - self.simulation.bs.antenna[0].add_beam(phi[0,1],theta[0,1]) - self.simulation.bs.antenna[1].add_beam(phi[1,2],theta[1,2]) - self.simulation.bs.antenna[1].add_beam(phi[1,3],theta[1,3]) - self.simulation.ue.antenna[0].add_beam(phi[0,0]-180,180-theta[0,0]) - self.simulation.ue.antenna[1].add_beam(phi[0,1]-180,180-theta[0,1]) - self.simulation.ue.antenna[2].add_beam(phi[1,2]-180,180-theta[1,2]) - self.simulation.ue.antenna[3].add_beam(phi[1,3]-180,180-theta[1,3]) - self.simulation.bs_to_ue_beam_rbs = np.array([0, 1, 0, 1],dtype=int) + self.simulation.bs.antenna[0].add_beam(phi[0, 0], theta[0, 0]) + self.simulation.bs.antenna[0].add_beam(phi[0, 1], theta[0, 1]) + self.simulation.bs.antenna[1].add_beam(phi[1, 2], theta[1, 2]) + self.simulation.bs.antenna[1].add_beam(phi[1, 3], theta[1, 3]) + self.simulation.ue.antenna[0].add_beam( + phi[0, 0] - 180, 180 - theta[0, 0]) + self.simulation.ue.antenna[1].add_beam( + phi[0, 1] - 180, 180 - theta[0, 1]) + self.simulation.ue.antenna[2].add_beam( + phi[1, 2] - 180, 180 - theta[1, 2]) + self.simulation.ue.antenna[3].add_beam( + phi[1, 3] - 180, 180 - theta[1, 3]) + self.simulation.bs_to_ue_beam_rbs = np.array([0, 1, 0, 1], dtype=int) # Test beams pointing npt.assert_allclose(self.simulation.bs.antenna[0].beams_list[0], - (0.0,-4.857),atol=eps) + (0.0, -4.857), atol=eps) npt.assert_allclose(self.simulation.bs.antenna[0].beams_list[1], - (29.92,-3.53),atol=eps) + (29.92, -3.53), atol=eps) npt.assert_allclose(self.simulation.bs.antenna[1].beams_list[0], - (0.0,-4.857),atol=eps) + (0.0, -4.857), atol=eps) npt.assert_allclose(self.simulation.bs.antenna[1].beams_list[1], - (-59.60,0.10),atol=eps) + (-59.60, 0.10), atol=eps) npt.assert_allclose(self.simulation.ue.antenna[0].beams_list[0], - (0.0,-35.143),atol=eps) + (0.0, -35.143), atol=eps) npt.assert_allclose(self.simulation.ue.antenna[1].beams_list[0], - (-62.04,-12.44),atol=eps) + (-62.04, -12.44), atol=eps) npt.assert_allclose(self.simulation.ue.antenna[2].beams_list[0], - (-88.66,-4.96),atol=eps) + (-88.66, -4.96), atol=eps) npt.assert_allclose(self.simulation.ue.antenna[3].beams_list[0], - (32.16,20.71),atol=eps) + (32.16, 20.71), atol=eps) # BS Gain matrix - ref_gain = np.array([[ 34.03, 32.37, 8.41, -9.71], - [ 8.41, -8.94, 34.03, 27.42]]) - gain = self.simulation.calculate_gains(self.simulation.bs,self.simulation.ue) - npt.assert_allclose(gain,ref_gain,atol=eps) + ref_gain = np.array([[34.03, 32.37, 8.41, -9.71], + [8.41, -8.94, 34.03, 27.42]]) + gain = self.simulation.calculate_gains( + self.simulation.bs, self.simulation.ue) + npt.assert_allclose(gain, ref_gain, atol=eps) # UE Gain matrix - ref_gain = np.array([[ 4.503, -44.198], - [ -3.362, -11.206], + ref_gain = np.array([[4.503, -44.198], + [-3.362, -11.206], [-14.812, -14.389], - [ -9.726, 3.853]]) - gain = self.simulation.calculate_gains(self.simulation.ue,self.simulation.bs) - npt.assert_allclose(gain,ref_gain,atol=eps) + [-9.726, 3.853]]) + gain = self.simulation.calculate_gains( + self.simulation.ue, self.simulation.bs) + npt.assert_allclose(gain, ref_gain, atol=eps) def test_calculate_imt_ul_tput(self): self.param.general.system = "FSS_SS" @@ -722,13 +757,13 @@ def test_calculate_imt_ul_tput(self): # Test 1 snir = np.array([0.0, 1.0, 15.0, -5.0, 100.00, 200.00]) - ref_tput = np.array([ 0.400, 0.470, 2.011, 0.159, 2.927, 2.927]) + ref_tput = np.array([0.400, 0.470, 2.011, 0.159, 2.927, 2.927]) tput = self.simulation.calculate_imt_tput(snir, - self.param.imt.ul_sinr_min, - self.param.imt.ul_sinr_max, - self.param.imt.ul_attenuation_factor) - npt.assert_allclose(tput,ref_tput,atol=eps) + self.param.imt.uplink.sinr_min, + self.param.imt.uplink.sinr_max, + self.param.imt.uplink.attenuation_factor) + npt.assert_allclose(tput, ref_tput, atol=eps) + if __name__ == '__main__': unittest.main() - diff --git a/tests/test_spectral_mask_3gpp.py b/tests/test_spectral_mask_3gpp.py index ecca99d1b..5a3be9141 100644 --- a/tests/test_spectral_mask_3gpp.py +++ b/tests/test_spectral_mask_3gpp.py @@ -11,8 +11,9 @@ from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp from sharc.support.enumerations import StationType + class SpectalMask3GppTest(unittest.TestCase): - + def setUp(self): # Initialize variables for BS Cat-A mask (3.5 GHz) sta_type = StationType.IMT_BS @@ -20,28 +21,30 @@ def setUp(self): frequency = 3490 bandwidth = 20 spurious = -13 - self.mask_bs_a = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious) - self.mask_bs_a.set_mask(power = p_tx) - + self.mask_bs_a = SpectralMask3Gpp( + sta_type, frequency, bandwidth, spurious) + self.mask_bs_a.set_mask(p_tx) + # Initialize variables for BS Cat-B mask (3.5 GHz) sta_type = StationType.IMT_BS p_tx = 46 frequency = 3490 bandwidth = 20 spurious = -30 - self.mask_bs_b = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious) - self.mask_bs_b.set_mask(power = p_tx) - + self.mask_bs_b = SpectralMask3Gpp( + sta_type, frequency, bandwidth, spurious) + self.mask_bs_b.set_mask(p_tx) + # Initialize variables for UE mask (3.5 GHz) sta_type = StationType.IMT_UE p_tx = 22 frequency = 3490 bandwidth = 20 spurious = -30 - self.mask_ue = SpectralMask3Gpp(sta_type, frequency, bandwidth, spurious) - self.mask_ue.set_mask(power = p_tx) - - + self.mask_ue = SpectralMask3Gpp( + sta_type, frequency, bandwidth, spurious) + self.mask_ue.set_mask(p_tx) + def test_power_calc_bs_a(self): ####################################################################### # BS Cat-A mask @@ -49,132 +52,138 @@ def test_power_calc_bs_a(self): fc = 3502.5 bandwidth = 5 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 7.01, delta = 1e-2) + self.assertAlmostEqual(poob, 7.01, delta=1e-2) ####################################################################### fc = 3507.5 bandwidth = 5 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 2.98, delta = 1e-2) - + self.assertAlmostEqual(poob, 2.98, delta=1e-2) + ####################################################################### fc = 3505 bandwidth = 10 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 8.46, delta = 1e-2) - + self.assertAlmostEqual(poob, 8.46, delta=1e-2) + ####################################################################### fc = 3510 bandwidth = 10 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 3.50, delta = 1e-2) - + self.assertAlmostEqual(poob, 3.50, delta=1e-2) + ####################################################################### fc = 3515 bandwidth = 10 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, -3, delta = 1e-2) + self.assertAlmostEqual(poob, -3, delta=1e-2) ####################################################################### fc = 3520 bandwidth = 20 poob = self.mask_bs_a.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 0.01, delta = 1e-2) - - def test_power_calc_bs_b(self): + self.assertAlmostEqual(poob, 0.01, delta=1e-2) + + def test_power_calc_bs_b(self): ####################################################################### # BS Cat-B mask ####################################################################### fc = 3502.5 bandwidth = 5 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 7.01, delta = 1e-2) + self.assertAlmostEqual(poob, 7.01, delta=1e-2) ####################################################################### fc = 3507.5 bandwidth = 5 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 2.98, delta = 1e-2) - + self.assertAlmostEqual(poob, 2.98, delta=1e-2) + ####################################################################### fc = 3505 bandwidth = 10 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 8.46, delta = 1e-2) - + self.assertAlmostEqual(poob, 8.46, delta=1e-2) + ####################################################################### fc = 3510 bandwidth = 10 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, 3, delta = 1e-2) - + self.assertAlmostEqual(poob, 3, delta=1e-2) + ####################################################################### fc = 3515 bandwidth = 10 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, -20, delta = 1e-2) + self.assertAlmostEqual(poob, -20, delta=1e-2) ####################################################################### fc = 3520 bandwidth = 20 poob = self.mask_bs_b.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, -16.98, delta = 1e-2) - - def test_power_calc_ue(self): + self.assertAlmostEqual(poob, -16.98, delta=1e-2) + + def test_power_calc_ue(self): ####################################################################### # UE mask ####################################################################### fc = 3500.5 bandwidth = 1 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, -21 + 10*np.log10(1/0.03), delta = 1e-2) + self.assertAlmostEqual(poob, -21 + 10 * np.log10(1 / 0.03), delta=1e-2) ####################################################################### fc = 3503 bandwidth = 4 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, -10 + 10*np.log10(4), delta = 1e-2) - + self.assertAlmostEqual(poob, -10 + 10 * np.log10(4), delta=1e-2) + ###################################################################### fc = 3502.5 bandwidth = 5 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, - 10*np.log10(np.power(10, 0.1*(-21 + 10*np.log10(1/0.03))) + np.power(10, 0.1*(-10 + 10*np.log10(4)))), - delta = 1e-2) - + self.assertAlmostEqual(poob, + 10 * np.log10(np.power(10, + 0.1 * (-21 + 10 * np.log10(1 / 0.03))) + np.power(10, + 0.1 * (-10 + 10 * np.log10(4)))), + delta=1e-2) + ####################################################################### fc = 3510 bandwidth = 10 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, - -13 + 10*np.log10(10), - delta = 1e-2) - + self.assertAlmostEqual(poob, + -13 + 10 * np.log10(10), + delta=1e-2) + ###################################################################### fc = 3520 bandwidth = 10 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, - 10*np.log10(np.power(10, 0.1*(-13 + 10*np.log10(5))) + np.power(10, 0.1*(-25 + 10*np.log10(5)))), - delta = 1e-2) + self.assertAlmostEqual(poob, + 10 * np.log10(np.power(10, + 0.1 * (-13 + 10 * np.log10(5))) + np.power(10, + 0.1 * (-25 + 10 * np.log10(5)))), + delta=1e-2) ####################################################################### fc = 3525 bandwidth = 10 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, - 10*np.log10(np.power(10, 0.1*(-25 + 10*np.log10(5))) + np.power(10, 0.1*(-30 + 10*np.log10(5)))), - delta = 1e-2) + self.assertAlmostEqual(poob, + 10 * np.log10(np.power(10, + 0.1 * (-25 + 10 * np.log10(5))) + np.power(10, + 0.1 * (-30 + 10 * np.log10(5)))), + delta=1e-2) ####################################################################### fc = 3600 bandwidth = 50 poob = self.mask_ue.power_calc(fc, bandwidth) - self.assertAlmostEqual(poob, - -30 + 10*np.log10(50), - delta = 1e-2) + self.assertAlmostEqual(poob, + -30 + 10 * np.log10(50), + delta=1e-2) + - if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_spectral_mask_imt.py b/tests/test_spectral_mask_imt.py index bed233744..4d9733e7a 100644 --- a/tests/test_spectral_mask_imt.py +++ b/tests/test_spectral_mask_imt.py @@ -11,8 +11,9 @@ from sharc.mask.spectral_mask_imt import SpectralMaskImt from sharc.support.enumerations import StationType + class SpectalMaskImtTest(unittest.TestCase): - + def setUp(self): # Initialize variables for 40 GHz sta_type = StationType.IMT_BS @@ -20,27 +21,47 @@ def setUp(self): freq = 43000 band = 200 spurious = -13 - + # Create mask for 40 GHz self.mask_bs_40GHz = SpectralMaskImt(sta_type, freq, band, spurious) - self.mask_bs_40GHz.set_mask(power = p_tx) - + self.mask_bs_40GHz.set_mask(p_tx) + # Initialize variables for 40 GHz sta_type = StationType.IMT_BS p_tx = 28.1 freq = 24350 band = 200 - + # Create mask for BS at 26 GHz self.mask_bs_26GHz = SpectralMaskImt(sta_type, freq, band, spurious) - self.mask_bs_26GHz.set_mask(power = p_tx) + self.mask_bs_26GHz.set_mask(p_tx) # Create mask for UE at 26 GHz sta_type = StationType.IMT_UE self.mask_ue_26GHz = SpectralMaskImt(sta_type, freq, band, spurious) - self.mask_ue_26GHz.set_mask(power = p_tx) - - + self.mask_ue_26GHz.set_mask(p_tx) + + # Initialize variables for 9GHz -13dBm/MHz + sta_type = StationType.IMT_BS + p_tx = 28.1 + freq = 9000 + band = 200 + + # Create mask for BS at 9 GHz + self.mask_bs_9GHz = SpectralMaskImt(sta_type, freq, band, -13) + self.mask_bs_9GHz.set_mask(p_tx) + + # Initialize variables for 9GHz -30dBm/MHz + sta_type = StationType.IMT_BS + p_tx = 28.1 + freq = 9000 + band = 200 + + # Create mask for BS at 9 GHz and spurious emission at -30dBm/MHz + self.mask_bs_9GHz_30_spurious = SpectralMaskImt( + sta_type, freq, band, -30) + self.mask_bs_9GHz_30_spurious.set_mask(p_tx) + def test_power_calc(self): ####################################################################### # Testing mask for 40 GHz @@ -52,29 +73,29 @@ def test_power_calc(self): with self.assertWarns(RuntimeWarning): poob = self.mask_bs_40GHz.power_calc(fc, band) self.assertAlmostEqual(poob, -np.inf, delta=1e-2) - + # Test 2 fc = 43300 band = 600 poob = self.mask_bs_40GHz.power_calc(fc, band) self.assertAlmostEqual(poob, 11.8003, delta=1e-2) - + # Test 3 fc = 43000 band = 1200 poob = self.mask_bs_40GHz.power_calc(fc, band) self.assertAlmostEqual(poob, 14.8106, delta=1e-2) - + # Test 4 fc = 45000 band = 1000 poob = self.mask_bs_40GHz.power_calc(fc, band) - self.assertAlmostEqual(poob, 17, delta=1e-2) - + self.assertAlmostEqual(poob, 17, delta=1e-2) + ####################################################################### # Testing mask for 26 GHz ####################################################################### - + # Test 1 - BS fc = 23800 band = 400 @@ -92,7 +113,7 @@ def test_power_calc(self): band = 400 poob = self.mask_bs_26GHz.power_calc(fc, band) self.assertAlmostEqual(poob, 13.02, delta=1e-2) - + # Test 1 - UE fc = 23800 band = 400 @@ -111,7 +132,54 @@ def test_power_calc(self): poob = self.mask_ue_26GHz.power_calc(fc, band) self.assertAlmostEqual(poob, 13.02, delta=1e-2) + ####################################################################### + # Testing mask for 9 GHz and -13dBm/MHz spurious emissions (alternative mask, for BS only) + ####################################################################### + + # Test 1 - BS + fc = 9200 + band = 200 + poob = self.mask_bs_9GHz.power_calc(fc, band) + # for this test to pass, alternative mask sample size needed to be at + # least approx. 30 for OOB start + self.assertAlmostEqual(poob, 10.09, delta=1e-2) + + # Test 2 - BS + fc = 8800 + band = 200 + poob = self.mask_bs_9GHz.power_calc(fc, band) + self.assertAlmostEqual(poob, 10.09, delta=1e-2) + + # Test 3 - BS + fc = 9400 + band = 400 + poob = self.mask_bs_9GHz.power_calc(fc, band) + self.assertAlmostEqual(poob, 13.02, delta=1e-2) + + ####################################################################### + # Testing mask for 9 GHz and -30dBm/MHz spurious emissions (alternative mask, for BS only) + ####################################################################### + + # Test 1 - BS + fc = 9200 + band = 200 + poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band) + # for this test to pass, 'ALTERNATIVE_MASK_DIAGONAL_SAMPLESIZE' needed + # to be at least approx. 40 + self.assertAlmostEqual(poob, 8.26, delta=1e-2) + + # Test 2 - BS + fc = 8800 + band = 200 + poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band) + self.assertAlmostEqual(poob, 8.26, delta=1e-2) + + # Test 3 - BS + fc = 9400 + band = 400 + poob = self.mask_bs_9GHz_30_spurious.power_calc(fc, band) + self.assertAlmostEqual(poob, 8.14, delta=1e-2) + - if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_station.py b/tests/test_station.py index 3c4256171..365e43d67 100644 --- a/tests/test_station.py +++ b/tests/test_station.py @@ -10,46 +10,48 @@ from sharc.support.enumerations import StationType from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt from sharc.antenna.antenna_omni import AntennaOmni -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.station import Station + class StationTest(unittest.TestCase): def setUp(self): - #Array parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_normalization = False - self.param.bs_normalization_file = None - self.param.bs_element_pattern = "M2101" - self.param.bs_minimum_array_gain = -200 - self.param.bs_element_max_g = 10 - self.param.bs_element_phi_3db = 65 - self.param.bs_element_theta_3db = 75 - self.param.bs_element_am = 35 - self.param.bs_element_sla_v = 25 - self.param.bs_n_rows = 8 - self.param.bs_n_columns = 8 - self.param.bs_element_horiz_spacing = 0.5 - self.param.bs_element_vert_spacing = 0.5 - self.param.bs_multiplication_factor = 12 - self.param.bs_downtilt = 0 - - self.param.ue_element_pattern = "M2101" - self.param.ue_normalization = False - self.param.ue_normalization_file = None - self.param.ue_minimum_array_gain = -200 - self.param.ue_element_max_g = 10 - self.param.ue_element_phi_3db = 75 - self.param.ue_element_theta_3db = 65 - self.param.ue_element_am = 25 - self.param.ue_element_sla_v = 35 - self.param.ue_n_rows = 2 - self.param.ue_n_columns = 2 - self.param.ue_element_horiz_spacing = 0.5 - self.param.ue_element_vert_spacing = 0.5 - self.param.ue_multiplication_factor = 12 + # Array parameters + self.bs_param = ParametersAntennaImt() + self.ue_param = ParametersAntennaImt() + + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.normalization = False + self.bs_param.normalization_file = None + self.bs_param.element_pattern = "M2101" + self.bs_param.minimum_array_gain = -200 + self.bs_param.element_max_g = 10 + self.bs_param.element_phi_3db = 65 + self.bs_param.element_theta_3db = 75 + self.bs_param.element_am = 35 + self.bs_param.element_sla_v = 25 + self.bs_param.n_rows = 8 + self.bs_param.n_columns = 8 + self.bs_param.element_horiz_spacing = 0.5 + self.bs_param.element_vert_spacing = 0.5 + self.bs_param.multiplication_factor = 12 + self.bs_param.downtilt = 0 + + self.ue_param.element_pattern = "M2101" + self.ue_param.normalization = False + self.ue_param.normalization_file = None + self.ue_param.minimum_array_gain = -200 + self.ue_param.element_max_g = 10 + self.ue_param.element_phi_3db = 75 + self.ue_param.element_theta_3db = 65 + self.ue_param.element_am = 25 + self.ue_param.element_sla_v = 35 + self.ue_param.n_rows = 2 + self.ue_param.n_columns = 2 + self.ue_param.element_horiz_spacing = 0.5 + self.ue_param.element_vert_spacing = 0.5 + self.ue_param.multiplication_factor = 12 self.station = Station() self.station.id = 1 @@ -59,8 +61,8 @@ def setUp(self): self.station.height = 6 self.station.tx_power = 20 self.station.rx_power = -3 - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.station.antenna = AntennaBeamformingImt(par,300,-10) + par = self.bs_param.get_antenna_parameters() + self.station.antenna = AntennaBeamformingImt(par, 300, -10) self.station2 = Station() self.station2.id = 1 @@ -70,8 +72,8 @@ def setUp(self): self.station2.height = 6 self.station2.tx_power = 17 self.station2.rx_power = 9 - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.station2.antenna = AntennaBeamformingImt(par,270,2) + par = self.ue_param.get_antenna_parameters() + self.station2.antenna = AntennaBeamformingImt(par, 270, 2) self.station3 = Station() self.station3.id = 2 @@ -87,7 +89,7 @@ def test_id(self): self.assertEqual(self.station.id, 1) def test_station_type(self): - self.assertEqual(self.station.station_type,StationType.IMT_BS) + self.assertEqual(self.station.station_type, StationType.IMT_BS) def test_x(self): self.assertEqual(self.station.x, 10) diff --git a/tests/test_station_factory.py b/tests/test_station_factory.py index 96cf8cf39..ffe4b79e2 100644 --- a/tests/test_station_factory.py +++ b/tests/test_station_factory.py @@ -9,16 +9,89 @@ import numpy as np import numpy.testing as npt +from sharc.parameters.imt.parameters_imt import ParametersImt from sharc.station_factory import StationFactory +from sharc.topology.topology_ntn import TopologyNTN + class StationFactoryTest(unittest.TestCase): - + """Test cases for StationFactory.""" + def setUp(self): - pass - + self.station_factory = StationFactory() + def test_generate_imt_base_stations(self): pass - - + + def test_generate_imt_base_stations_ntn(self): + """Test for IMT-NTN space station generation.""" + seed = 100 + rng = np.random.RandomState(seed) + + param_imt = ParametersImt() + param_imt.topology.type = "NTN" + + # Paramters for IMT-NTN + param_imt.topology.ntn.bs_height = 1200000 # meters + param_imt.topology.ntn.cell_radius = 45000 # meters + param_imt.topology.ntn.bs_azimuth = 60 # degrees + param_imt.topology.ntn.bs_elevation = 45 # degrees + param_imt.topology.ntn.num_sectors = 1 + + ntn_topology = TopologyNTN( + param_imt.topology.ntn.intersite_distance, + param_imt.topology.ntn.cell_radius, + param_imt.topology.ntn.bs_height, + param_imt.topology.ntn.bs_azimuth, + param_imt.topology.ntn.bs_elevation, + param_imt.topology.ntn.num_sectors) + + ntn_topology.calculate_coordinates() + ntn_bs = StationFactory.generate_imt_base_stations(param_imt, param_imt.bs.antenna, ntn_topology, rng) + npt.assert_equal(ntn_bs.height, param_imt.topology.ntn.bs_height) + # the azimuth seen from BS antenna + npt.assert_almost_equal(ntn_bs.azimuth[0], param_imt.topology.ntn.bs_azimuth - 180, 1e-3) + # Elevation w.r.t to xy plane + npt.assert_almost_equal(ntn_bs.elevation[0], -45.0, 1e-2) + npt.assert_almost_equal(ntn_bs.x, param_imt.topology.ntn.bs_height * + np.tan(np.radians(param_imt.topology.ntn.bs_elevation)) * + np.cos(np.radians(param_imt.topology.ntn.bs_azimuth)), 1e-2) + + def test_generate_imt_ue_outdoor_ntn(self): + """Basic test for IMT UE NTN generation.""" + seed = 100 + rng = np.random.RandomState(seed) + + # Parameters used for IMT-NTN and UE distribution + param_imt = ParametersImt() + param_imt.topology.type = "NTN" + param_imt.ue.azimuth_range = (-180, 180) + param_imt.ue.distribution_type = "ANGLE_AND_DISTANCE" + param_imt.ue.distribution_azimuth = "UNIFORM" + param_imt.ue.distribution_distance = "UNIFORM" + param_imt.ue.k = 1000 + + # Paramters for IMT-NTN + param_imt.topology.ntn.bs_height = 1200000 # meters + param_imt.topology.ntn.cell_radius = 45000 # meters + param_imt.topology.ntn.bs_azimuth = 60 # degrees + param_imt.topology.ntn.bs_elevation = 45 # degrees + param_imt.topology.ntn.num_sectors = 1 + + ntn_topology = TopologyNTN( + param_imt.topology.ntn.intersite_distance, + param_imt.topology.ntn.cell_radius, + param_imt.topology.ntn.bs_height, + param_imt.topology.ntn.bs_azimuth, + param_imt.topology.ntn.bs_elevation, + param_imt.topology.ntn.num_sectors) + + ntn_topology.calculate_coordinates() + ntn_ue = StationFactory.generate_imt_ue_outdoor(param_imt, param_imt.ue.antenna, rng, ntn_topology) + dist = np.sqrt(ntn_ue.x**2 + ntn_ue.y**2) + # test if the maximum distance is close to the cell radius within a 100km range + npt.assert_almost_equal(dist.max(), param_imt.topology.ntn.cell_radius, -2) + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_station_manager.py b/tests/test_station_manager.py index a1dbf64a0..6b1c1ef10 100644 --- a/tests/test_station_manager.py +++ b/tests/test_station_manager.py @@ -11,7 +11,7 @@ from sharc.support.enumerations import StationType from sharc.antenna.antenna_beamforming_imt import AntennaBeamformingImt -from sharc.parameters.parameters_antenna_imt import ParametersAntennaImt +from sharc.parameters.imt.parameters_antenna_imt import ParametersAntennaImt from sharc.station import Station from sharc.station_manager import StationManager @@ -19,42 +19,44 @@ class StationManagerTest(unittest.TestCase): def setUp(self): - #Array parameters - self.param = ParametersAntennaImt() - - self.param.adjacent_antenna_model = "SINGLE_ELEMENT" - self.param.bs_normalization = False - self.param.bs_normalization_file = None - self.param.bs_element_pattern = "M2101" - self.param.bs_minimum_array_gain = -200 - self.param.bs_downtilt = 0 - - self.param.bs_element_max_g = 10 - self.param.bs_element_phi_3db = 65 - self.param.bs_element_theta_3db = 75 - self.param.bs_element_am = 35 - self.param.bs_element_sla_v = 25 - self.param.bs_n_rows = 8 - self.param.bs_n_columns = 8 - self.param.bs_element_horiz_spacing = 0.5 - self.param.bs_element_vert_spacing = 0.5 - self.param.bs_multiplication_factor = 12 - - self.param.ue_element_pattern = "M2101" - self.param.ue_normalization = False - self.param.ue_normalization_file = None - self.param.ue_minimum_array_gain = -200 - - self.param.ue_element_max_g = 10 - self.param.ue_element_phi_3db = 75 - self.param.ue_element_theta_3db = 65 - self.param.ue_element_am = 25 - self.param.ue_element_sla_v = 35 - self.param.ue_n_rows = 2 - self.param.ue_n_columns = 2 - self.param.ue_element_horiz_spacing = 0.5 - self.param.ue_element_vert_spacing = 0.5 - self.param.ue_multiplication_factor = 12 + # Array parameters + self.bs_param = ParametersAntennaImt() + self.ue_param = ParametersAntennaImt() + + self.ue_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.adjacent_antenna_model = "SINGLE_ELEMENT" + self.bs_param.normalization = False + self.bs_param.normalization_file = None + self.bs_param.element_pattern = "M2101" + self.bs_param.minimum_array_gain = -200 + self.bs_param.downtilt = 0 + + self.bs_param.element_max_g = 10 + self.bs_param.element_phi_3db = 65 + self.bs_param.element_theta_3db = 75 + self.bs_param.element_am = 35 + self.bs_param.element_sla_v = 25 + self.bs_param.n_rows = 8 + self.bs_param.n_columns = 8 + self.bs_param.element_horiz_spacing = 0.5 + self.bs_param.element_vert_spacing = 0.5 + self.bs_param.multiplication_factor = 12 + + self.ue_param.element_pattern = "M2101" + self.ue_param.normalization = False + self.ue_param.normalization_file = None + self.ue_param.minimum_array_gain = -200 + + self.ue_param.element_max_g = 10 + self.ue_param.element_phi_3db = 75 + self.ue_param.element_theta_3db = 65 + self.ue_param.element_am = 25 + self.ue_param.element_sla_v = 35 + self.ue_param.n_rows = 2 + self.ue_param.n_columns = 2 + self.ue_param.element_horiz_spacing = 0.5 + self.ue_param.element_vert_spacing = 0.5 + self.ue_param.multiplication_factor = 12 self.station_manager = StationManager(3) self.station_manager.x = np.array([10, 20, 30]) @@ -64,8 +66,9 @@ def setUp(self): # this is for downlink self.station_manager.tx_power = dict({0: [27, 30], 1: [35], 2: [40]}) self.station_manager.rx_power = np.array([-50, -35, -10]) - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.station_manager.antenna = np.array([AntennaBeamformingImt(par,60,-10), AntennaBeamformingImt(par,180,-10), AntennaBeamformingImt(par,300,-10)]) + par = self.bs_param.get_antenna_parameters() + self.station_manager.antenna = np.array([AntennaBeamformingImt( + par, 60, -10), AntennaBeamformingImt(par, 180, -10), AntennaBeamformingImt(par, 300, -10)]) self.station_manager.station_type = StationType.IMT_BS self.station_manager2 = StationManager(2) @@ -74,10 +77,11 @@ def setUp(self): self.station_manager2.height = np.array([4, 5]) self.station_manager2.intersite_dist = 100.0 # this is for downlink - self.station_manager2.tx_power = dict({0: [25], 1: [28,35]}) + self.station_manager2.tx_power = dict({0: [25], 1: [28, 35]}) self.station_manager2.rx_power = np.array([-50, -35]) - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.station_manager2.antenna = np.array([AntennaBeamformingImt(par,0,-5), AntennaBeamformingImt(par,180,-5)]) + par = self.bs_param.get_antenna_parameters() + self.station_manager2.antenna = np.array( + [AntennaBeamformingImt(par, 0, -5), AntennaBeamformingImt(par, 180, -5)]) self.station_manager2.station_type = StationType.IMT_BS self.station_manager3 = StationManager(1) @@ -87,9 +91,10 @@ def setUp(self): self.station_manager3.intersite_dist = 100.0 # this is for uplink self.station_manager3.tx_power = 22 - self.station_manager3.rx_power = np.array([-50,-35]) - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.station_manager3.antenna = np.array([AntennaBeamformingImt(par,0,-30), AntennaBeamformingImt(par,35,45)]) + self.station_manager3.rx_power = np.array([-50, -35]) + par = self.ue_param.get_antenna_parameters() + self.station_manager3.antenna = np.array( + [AntennaBeamformingImt(par, 0, -30), AntennaBeamformingImt(par, 35, 45)]) self.station_manager3.station_type = StationType.IMT_UE self.station = Station() @@ -99,8 +104,8 @@ def setUp(self): self.station.height = 1 self.station.tx_power = 30 self.station.rx_power = -50 - par = self.param.get_antenna_parameters(StationType.IMT_UE) - self.station.antenna = AntennaBeamformingImt(par,100,-10) + par = self.ue_param.get_antenna_parameters() + self.station.antenna = AntennaBeamformingImt(par, 100, -10) self.station.station_type = StationType.IMT_UE self.station2 = Station() @@ -110,11 +115,10 @@ def setUp(self): self.station2.height = 2 self.station2.tx_power = 35 self.station2.rx_power = -35 - par = self.param.get_antenna_parameters(StationType.IMT_BS) - self.station2.antenna = AntennaBeamformingImt(par,-90,-15) + par = self.bs_param.get_antenna_parameters() + self.station2.antenna = AntennaBeamformingImt(par, -90, -15) self.station2.station_type = StationType.IMT_BS - def test_num_stations(self): self.assertEqual(self.station_manager.num_stations, 3) self.assertEqual(self.station_manager2.num_stations, 2) @@ -122,85 +126,95 @@ def test_num_stations(self): def test_station_type(self): self.assertEqual(self.station_manager.station_type, StationType.IMT_BS) - self.assertEqual(self.station_manager2.station_type, StationType.IMT_BS) - self.assertEqual(self.station_manager3.station_type, StationType.IMT_UE) + self.assertEqual( + self.station_manager2.station_type, + StationType.IMT_BS) + self.assertEqual( + self.station_manager3.station_type, + StationType.IMT_UE) def test_x(self): # get a single value from the original array self.assertEqual(self.station_manager.x[0], 10) # get two specific values - npt.assert_array_equal(self.station_manager.x[[1,2]], [20,30]) + npt.assert_array_equal(self.station_manager.x[[1, 2]], [20, 30]) # get values in reverse order - npt.assert_array_equal(self.station_manager.x[[2,1,0]], [30,20,10]) + npt.assert_array_equal(self.station_manager.x[[2, 1, 0]], [30, 20, 10]) # get all values (no need to specify the id's) - npt.assert_array_equal(self.station_manager.x, [10,20,30]) + npt.assert_array_equal(self.station_manager.x, [10, 20, 30]) # set a single value and get it self.station_manager.x[0] = 8 - npt.assert_array_equal(self.station_manager.x[[0,1]], [8,20]) + npt.assert_array_equal(self.station_manager.x[[0, 1]], [8, 20]) # set two values and then get all values - self.station_manager.x[[1,2]] = [16,32] - npt.assert_array_equal(self.station_manager.x, [8,16,32]) + self.station_manager.x[[1, 2]] = [16, 32] + npt.assert_array_equal(self.station_manager.x, [8, 16, 32]) def test_y(self): # get a single value from the original array self.assertEqual(self.station_manager.y[0], 15) # get two specific values - npt.assert_array_equal(self.station_manager.y[[1,2]], [25,35]) + npt.assert_array_equal(self.station_manager.y[[1, 2]], [25, 35]) # get values in reverse order - npt.assert_array_equal(self.station_manager.y[[2,1,0]], [35,25,15]) + npt.assert_array_equal(self.station_manager.y[[2, 1, 0]], [35, 25, 15]) # get all values (no need to specify the id's) - npt.assert_array_equal(self.station_manager.y, [15,25,35]) + npt.assert_array_equal(self.station_manager.y, [15, 25, 35]) # set a single value and get it self.station_manager.y[1] = 9 - npt.assert_array_equal(self.station_manager.y[[0,1]], [15,9]) + npt.assert_array_equal(self.station_manager.y[[0, 1]], [15, 9]) # set two values and then get all values - self.station_manager.y[[0,2]] = [7,21] - npt.assert_array_equal(self.station_manager.y, [7,9,21]) + self.station_manager.y[[0, 2]] = [7, 21] + npt.assert_array_equal(self.station_manager.y, [7, 9, 21]) def test_height(self): # get a single value from the original array self.assertEqual(self.station_manager.height[0], 1) # get two specific values - npt.assert_array_equal(self.station_manager.height[[0,2]], [1,3]) + npt.assert_array_equal(self.station_manager.height[[0, 2]], [1, 3]) # get values in reverse order - npt.assert_array_equal(self.station_manager.height[[2,1,0]], [3,2,1]) + npt.assert_array_equal( + self.station_manager.height[[2, 1, 0]], [3, 2, 1]) # get all values (no need to specify the id's) - npt.assert_array_equal(self.station_manager.height, [1,2,3]) + npt.assert_array_equal(self.station_manager.height, [1, 2, 3]) # set a single value and get it self.station_manager.height[1] = 7 - npt.assert_array_equal(self.station_manager.height[[1,2]], [7,3]) + npt.assert_array_equal(self.station_manager.height[[1, 2]], [7, 3]) # set two values and then get all values - self.station_manager.height[[0,2]] = [5,4] - npt.assert_array_equal(self.station_manager.height, [5,7,4]) + self.station_manager.height[[0, 2]] = [5, 4] + npt.assert_array_equal(self.station_manager.height, [5, 7, 4]) def test_tx_power(self): # get a single value from the original array - self.assertEqual(self.station_manager.tx_power[0], [27,30]) + self.assertEqual(self.station_manager.tx_power[0], [27, 30]) self.assertEqual(self.station_manager.tx_power[1], [35]) # get all values (no need to specify the id's) - npt.assert_array_equal(self.station_manager.tx_power, dict({0: [27, 30], 1: [35], 2: [40]})) + npt.assert_array_equal(self.station_manager.tx_power, dict( + {0: [27, 30], 1: [35], 2: [40]})) # set a single value and get it - self.station_manager.tx_power[0] = [33,38] - npt.assert_array_equal(self.station_manager.tx_power[0], [33,38]) + self.station_manager.tx_power[0] = [33, 38] + npt.assert_array_equal(self.station_manager.tx_power[0], [33, 38]) # set two values and then get all values - self.station_manager.tx_power[2] = [20,25] - npt.assert_array_equal(self.station_manager.tx_power, dict({0: [33,38], 1: [35], 2: [20,25]})) + self.station_manager.tx_power[2] = [20, 25] + npt.assert_array_equal(self.station_manager.tx_power, dict( + {0: [33, 38], 1: [35], 2: [20, 25]})) def test_rx_power(self): # get a single value from the original array self.assertEqual(self.station_manager.rx_power[2], -10) # get two specific values - npt.assert_array_equal(self.station_manager.rx_power[[0,1]], [-50,-35]) + npt.assert_array_equal( + self.station_manager.rx_power[[0, 1]], [-50, -35]) # get values in reverse order - npt.assert_array_equal(self.station_manager.rx_power[[2,1,0]], [-10,-35,-50] ) + npt.assert_array_equal( + self.station_manager.rx_power[[2, 1, 0]], [-10, -35, -50]) # get all values (no need to specify the id's) - npt.assert_array_equal(self.station_manager.rx_power, [-50,-35,-10]) + npt.assert_array_equal(self.station_manager.rx_power, [-50, -35, -10]) # set a single value and get it self.station_manager.rx_power[2] = -15 - npt.assert_array_equal(self.station_manager.rx_power[[2,0]], [-15,-50]) + npt.assert_array_equal( + self.station_manager.rx_power[[2, 0]], [-15, -50]) # set two values and then get all values - self.station_manager.rx_power[[0,1]] = [-60,-30] - npt.assert_array_equal(self.station_manager.rx_power, [-60,-30,-15]) + self.station_manager.rx_power[[0, 1]] = [-60, -30] + npt.assert_array_equal(self.station_manager.rx_power, [-60, -30, -15]) def test_antenna(self): self.assertEqual(self.station_manager.antenna[0].azimuth, 60) @@ -247,7 +261,7 @@ def test_station(self): self.station.id = 1 self.assertTrue(self.station != self.station_manager.get_station(0)) # test station type - self.assertEqual(self.station_manager.get_station(0).station_type,\ + self.assertEqual(self.station_manager.get_station(0).station_type, StationType.IMT_BS) def test_station_list(self): @@ -256,7 +270,7 @@ def test_station_list(self): self.assertTrue(self.station in station_list) self.assertTrue(self.station2 in station_list) # - station_list = self.station_manager.get_station_list([0,2]) + station_list = self.station_manager.get_station_list([0, 2]) self.assertTrue(self.station in station_list) self.assertTrue(self.station2 not in station_list) # @@ -265,86 +279,93 @@ def test_station_list(self): self.assertTrue(self.station2 not in station_list) def test_distance_to(self): - ref_distance = np.array([[ 356.405, 180.277]]) + ref_distance = np.array([[356.405, 180.277]]) distance = self.station_manager3.get_distance_to(self.station_manager2) npt.assert_allclose(distance, ref_distance, atol=1e-2) - ref_distance = np.asarray([[ 127.279, 302.200], - [ 113.137, 288.140], - [ 98.994, 274.089]]) + ref_distance = np.asarray([[127.279, 302.200], + [113.137, 288.140], + [98.994, 274.089]]) distance = self.station_manager.get_distance_to(self.station_manager2) npt.assert_allclose(distance, ref_distance, atol=1e-2) def test_3d_distance_to(self): - ref_distance = np.asarray([[ 356.411, 180.302]]) - distance = self.station_manager3.get_3d_distance_to(self.station_manager2) + ref_distance = np.asarray([[356.411, 180.302]]) + distance = self.station_manager3.get_3d_distance_to( + self.station_manager2) npt.assert_allclose(distance, ref_distance, atol=1e-2) - ref_distance = np.asarray([[ 127.314, 302.226], - [ 113.154, 288.156], - [ 99, 274.096]]) - distance = self.station_manager.get_3d_distance_to(self.station_manager2) + ref_distance = np.asarray([[127.314, 302.226], + [113.154, 288.156], + [99, 274.096]]) + distance = self.station_manager.get_3d_distance_to( + self.station_manager2) npt.assert_allclose(distance, ref_distance, atol=1e-2) - + def test_wrap_around(self): self.station_manager = StationManager(2) self.station_manager.x = np.array([0, 150]) self.station_manager.y = np.array([0, -32]) self.station_manager.height = np.array([4, 5]) self.station_manager.intersite_dist = 100.0 - + self.station_manager2 = StationManager(3) - self.station_manager2.x = np.array([10, 200, 30]) + self.station_manager2.x = np.array([10, 200, 30]) self.station_manager2.y = np.array([15, 250, -350]) self.station_manager2.height = np.array([1, 2, 3]) - + # 2D Distance - d_2D, d_3D, phi, theta = self.station_manager.get_dist_angles_wrap_around(self.station_manager2) - ref_d_2D = np.asarray([[ 18.03, 150.32, 85.39], - [ 147.68, 181.12, 205.25]]) + d_2D, d_3D, phi, theta = self.station_manager.get_dist_angles_wrap_around( + self.station_manager2) + ref_d_2D = np.asarray([[18.03, 150.32, 85.39], + [147.68, 181.12, 205.25]]) npt.assert_allclose(d_2D, ref_d_2D, atol=1e-2) - + # 3D Distance - ref_d_3D = np.asarray([[ 18.27, 150.33, 85.39], - [ 147.73, 181.15, 205.26]]) + ref_d_3D = np.asarray([[18.27, 150.33, 85.39], + [147.73, 181.15, 205.26]]) npt.assert_allclose(d_3D, ref_d_3D, atol=1e-2) - + # Point vec - ref_phi = np.asarray([[ 56.31, -176.26 , 103.55], - [ 161.44, -56.49, 145.92]]) + ref_phi = np.asarray([[56.31, -176.26, 103.55], + [161.44, -56.49, 145.92]]) npt.assert_allclose(phi, ref_phi, atol=1e-2) - - ref_theta = np.asarray([[ 99.45, 90.76, 90.67], - [ 91.55, 90.95, 90.56]]) - npt.assert_allclose(theta,ref_theta, atol=1e-2) + + ref_theta = np.asarray([[99.45, 90.76, 90.67], + [91.55, 90.95, 90.56]]) + npt.assert_allclose(theta, ref_theta, atol=1e-2) def test_pointing_vector_to(self): eps = 1e-1 # Test 1 - phi, theta = self.station_manager.get_pointing_vector_to(self.station_manager2) - npt.assert_allclose(phi,np.array([[45.00, 51.04], + phi, theta = self.station_manager.get_pointing_vector_to( + self.station_manager2) + npt.assert_allclose(phi, np.array([[45.00, 51.04], [45.00, 51.34], - [45.00, 51.67]]),atol=eps) - npt.assert_allclose(theta,np.array([[88.65, 89.24], + [45.00, 51.67]]), atol=eps) + npt.assert_allclose(theta, np.array([[88.65, 89.24], [88.89, 89.40], - [89.42, 89.58]]),atol=eps) + [89.42, 89.58]]), atol=eps) # Test 2 - phi, theta = self.station_manager2.get_pointing_vector_to(self.station_manager) - npt.assert_allclose(phi,np.array([[-135.00, -135.00, -135.00], - [-128.96, -128.66, -128.33]]),atol=eps) - npt.assert_allclose(theta,np.array([[91.35, 91.01, 90.58], - [90.76, 90.60, 90.42]]),atol=eps) + phi, theta = self.station_manager2.get_pointing_vector_to( + self.station_manager) + npt.assert_allclose(phi, np.array([[-135.00, -135.00, -135.00], + [-128.96, -128.66, -128.33]]), atol=eps) + npt.assert_allclose(theta, np.array([[91.35, 91.01, 90.58], + [90.76, 90.60, 90.42]]), atol=eps) # Test 3 - phi, theta = self.station_manager3.get_pointing_vector_to(self.station_manager2) - npt.assert_allclose(phi,np.array([[-124.13, -123.69]]),atol=eps) - npt.assert_allclose(theta,np.array([[89.73, 89.05]]),atol=eps) + phi, theta = self.station_manager3.get_pointing_vector_to( + self.station_manager2) + npt.assert_allclose(phi, np.array([[-124.13, -123.69]]), atol=eps) + npt.assert_allclose(theta, np.array([[89.73, 89.05]]), atol=eps) # Test 4 - phi, theta = self.station_manager2.get_pointing_vector_to(self.station_manager3) - npt.assert_allclose(phi,np.array([[55.86], [56.31]]),atol=eps) - npt.assert_allclose(theta,np.array([[90.32], [90.95]]),atol=eps) + phi, theta = self.station_manager2.get_pointing_vector_to( + self.station_manager3) + npt.assert_allclose(phi, np.array([[55.86], [56.31]]), atol=eps) + npt.assert_allclose(theta, np.array([[90.32], [90.95]]), atol=eps) def test_off_axis_angle(self): sm1 = StationManager(1) @@ -416,36 +437,36 @@ def test_off_axis_angle(self): sm7.elevation = np.array([0, 0]) phi_ref = np.array([[0, 45]]) - npt.assert_allclose(phi_ref, sm6.get_off_axis_angle(sm7), atol=1e-2) - - + npt.assert_allclose(phi_ref, sm6.get_off_axis_angle(sm7), atol=1e-2) + def test_elevation(self): sm1 = StationManager(1) sm1.x = np.array([0]) sm1.y = np.array([0]) sm1.height = np.array([10]) - + sm2 = StationManager(6) - sm2.x = np.array([10, 10, 0, 0, 30, 20]) - sm2.y = np.array([ 0, 0, 5, 10, 30, 20]) - sm2.height = np.array([10, 20, 5, 0, 20, 20]) - + sm2.x = np.array([10, 10, 0, 0, 30, 20]) + sm2.y = np.array([0, 0, 5, 10, 30, 20]) + sm2.height = np.array([10, 20, 5, 0, 20, 20]) + elevation_ref = np.array([[0, 45, -45, -45, 13.26, 19.47]]) - npt.assert_allclose(elevation_ref, sm1.get_elevation(sm2), atol=1e-2) - + npt.assert_allclose(elevation_ref, sm1.get_elevation(sm2), atol=1e-2) + ####################################################################### sm3 = StationManager(2) sm3.x = np.array([0, 30]) sm3.y = np.array([0, 0]) sm3.height = np.array([10, 10]) - + sm4 = StationManager(2) - sm4.x = np.array([10, 10]) - sm4.y = np.array([ 0, 0]) + sm4.x = np.array([10, 10]) + sm4.y = np.array([0, 0]) sm4.height = np.array([10, 20]) - + elevation_ref = np.array([[0, 45], [0, 26.56]]) - npt.assert_allclose(elevation_ref, sm3.get_elevation(sm4), atol=1e-2) + npt.assert_allclose(elevation_ref, sm3.get_elevation(sm4), atol=1e-2) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_topology_hotspot.py b/tests/test_topology_hotspot.py index 3e6806950..d4c02ac20 100644 --- a/tests/test_topology_hotspot.py +++ b/tests/test_topology_hotspot.py @@ -7,14 +7,14 @@ import unittest import numpy as np -#import numpy.testing as npt +# import numpy.testing as npt -from sharc.parameters.parameters_hotspot import ParametersHotspot +from sharc.parameters.imt.parameters_hotspot import ParametersHotspot from sharc.topology.topology_hotspot import TopologyHotspot + class TopologyHotspotTest(unittest.TestCase): - - + def setUp(self): # For this test case, hotspot parameters are useless because we are # testing only the validation methods @@ -26,25 +26,25 @@ def setUp(self): intersite_distance = 1000 num_clusters = 1 - self.topology = TopologyHotspot(param, intersite_distance, num_clusters) - - + self.topology = TopologyHotspot( + param, intersite_distance, num_clusters) + def test_overlapping_hotspots(self): candidate_x = np.array([300]) candidate_y = np.array([0]) candidate_azimuth = np.array([-180]) set_x = np.array([0, 200]) - set_y = np.array([0, 0]) + set_y = np.array([0, 0]) set_azimuth = np.array([0, -180]) radius = 100 - self.assertFalse(self.topology.overlapping_hotspots(candidate_x, - candidate_y, - candidate_azimuth, - set_x, - set_y, + self.assertFalse(self.topology.overlapping_hotspots(candidate_x, + candidate_y, + candidate_azimuth, + set_x, + set_y, set_azimuth, radius)) - + candidate_x = np.array([0]) candidate_y = np.array([0]) candidate_azimuth = np.array([0]) @@ -52,45 +52,44 @@ def test_overlapping_hotspots(self): set_y = np.array([150, 400]) set_azimuth = np.array([270, 270]) radius = 100 - self.assertTrue(self.topology.overlapping_hotspots(candidate_x, - candidate_y, - candidate_azimuth, - set_x, - set_y, + self.assertTrue(self.topology.overlapping_hotspots(candidate_x, + candidate_y, + candidate_azimuth, + set_x, + set_y, set_azimuth, radius)) - + candidate_x = np.array([0]) candidate_y = np.array([0]) candidate_azimuth = np.array([0]) - set_x = np.array([ -1, 101]) - set_y = np.array([ 0, 0]) + set_x = np.array([-1, 101]) + set_y = np.array([0, 0]) set_azimuth = np.array([180, 0]) radius = 100 - self.assertFalse(self.topology.overlapping_hotspots(candidate_x, - candidate_y, - candidate_azimuth, - set_x, - set_y, + self.assertFalse(self.topology.overlapping_hotspots(candidate_x, + candidate_y, + candidate_azimuth, + set_x, + set_y, set_azimuth, radius)) - + candidate_x = np.array([1]) candidate_y = np.array([0]) candidate_azimuth = np.array([0]) - set_x = np.array([ 0]) - set_y = np.array([ 1]) + set_x = np.array([0]) + set_y = np.array([1]) set_azimuth = np.array([90]) radius = 100 - self.assertTrue(self.topology.overlapping_hotspots(candidate_x, - candidate_y, - candidate_azimuth, - set_x, - set_y, + self.assertTrue(self.topology.overlapping_hotspots(candidate_x, + candidate_y, + candidate_azimuth, + set_x, + set_y, set_azimuth, radius)) - - - + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_topology_macrocell_.py b/tests/test_topology_macrocell_.py index 659de69ca..06c4e12bb 100644 --- a/tests/test_topology_macrocell_.py +++ b/tests/test_topology_macrocell_.py @@ -11,11 +11,12 @@ from sharc.topology.topology_macrocell import TopologyMacrocell + class TopologyMacrocellTest(unittest.TestCase): - + def setUp(self): pass - + def test_intersite_distance(self): intersite_distance = 1000 num_clusters = 1 @@ -23,7 +24,7 @@ def test_intersite_distance(self): topology.calculate_coordinates() self.assertEqual(topology.intersite_distance, 1000) self.assertAlmostEqual(topology.cell_radius, 666.66, places=1) - + # when intersite distance changes, cell radius also changes intersite_distance = 600 num_clusters = 1 @@ -31,7 +32,7 @@ def test_intersite_distance(self): topology.calculate_coordinates() self.assertEqual(topology.intersite_distance, 600) self.assertEqual(topology.cell_radius, 400) - + # let's change it one more time... intersite_distance = 1500 num_clusters = 1 @@ -47,56 +48,56 @@ def test_num_clusters(self): topology = TopologyMacrocell(intersite_distance, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.num_clusters, 1) - + # set to 7 clusters intersite_distance = 1000 num_clusters = 7 topology = TopologyMacrocell(intersite_distance, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.num_clusters, 7) - + # set to any other value raises an exception intersite_distance = 1000 num_clusters = 3 with self.assertRaises(ValueError): topology = TopologyMacrocell(intersite_distance, num_clusters) - + intersite_distance = 1000 num_clusters = 8 with self.assertRaises(ValueError): topology = TopologyMacrocell(intersite_distance, num_clusters) - + def test_coordinates(self): """ - TODO: test the case when number of clusters is 7 + TODO: test the case when number of clusters is 7 """ intersite_distance = 1000 num_clusters = 1 topology = TopologyMacrocell(intersite_distance, num_clusters) topology.calculate_coordinates() - + num_sites = 19 num_bs_per_site = 3 - num_bs = num_sites*num_bs_per_site - + num_bs = num_sites * num_bs_per_site + # check the number of base stations self.assertEqual(len(topology.x), num_bs) self.assertEqual(len(topology.y), num_bs) self.assertEqual(len(topology.azimuth), num_bs) - + # check coordinates - x_ref = np.repeat(np.array([0, 1000, 500, -500, -1000, -500, - 500, 2000, 1500, 1000, 0, -1000, - -1500, -2000, -1500, -1000, 0, 1000, 1500]), num_bs_per_site) - y_ref = np.repeat(np.array([0, 0, 866.02, 866.02, 0, -866.02, - -866.02, 0, 866.02, 1732.05, 1732.05, 1732.05, - 866.02, 0, -866.02, -1732.05, -1732.05, -1732.05, -866.02]), num_bs_per_site) + x_ref = np.repeat(np.array([0, 1000, 500, -500, -1000, -500, + 500, 2000, 1500, 1000, 0, -1000, + -1500, -2000, -1500, -1000, 0, 1000, 1500]), num_bs_per_site) + y_ref = np.repeat(np.array([0, 0, 866.02, 866.02, 0, -866.02, + -866.02, 0, 866.02, 1732.05, 1732.05, 1732.05, + 866.02, 0, -866.02, -1732.05, -1732.05, -1732.05, -866.02]), num_bs_per_site) az_ref = np.tile([60, 180, 300], num_sites) - + npt.assert_allclose(topology.x, x_ref, atol=1e-2) npt.assert_allclose(topology.y, y_ref, atol=1e-2) npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2) - + # change intersite distance and check new cell radius and coordinates intersite_distance = 500 num_clusters = 1 @@ -109,19 +110,18 @@ def test_coordinates(self): self.assertEqual(len(topology.azimuth), num_bs) # check coordinates - x_ref = np.repeat(np.array([0, 500, 250, -250, -500, -250, - 250, 1000, 750, 500, 0, -500, - -750, -1000, -750, -500, 0, 500, 750]), num_bs_per_site) - y_ref = np.repeat(np.array([0, 0, 433.01, 433.01, 0, -433.01, - -433.01, 0, 433.01, 866.02, 866.02, 866.02, - 433.01, 0, -433.01, -866.02, -866.02, -866.02, -433.01]), num_bs_per_site) + x_ref = np.repeat(np.array([0, 500, 250, -250, -500, -250, + 250, 1000, 750, 500, 0, -500, + -750, -1000, -750, -500, 0, 500, 750]), num_bs_per_site) + y_ref = np.repeat(np.array([0, 0, 433.01, 433.01, 0, -433.01, + -433.01, 0, 433.01, 866.02, 866.02, 866.02, + 433.01, 0, -433.01, -866.02, -866.02, -866.02, -433.01]), num_bs_per_site) az_ref = np.tile([60, 180, 300], num_sites) - + npt.assert_allclose(topology.x, x_ref, atol=1e-2) npt.assert_allclose(topology.y, y_ref, atol=1e-2) - npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2) + npt.assert_allclose(topology.azimuth, az_ref, atol=1e-2) + - if __name__ == '__main__': unittest.main() - \ No newline at end of file diff --git a/tests/test_topology_ntn.py b/tests/test_topology_ntn.py new file mode 100644 index 000000000..0a09ecb36 --- /dev/null +++ b/tests/test_topology_ntn.py @@ -0,0 +1,171 @@ +""" +Created on Tue Dec 28 21:41:00 2024 + +@author: Vitor Borges +""" + +import unittest +import numpy as np +import math +from sharc.topology.topology_ntn import TopologyNTN + + +class TopologyNTNTest(unittest.TestCase): + + def setUp(self): + self.bs_height = 1000e3 # meters + self.bs_azimuth = 45 # degrees + self.bs_elevation = 45 # degrees + self.beamwidth = 10 + denominator = math.tan(np.radians(self.beamwidth)) + nominator = math.cos(np.radians(self.bs_elevation)) + self.cell_radius = self.bs_height * denominator / nominator # meters + self.intersite_distance = self.cell_radius * np.sqrt(3) # meters + + self.cos = lambda x: np.cos(np.radians(x)) + self.sin = lambda x: np.sin(np.radians(x)) + self.tan = lambda x: np.tan(np.radians(x)) + + def test_single_sector(self): + topology = TopologyNTN( + self.intersite_distance, + self.cell_radius, + self.bs_height, + self.bs_azimuth, + self.bs_elevation, + num_sectors=1) + topology.calculate_coordinates() + expected_x = expected_y = expected_z = [0] + self.assertListEqual(list(topology.x), expected_x) + self.assertListEqual(list(topology.y), expected_y) + self.assertListEqual(list(topology.z), expected_z) + expected_azimuth = [-135.0] + for actual_azi, expected_azi in zip( + topology.azimuth, expected_azimuth): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_azi, expected_azi, places=3) + expected_elevation = [-45.0] + for expected_elev, actual_elev in zip( + topology.elevation, expected_elevation): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_elev, expected_elev, places=3) + + def test_seven_sectors(self): + topology = TopologyNTN( + self.intersite_distance, + self.cell_radius, + self.bs_height, + self.bs_azimuth, + self.bs_elevation, + num_sectors=7) + topology.calculate_coordinates() + + d = self.intersite_distance + + # defining expected x, y, z + expected_x = [0] + expected_x.extend([d * self.cos(30 + 60 * k) for k in range(6)]) + expected_y = [0] + expected_y.extend([d * self.sin(30 + 60 * k) for k in range(6)]) + expected_z = [0] + expected_z.extend([0 for _ in range(6)]) + + # testing expected x, y, z + for actual_x, expec_x in zip(topology.x, expected_x): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_x, expec_x, places=3) + for actual_y, expec_y in zip(topology.y, expected_y): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_y, expec_y, places=3) + self.assertListEqual(list(topology.z), expected_z) + + # defining expected azimuth and elevation + space_x = np.repeat(topology.space_station_x, topology.num_sectors) + space_y = np.repeat(topology.space_station_y, topology.num_sectors) + space_z = np.repeat(topology.space_station_z, topology.num_sectors) + # using expected_x and expected_y so this test is independent of other + # tests + expected_azimuth = np.arctan2( + expected_y - space_y, + expected_x - space_x) * 180 / np.pi + expected_dist_xy = np.sqrt( + (expected_x - space_x)**2 + (expected_y - space_y)**2) + # using expected_z so this test is independent of other tests + expected_elevation = np.arctan2( + expected_z - space_z, + expected_dist_xy) * 180 / np.pi + + # testing expected azimuth and elevation + for actual_azi, expected_azi in zip( + topology.azimuth, expected_azimuth): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_azi, expected_azi, places=3) + for expected_elev, actual_elev in zip( + topology.elevation, expected_elevation): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_elev, expected_elev, places=3) + + def test_nineteen_sectors(self): + topology = TopologyNTN( + self.intersite_distance, + self.cell_radius, + self.bs_height, + self.bs_azimuth, + self.bs_elevation, + num_sectors=19) + topology.calculate_coordinates() + + d = self.intersite_distance + + # defining expected x, y, z + expected_x = [0] + expected_y = [0] + expected_x.extend([d * self.cos(30 + 60 * k) for k in range(6)]) + expected_y.extend([d * self.sin(30 + 60 * k) for k in range(6)]) + for k in range(6): + # already rotated 30 degrees + angle = 30 + k * 60 + expected_x.append(2 * d * self.cos(angle)) + expected_y.append(2 * d * self.sin(angle)) + expected_x.append(d * self.cos(angle) + d * self.cos(angle + 60)) + expected_y.append(d * self.sin(angle) + d * self.sin(angle + 60)) + expected_z = [0 for _ in range(19)] + + # testing expected x, y, z + for actual_x, expec_x in zip(topology.x, expected_x): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_x, expec_x, places=3) + for actual_y, expec_y in zip(topology.y, expected_y): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_y, expec_y, places=3) + self.assertListEqual(list(topology.z), expected_z) + + # defining expected azimuth and elevation + space_x = np.repeat(topology.space_station_x, topology.num_sectors) + space_y = np.repeat(topology.space_station_y, topology.num_sectors) + space_z = np.repeat(topology.space_station_z, topology.num_sectors) + # using expected_x and expected_y so this test is independent of other + # tests + expected_azimuth = np.arctan2( + expected_y - space_y, + expected_x - space_x) * 180 / np.pi + expected_distance_xy = np.sqrt( + (expected_x - space_x)**2 + (expected_y - space_y)**2) + # using expected_z so this test is independent of other tests + expected_elevation = np.arctan2( + expected_z - space_z, + expected_distance_xy) * 180 / np.pi + + # testing expected azimuth and elevation + for actual_azi, expected_azi in zip( + topology.azimuth, expected_azimuth): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_azi, expected_azi, places=3) + for expected_elev, actual_elev in zip( + topology.elevation, expected_elevation): + # cannot check asserListEqual because of float precision + self.assertAlmostEqual(actual_elev, expected_elev, places=3) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_topology_single_base_station.py b/tests/test_topology_single_base_station.py index 0fdd6fd88..8dd228ec8 100644 --- a/tests/test_topology_single_base_station.py +++ b/tests/test_topology_single_base_station.py @@ -11,11 +11,12 @@ from sharc.topology.topology_single_base_station import TopologySingleBaseStation + class TopologySingleBaseStationTest(unittest.TestCase): - + def setUp(self): pass - + def test_coordinates(self): cell_radius = 1500 num_clusters = 1 @@ -28,17 +29,16 @@ def test_coordinates(self): num_clusters = 2 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() - npt.assert_equal(topology.x, np.array([0, 2*cell_radius])) + npt.assert_equal(topology.x, np.array([0, 2 * cell_radius])) npt.assert_equal(topology.y, np.array([0, 0])) - def test_num_clusters(self): cell_radius = 1500 num_clusters = 1 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.num_clusters, 1) - + cell_radius = 1500 num_clusters = 2 topology = TopologySingleBaseStation(cell_radius, num_clusters) @@ -49,7 +49,6 @@ def test_num_clusters(self): num_clusters = 3 with self.assertRaises(ValueError): topology = TopologySingleBaseStation(cell_radius, num_clusters) - def test_intersite_distance(self): cell_radius = 1500 @@ -57,42 +56,39 @@ def test_intersite_distance(self): topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.intersite_distance, 3000) - + cell_radius = 1000 num_clusters = 1 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.intersite_distance, 2000) - def test_cell_radius(self): cell_radius = 1500 num_clusters = 1 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.cell_radius, 1500) - + cell_radius = 1000 num_clusters = 1 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.cell_radius, 1000) - - + def test_azimuth(self): cell_radius = 1500 num_clusters = 1 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() self.assertEqual(topology.azimuth, 0) - + cell_radius = 1000 num_clusters = 2 topology = TopologySingleBaseStation(cell_radius, num_clusters) topology.calculate_coordinates() npt.assert_equal(topology.azimuth, np.array([0, 180])) - - + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tox.yaml b/tox.yaml new file mode 100644 index 000000000..46abcda43 --- /dev/null +++ b/tox.yaml @@ -0,0 +1,9 @@ +tox: + envlist : py26, py27, py33, py34, py35, flake8 +testenv:flake8: + basepython :python + deps :flake8 + commands :flake8 sharc +testenv: + PYTHONPATH : {toxinidir}:{toxinidir}/sharc + py.test --basetemp :{envtmpdir}