diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..96c3869 --- /dev/null +++ b/.clang-format @@ -0,0 +1,103 @@ +# NTIA/ITS C++ Clang-Format Style Options +# Updated 9/25/2024 +--- +AlignAfterOpenBracket: BlockIndent +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BasedOnStyle: WebKit +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: After +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + BeforeLambdaBody: false + BeforeWhile: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakInheritanceList: AfterColon +BreakBeforeConceptDeclarations: false +BreakConstructorInitializers: AfterColon +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: Never +FixNamespaceComments: true +IncludeBlocks: Regroup +IndentAccessModifiers: true +IndentCaseBlocks: true +IndentCaseLabels: true +IndentExternBlock: Indent +IndentGotoLabels: true +IndentPPDirectives: BeforeHash +IndentRequires: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +LineEnding: CRLF +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Right +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInConditionalStatement: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++14 +TabWidth: 4 +UseTab: Never diff --git a/.github/workflows/cff-validator.yml b/.github/workflows/cff-validator.yml new file mode 100644 index 0000000..ccbcc68 --- /dev/null +++ b/.github/workflows/cff-validator.yml @@ -0,0 +1,29 @@ +name: Validate CITATION.cff + +on: + push: + branches: ["main"] + paths: + - 'CITATION.cff' + - '.github/workflows/cff-validator.yml' + pull_request: + branches: ["main", "dev"] + paths: + - 'CITATION.cff' + - '.github/workflows/cff-validator.yml' + workflow_dispatch: + +jobs: + Validate-CITATION-cff: + runs-on: ubuntu-latest + name: Validate CITATION.cff + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Validate CITATION.cff + uses: dieghernan/cff-validator@v3 + with: + install-r: true diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml new file mode 100644 index 0000000..856c587 --- /dev/null +++ b/.github/workflows/ctest.yml @@ -0,0 +1,73 @@ +# This action compiles the library and driver and runs all unit tests using an OS and CMake matrix +name: Unit Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["main", "dev"] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +# Define the matrix for different operating systems +jobs: + build-and-test: + name: ${{ matrix.os }} / ${{ matrix.architecture }} / CMake ${{ matrix.cmakeVersion }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest # Apple + - macos-13 # Intel + - windows-latest + architecture: [arm64, x64, x86] + cmakeVersion: ["3.21", latest] # CMake >= 3.21 is required to use "--preset " and discover generators + exclude: + - os: macos-latest + architecture: x86 + - os: macos-latest + architecture: x64 + - os: macos-13 + architecture: x86 + - os: macos-13 + architecture: arm64 + - os: windows-latest + architecture: arm64 + - os: ubuntu-latest + architecture: arm64 + - os: ubuntu-latest + architecture: x86 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Clone required submodules + run: | + git submodule init extern/googletest + git submodule init extern/test-data + git submodule update + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ matrix.cmakeVersion }} + + - name: "CMake: Build and Test (64-bit)" + if: matrix.architecture != 'x86' + uses: lukka/run-cmake@v10 + with: + configurePreset: debug64 + buildPreset: debug64 + testPreset: debug64 + + - name: "CMake: Build and Test (32-bit)" + if: matrix.architecture == 'x86' + uses: lukka/run-cmake@v10 + with: + configurePreset: debug32 + buildPreset: debug32 + testPreset: debug32 diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml new file mode 100644 index 0000000..c5ee894 --- /dev/null +++ b/.github/workflows/doxygen.yml @@ -0,0 +1,72 @@ +# This action builds **AND DEPLOYS** Doxygen documentation to GitHub Pages +# Doxygen site is DEPLOYED if this action is triggered by publishing a release. +# Doxygen site is NOT DEPLOYED (only built) when triggered by pull request or dispatched. +name: C++ Docs + +on: + release: + types: ["published"] + pull_request: + branches: ["main", "dev"] + push: + branches: ["main"] + workflow_dispatch: + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Clone doxygen-awesome-css submodule + run: | + git submodule init extern/doxygen-awesome-css + git submodule update + + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 + with: + version: "1.12.0" + + - name: Setup GitHub Pages + if: ${{ github.event_name == 'release' }} + id: pages + uses: actions/configure-pages@v5 + + - name: Install CMake + uses: lukka/get-cmake@latest + + - name: Build documentation with Doxygen + uses: lukka/run-cmake@v10 + with: + configurePreset: docsOnly + buildPreset: docsOnly + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v3 + if: ${{ github.event_name == 'release' }} + with: + path: ./docs/html/ + + deploy: + if: ${{ github.event_name == 'release' }} + needs: build + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..19b1ec3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,86 @@ +# This action compiles multi-platform binaries for a release. +# It is triggered when a new tag is made with a version number starting with "v" +name: Create Release Artifacts + +on: + push: + tags: ['v[0-9]+.*'] + workflow_dispatch: + +permissions: + contents: write + +jobs: + create_release_artifacts: + name: 'Create Release: ${{ matrix.relName}}' + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: windows-latest + architecture: x64 + relName: Windows x64 + - os: windows-latest + architecture: x86 + relName: Windows x86 + - os: macos-latest + architecture: arm64 + relName: macOS Universal + - os: ubuntu-latest + architecture: x64 + relName: Linux x64 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install CMake # (latest stable version) + uses: lukka/get-cmake@latest + + - name: "CMake: Build (64-bit)" + if: matrix.architecture != 'x86' + uses: lukka/run-cmake@v10 + with: + configurePreset: release64 + configurePresetAdditionalArgs: "['-DBUILD_DOCS=OFF', '-DRUN_TESTS=OFF', '-DRUN_DRIVER_TESTS=OFF']" + buildPreset: release64 + + - name: "CMake: Build (32-bit)" + if: matrix.architecture == 'x86' + uses: lukka/run-cmake@v10 + with: + configurePreset: release32 + configurePresetAdditionalArgs: "['-DBUILD_DOCS=OFF', '-DRUN_TESTS=OFF', '-DRUN_DRIVER_TESTS=OFF', '-A', 'Win32']" + buildPreset: release32 + + - name: Upload release artifacts (binaries) + if: runner.os != 'Windows' + uses: actions/upload-artifact@v4 + with: + name: "${{ github.event.repository.name }}-release-${{ runner.os }}" + path: | + ${{ github.workspace }}/bin/*-universal.dylib + ${{ github.workspace }}/bin/*-x86_64.so + ${{ github.workspace }}/bin/*Driver*-*-Darwin-universal + ${{ github.workspace }}/bin/*Driver*-*-Linux-x86_64 + if-no-files-found: error + overwrite: true + + - name: Upload release artifact (Windows) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v4 + with: + name: "${{ github.event.repository.name }}-release-${{ runner.os }}-${{ matrix.architecture }}" + path: | + ${{ github.workspace }}\bin\Release\*.dll + ${{ github.workspace }}\bin\Release\*Driver-*.exe + if-no-files-found: error + overwrite: true + + - name: Upload release artifact (C++ Header) + if: runner.os == 'Linux' + uses: actions/upload-artifact@v4 + with: + name: "${{ github.event.repository.name }}-release-cpp-header" + path: ${{ github.workspace }}/include/P2108.h + if-no-files-found: error + overwrite: true diff --git a/.gitignore b/.gitignore index 0d01ff9..5fed29b 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,32 @@ Thumbs.db *.aps **/obj **/x64 -**/x86 \ No newline at end of file +**/x86 + +######### +## CMake +######### +CMakeLists.txt.user +CMakeCache.txt +CMakeUserPresets.json +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +build + + +########### +## Doxygen +########### +docs/html + +########### +## VS Code +########### +.vscode \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4da471a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,10 @@ +[submodule "extern/googletest"] + path = extern/googletest + url = https://github.com/google/googletest + branch = v1.12.x +[submodule "extern/doxygen-awesome-css"] + path = extern/doxygen-awesome-css + url = https://github.com/jothepro/doxygen-awesome-css +[submodule "extern/p2108-test-data"] + path = extern/test-data + url = https://github.com/NTIA/p2108-test-data diff --git a/.zenodo.json b/.zenodo.json index bcbd657..a6e1a89 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,17 +1,58 @@ { + "upload_type": "software", + "publication_date": "2022-09-26", + "title": "Recommendation ITU-R P.2108-1, Release 1.0", "creators": [ - { - "orcid": "0000-0002-7417-4009", - "affiliation": "The Institute for Telecommunication Sciences", - "name": "Kozma Jr, William" + { + "name": "Kozma Jr, William", + "affiliation": "U.S. Department of Commerce, National Telecommunications and Information Administration, Institute for Telecommunication Sciences", + "orcid": "0000-0002-7417-4009" + }, + { + "name": "Romaniello, Anthony W.", + "affiliation": "U.S. Department of Commerce, National Telecommunications and Information Administration, Institute for Telecommunication Sciences", + "orcid": "0000-0001-8437-6504" } ], - - "title": "Recommendation ITU-R P.2108-1, Release 1.0", - - "upload_type": "software", - - "version": "1.0.0", - - "keywords": ["Study Group 3", "ITU-R", "P2108", "clutter", "propagation"] + "description": "This repository contains the NTIA/ITS implementation of Recommendation ITU-R P.2108. This Recommendation contains three methods for the prediction of clutter loss: the Height Gain Terminal Correction Model, the Terrestrial Statistical Model, and the Aeronautical Statistical Model. The software implements Section 3 of Annex 1 of the Recommendation.", + "keywords": [ + "Study Group 3", + "ITU-R", + "P2108", + "clutter", + "propagation" + ], + "related_identifiers": [ + { + "identifier": "https://github.com/NTIA/p2108-dotnet", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/p2108-matlab", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/p2108-python", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/-test-data", + "relation": "isSupplementedBy", + "resource_type": "dataset" + }, + { + "identifier": "https://ntia.github.io/P2108/", + "relation": "isDocumentedBy", + "resource_type": "softwaredocumentation" + }, + { + "identifier": "https://ntia.github.io/propagation-library-wiki/models/P2108/", + "relation": "isDocumentedBy", + "resource_type": "softwaredocumentation" + } + ], + "version": "1.0" } \ No newline at end of file diff --git a/AeronauticalStatisticalModelTestData.csv b/AeronauticalStatisticalModelTestData.csv deleted file mode 100644 index cdcad8f..0000000 --- a/AeronauticalStatisticalModelTestData.csv +++ /dev/null @@ -1,14 +0,0 @@ -f__ghz,theta_deg,p,rtn,L_ces__db -30,2,5,0,7.7 -30,2,1,0,1.9 -30,2,99,0,87.3 -10,10.5,45,0,12.4 -15,90,50,0,0 -20,0,50,0,45.6 -11.1,15.5,80.5,0,14.7 -9.9,45,45,3300,0 -100.1,45,45,3300,0 -18,-0.1,50,3301,0 -18,90.1,50,3301,0 -22,25,0,3302,0 -22,25,100,3302,0 diff --git a/CITATION.cff b/CITATION.cff index 1c1824d..a5b765e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,16 +1,43 @@ cff-version: 1.2.0 -title: 'Recommendation ITU-R P.2108-1' -message: >- - If you use this software, please cite it using the - metadata from this file. +title: >- + Recommendation ITU-R P.2108 +message: Please cite this software using these metadata. type: software authors: - given-names: William family-names: Kozma - name-suffix: Jr + name-suffix: Jr. email: wkozma@ntia.gov - affiliation: The Institute for Telecommunication Sciences + affiliation: >- + U.S. Department of Commerce, National + Telecommunications and Information Administration, + Institute for Telecommunication Sciences orcid: 'https://orcid.org/0000-0002-7417-4009' -doi: 10.5281/zenodo.7114523 -url: https://github.com/NTIA/p2108 -version: 0.0.0 + - given-names: Anthony W. + family-names: Romaniello + email: aromaniello@ntia.gov + affiliation: >- + U.S. Department of Commerce, National + Telecommunications and Information Administration, + Institute for Telecommunication Sciences + orcid: 'https://orcid.org/0000-0001-8437-6504' + - name: >- + U.S. Department of Commerce, National + Telecommunications and Information Administration, + Institute for Telecommunication Sciences + address: 325 Broadway + city: Boulder + country: US + post-code: '80305' + region: Colorado + alias: NTIA/ITS + email: code@ntia.gov + website: 'https://its.ntia.gov' +repository-code: 'https://github.com/NTIA/p2108' +url: 'https://ntia.github.io/propagation-library-wiki/models/P2108' +keywords: + - propagation + - rf + - clutter + - itu +version: 1.0.0 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7072edb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,185 @@ +############################################################ +## CMakeList.txt : Top-level CMake project file, do global +## configuration and include sub-projects here. +############################################################ + +# >=3.21 required for Ninja Generators to use absolute paths. +# See https://stackoverflow.com/questions/69846931/ +# This is relevant for specifying unit test data file paths +# Automated testing only runs >=3.21 for this reason. +# >=3.15 required for CMake features used in this project +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +# Warn if '-A Win32' is provided but BUILD_32BIT has not been set. This may +# indicate accidental use of a 64-bit CMake preset while intending to build for 32-bit. +if (DEFINED ENV{CMAKE_GENERATOR_PLATFORM}) + if ($ENV{CMAKE_GENERATOR_PLATFORM} STREQUAL "Win32") + if (NOT BUILD_32BIT) + message(WARNING "Generator platform is Win32 but 32-bit preset is not being used.") + endif () + endif () +endif () + +# If on macOS, handle arm64/x86_64 architectures (must be done before project()) +# For other OS, identify the architecture and save it to the ARCH_SUFFIX variable. +if (APPLE) + # Get the current platform's native architecture + execute_process( + COMMAND uname -m + RESULT_VARIABLE result + OUTPUT_VARIABLE MACOS_NATIVE_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + # If running on Apple silicon, try a universal build. Otherwise, a native build. + if (${MACOS_NATIVE_ARCHITECTURE} STREQUAL "arm64") + set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) + set(ARCH_SUFFIX "universal") + else () + set(ARCH_SUFFIX ${MACOS_NATIVE_ARCHITECTURE}) + endif () +elseif (WIN32) + if (BUILD_32BIT) + set(ARCH_SUFFIX "x86") + else () + set(ARCH_SUFFIX "x64") + endif () +elseif (UNIX) # Only non-Apple Linux evaluates as True here + set(ARCH_SUFFIX "x86_64") +endif () + +# Convenience function for printing visible status messages. +# Accepts arbitrary number of string inputs, each printed as a +# new line in the status message. +function(proplib_message) + string(REPLACE ";" "\n-- " all_args "${ARGN}") + message(STATUS + "==========[PROPLIB STATUS MESSAGE]==========\n" + "-- ${all_args}\n-- " + "============================================\n" + ) +endfunction() + +########################################### +## PROJECT METADATA +########################################### +set(LIB_NAME "P2108") # Name of library/target +set(LIB_NAMESPACE "ITS.ITU.PSeries") # Namespace for the named library +project( + "${LIB_NAMESPACE}.${LIB_NAME}" + VERSION 1.0 + DESCRIPTION "Recommendation ITU-R P.2108" + HOMEPAGE_URL "https://ntia.github.io/propagation-library-wiki/models/P2108" + LANGUAGES "CXX" +) + +########################################### +## CMAKE OPTIONS AND DEFAULTS +########################################### +# Define options. Defaults to: compile 64-bit library and driver, build docs, run tests +option(BUILD_DOCS "Generate documentation site with Doxygen" ON) +option(BUILD_DRIVER "Build the command-line driver executable" ON) +option(RUN_DRIVER_TESTS "Test the command-line driver executable" ON) +option(DOCS_ONLY "Skip all steps except generating the documentation site" OFF) +option(RUN_TESTS "Run unit tests for the main library" ON) +option(BUILD_32BIT "Build project for x86/32-bit instead of x64/64-bit" OFF) + +########################################### +## SETUP +########################################### +# C++11 and some extensions (e.g., for `auto`) are the minimum required +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +# Get 0/1 values indicating compiler type +set(gcc_like_cxx "$") +set(msvc_cxx "$") + +# Define a function to set compile options of a provided target. +function(configure_proplib_target proplib_target) + target_compile_options(${proplib_target} PRIVATE + # For GCC-like compilers in any configuration + "$<${gcc_like_cxx}:$>" + # For GCC-like compilers in Release configurations + "$<${gcc_like_cxx}:$:-O3;-DNDEBUG>>>" + # For GCC-like compilers in Debug configurations + "$<${gcc_like_cxx}:$:-g;-O0>>>" + # For GCC-like compilers in 32-bit configurations + "$<${gcc_like_cxx}:$:-m32>>>" + # For MSVC compiler in any configuration + "$<${msvc_cxx}:$>" + # For MSVC compiler in Release configurations + "$<${msvc_cxx}:$:/W3;/O2;/DNDEBUG>>>" + # For MSVC compiler in Debug configurations + "$<${msvc_cxx}:$:/W4;/Od;/Zi>>>" + ) +endfunction() + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif () + +# control where the static and shared libraries are built +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin" CACHE STRING "Set the CMAKE Archive Output Directory") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin" CACHE STRING "Set the CMAKE Library Output Directory") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin" CACHE STRING "Set the CMAKE Runtime Output Directory") + +proplib_message( + "Initial Configuration Complete" + "Generator: ${CMAKE_GENERATOR}" + "Generator platform (for multi-config generators only): $ENV{CMAKE_GENERATOR_PLATFORM}" + "C++ Compiler: ${CMAKE_CXX_COMPILER}" + "Target architecture: ${ARCH_SUFFIX}" + "CMake Options:" + " BUILD_32BIT = ${BUILD_32BIT}" + " BUILD_DOCS = ${BUILD_DOCS}" + " BUILD_DRIVER = ${BUILD_DRIVER}" + " RUN_DRIVER_TESTS = ${RUN_DRIVER_TESTS}" + " DOCS_ONLY = ${DOCS_ONLY}" + " RUN_TESTS = ${RUN_TESTS}" +) + +########################################## +## BUILD/RUN +########################################## +if (NOT DOCS_ONLY) + add_subdirectory(src) # Configure the library + if (RUN_TESTS OR RUN_DRIVER_TESTS) + enable_testing() + include(CTest) + # Initialize GoogleTest if any tests will be configured + if (EXISTS "${PROJECT_SOURCE_DIR}/extern/googletest/CMakeLists.txt") + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + # Ensure GoogleTest is built as a static library + if (DEFINED BUILD_SHARED_LIBS AND BUILD_SHARED_LIBS) + set(BUILD_SHARED_LIBS_${LIB_NAME} ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS OFF) + endif () + add_subdirectory("${PROJECT_SOURCE_DIR}/extern/googletest" "extern/googletest" EXCLUDE_FROM_ALL) + include(GoogleTest) + # Restore initial value of BUILD_SHARED_LIBS + if (DEFINED BUILD_SHARED_LIBS_${LIB_NAME}) + set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_${LIB_NAME}}) + endif () + else () + message(SEND_ERROR + "Unable to build tests. GoogleTest submodule is missing. " + "Run `git submodule init extern/googletest` then " + "`git submodule update` and try again." + ) + endif () + endif () + if (RUN_TESTS) # Build and run unit tests + add_subdirectory(tests) + endif () + if (BUILD_DRIVER OR RUN_DRIVER_TESTS) + add_subdirectory(app) + endif () +endif () + +# Generate documentation +if (BUILD_DOCS OR DOCS_ONLY) + add_subdirectory(docs) +endif () diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..c68dd72 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,207 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "proplib-config-base", + "hidden": true, + "description": "Base configuration preset for ITS PropLib libraries", + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "DOCS_ONLY": "OFF", + "RUN_TESTS": "ON", + "BUILD_32BIT": "OFF" + } + }, + { + "name": "proplib-config-release-base", + "hidden": true, + "inherits": "proplib-config-base", + "description": "Base 'Release' configuration preset for ITS PropLib libraries", + "cacheVariables": { + "CMAKE_BUILD_RPATH_USE_ORIGIN": "ON", + "BUILD_SHARED_LIBS": "ON", + "BUILD_DOCS": "ON", + "BUILD_DRIVER": "ON", + "RUN_DRIVER_TESTS": "ON" + }, + "environment": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + }, + { + "name": "proplib-config-debug-base", + "hidden": true, + "inherits": "proplib-config-base", + "description": "Base 'Debug' configuration preset for ITS PropLib libraries", + "cacheVariables": { + "CMAKE_BUILD_RPATH_USE_ORIGIN": "ON", + "BUILD_SHARED_LIBS": "ON", + "BUILD_DOCS": "OFF", + "BUILD_DRIVER": "ON", + "RUN_DRIVER_TESTS": "ON" + }, + "environment": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "name": "debug64", + "displayName": "Debug, 64-Bit", + "description": "Build library, driver and tests with debug options. Skip building docs.", + "inherits": "proplib-config-debug-base" + }, + { + "name": "release64", + "displayName": "Release, 64-Bit", + "description": "Build library, driver, docs and tests with release options.", + "inherits": "proplib-config-release-base" + }, + { + "name": "debug32", + "displayName": "Debug, 32-bit", + "description": "Build 32-bit library, driver and tests with debug options. Skip building docs.", + "inherits": "proplib-config-debug-base", + "cacheVariables": { + "BUILD_32BIT": "ON" + } + }, + { + "name": "release32", + "displayName": "Release, 32-bit", + "description": "Build 32-bit library, driver, docs and tests with release options.", + "inherits": "proplib-config-release-base", + "cacheVariables": { + "BUILD_32BIT": "ON" + } + }, + { + "name": "docsOnly", + "displayName": "Doxygen only", + "description": "Do not build or test the library or driver; only build Doxygen docs.", + "inherits": "proplib-config-base", + "cacheVariables": { + "BUILD_DOCS": "ON", + "DOCS_ONLY": "ON", + "RUN_TESTS": "OFF", + "BUILD_DRIVER": "OFF", + "RUN_DRIVER_TESTS": "OFF" + } + } + ], + "buildPresets": [ + { + "name": "proplib-build-base", + "hidden": true, + "description": "Base build preset for ITS PropLib libraries" + }, + { + "name": "proplib-build-release-base", + "hidden": true, + "inherits": "proplib-build-base", + "configuration": "Release", + "description": "Base 'Release' build preset for ITS PropLib libraries", + "cleanFirst": true + }, + { + "name": "proplib-build-debug-base", + "hidden": true, + "inherits": "proplib-build-base", + "configuration": "Debug", + "description": "Base 'Debug' build preset for ITS PropLib libraries", + "verbose": true + }, + { + "name": "debug64", + "inherits": "proplib-build-debug-base", + "displayName": "Build Debug, 64-Bit", + "description": "Build library, driver and tests with debug options, skip building docs", + "configurePreset": "debug64" + }, + { + "name": "release64", + "inherits": "proplib-build-release-base", + "displayName": "Build Release, 64-Bit", + "description": "Build library, driver, docs and tests with release options", + "configurePreset": "release64" + }, + { + "name": "debug32", + "inherits": "proplib-build-debug-base", + "displayName": "Build Debug, 32-Bit", + "description": "Build library, driver and tests with debug options, skip building docs", + "configurePreset": "debug32" + }, + { + "name": "release32", + "inherits": "proplib-build-release-base", + "displayName": "Build Release, 32-Bit", + "description": "Build library, driver, docs and tests with release options", + "configurePreset": "release32" + }, + { + "name": "docsOnly", + "inherits": "proplib-build-base", + "displayName": "Build Doxygen Docs Only", + "description": "Do not build the library; only build Doxygen docs.", + "configurePreset": "docsOnly" + } + ], + "testPresets": [ + { + "name": "proplib-test-base", + "hidden": true, + "description": "Base test preset for ITS PropLib libraries", + "output": { + "shortProgress": true, + "outputOnFailure": true + } + }, + { + "name": "proplib-test-debug-base", + "hidden": true, + "inherits": "proplib-test-base", + "description": "Base 'Debug' test preset for ITS PropLib libraries" + }, + { + "name": "proplib-test-release-base", + "hidden": true, + "inherits": "proplib-test-base", + "description": "Base 'Release' test preset for ITS PropLib libraries" + }, + { + "name": "debug64", + "inherits": "proplib-test-debug-base", + "displayName": "Test Debug, 64-bit", + "description": "Build library, driver and tests with debug options, skip building docs", + "configurePreset": "debug64" + }, + { + "name": "release64", + "inherits": "proplib-test-release-base", + "displayName": "Test Release, 64-Bit", + "description": "Build library, driver and tests with release options, and build docs", + "configurePreset": "release64" + }, + { + "name": "debug32", + "inherits": "proplib-test-debug-base", + "displayName": "Test Debug, 32-Bit", + "description": "Build library, driver and tests with debug options, skip building docs", + "configurePreset": "debug32" + }, + { + "name": "release32", + "inherits": "proplib-test-release-base", + "displayName": "Test Release, 32-Bit", + "description": "Build library, driver and tests with release options, and build docs", + "configurePreset": "release32" + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e425fa1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,367 @@ +# NTIA/ITS Propagation Library Contribution Guide + +Thank you for your interest in contributing to this open source software. On this +page you will get an overview of the contribution workflow from opening an issue, +creating a PR, reviewing, +and merging the PR. This page also includes some information about the project +structures, development workflows, and code styles which are used throughout the +ITS Propagation Library. + +If you are instead interested in usage documentation, please refer to the +[Propagation Library Wiki](https://ntia.github.io/propagation-library-wiki). + +## Contents + +- [Found a Bug?](#found-a-bug) +- [Background for New Contributors](#background-for-new-contributors) +- [Notes on Code Style](#notes-on-code-style) +- [Project Structure and CMake](#project-structure-and-cmake) +- [Documenting Code](#documenting-code) +- [Testing Code](#testing-code) + +## Found a Bug? + +If you spot a problem with this software, +[search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). +If a related issue doesn't exist, we encourage you to open one (even if you +don't plan to contribute a resolution yourself). Issues may be opened for bugs, +documentation errors, or feature requests. + +## Background for new contributors + +The workflow we recommend and describe here follows from best and common +practices in the Git and GitHub ecosystems. We aim to leverage this workflow, +especially the elements of code review and approval, to enable open source +development of robust, trustworthy radio propagation software. Here are some +resources to help you get started with open source contributions: + +- [Set up Git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) +- [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) +- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests) +- [Basic explanation of Git submodules](https://gist.github.com/gitaarik/8735255) +by [**@gitaarik**](https://github.com/gitaarik) + +### Git Branches + +Our repositories use the following approach to organize and keep track of branches. +The `main` branch typically represents the most recently released version of the software. +The `dev` branch stages changes before they are merged into `main` and a new release is created. +New features or bug fixes should be developed on individual "feature branches" with descriptive names. +When complete, features branches should merge into `dev`. + +### Git Submodules + +PropLib C++ repositories make use of Git submodules to reference certain development +dependencies, e.g. GoogleTest. Depending on the CMake preset or options used, submodules +may be required to successfully build and/or test the software. When cloning a repository, +submodules are not additionally cloned by default. Use the following commands to initialize +and clone any submodules in a repository: + +```cmd +git submodule init +git submodule update +``` + +### Contributing on GitHub + +If you'd like to solve an existing issue, add a new feature, or modify this software, +follow these steps when making your changes. + +1. Fork the repository. This allows you to make your changes without affecting the +original project until you're ready to merge them. You can create a fork +[with GitHub Desktop](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop) +or [using the command line](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) + +1. Create a working branch and start with your changes! Commit changes +incrementally to your fork. See the sections below for details about unit tests, +code style, and documentation. + +1. When you're done making changes, create a pull request (PR). In your PR, please include +a meaningful description of the changes you've made. If your PR solves an issue, +[link to it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)! + +Once you submit your PR, a maintainer will review your changes to determine +whether or not they should be merged. We may ask questions or request additional +changes which must be addressed. For example, we may request changes so that the code +meets structure, formatting, accuracy, or testing requirements. + +If your PR is approved and merged, your changes will be a part of the `dev` +branch of the repository, where they will stay until a new release is made. At that +point, `dev` will merge into `main` and a new release will be created. The maintainers +of a repository hold the authority on when a new release should be created. For example, +important bug fixes may take higher priority, while small improvements may stay on `dev` +for a while. Rest assured, even if a new release is not immediately made, your approved +changes will be always packaged into the next release. + +## Notes on Code Style + +- In general, variables follow the naming convention in which a single underscore +denotes a subscript (pseudo-LaTeX format), where a double underscore is followed +by the units, i.e. `h_1__meter`. +- Variables are named to match their corresponding mathematical variables in the +underlying text, when applicable. +- Wherever possible, equation numbers are provided. It is assumed that a user +reviewing this source code would have a copy of the relevant text available +as a primary reference. +- _For base/C++ repositories_, a `.clang-format` file is included in the root directory. +Most IDEs support this type of file, which can and should be used to apply uniform +code styling to C++ source and header files. +- _For Python wrapper repositories_, a `.pre-commit-config.yaml` file is included +in the root directory. This file implements multiple hooks for the [pre-commit](https://pre-commit.com/) +tool, which apply automated formatting to files when they are committed to Git. +It is recommended to use this tool to autoformat Python code when checked in. + +## Project Structure and CMake + +Software in the ITS Propagation Library is primarily implemented in C++, then +wrapped with interfaces exposing the C++ library to users of other languages. The +primary repository for each software package uses [CMake](https://cmake.org/) to +handle cross-platform C++ build configuration, C++ unit tests (with +[GoogleTest](https://github.com/google/googletest) and +[CTest](https://cmake.org/cmake/help/latest/manual/ctest.1.html)), and generation of +API documentation (with [Doxygen](https://www.doxygen.nl/)). Many IDEs support CMake +integration in some form or fashion, and it is recommended that you familiarize yourself +with any such functionality of your chosen IDE. + +This section shows a typical project structure for a primary (i.e., non-wrapper) +repository. For details about wrapper repositories, refer to their own README files. + +```bash +app/ # The command-line driver which can run the library + include/ # Headers used by the command-line driver + src/ # Source code for the command-line driver + tests/ # Header and source files for testing the command-line driver + CMakeLists.txt # Configuration for the command-line driver and its tests +docs/ + CMakeLists.txt # Doxygen configuration + ... # Static files (images, HTML, CS, Markdown) used by Doxygen +extern/ + test-data/ # Git submodule containing test data files shared with wrappers + ... # Other external Git submodules/dependencies +include/ + .h # Library interface header file goes here, e.g. "ITM.h" +src/ + .cpp # Source files go here, e.g. "LongleyRice.cpp" and "FreeSpaceLoss.cpp" + CMakeLists.txt # Configures cross-platform build +tests/ + .cpp # Unit tests, usually one test file per source file. + .h # Any headers used by tests go here as well. + CMakeLists.txt # CTest+GTest config. Files containing tests must be included here. +CMakeLists.txt # Top-level CMakeLists.txt: project metadata and options +CMakePresets.json # Presets for CMake, e.g. "release64", "debug32", etc. +... +``` + +### CMake Options and CMake Presets + +As you can see, multiple `CMakeLists.txt` files exist within the project. Each +one contains configurations relevant to the directory where it is stored. For +example, the `tests/CMakeLists.txt` file configures unit tests using CMake. The +top-level `CMakeLists.txt` stores the primary project configuration and includes +the lower-level configurations based on the preset or specified CMake options. + +The following CMake options are used for top-level project configuration: + +| Option | Default | Definition | +|--------------------|---------|------------------------------------------| +| `BUILD_DOCS` | `ON` | Generate documentation site with Doxygen | +| `BUILD_DRIVER` | `ON` | Build the command-line driver executable | +| `RUN_DRIVER_TESTS` | `ON` | Test the command-line driver executable | +| `DOCS_ONLY` | `OFF` | Skip all steps _except_ generating the documentation site | +| `RUN_TESTS` | `ON` | Run unit tests for the main library | + +[CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) are +provided to support common build configurations. These are specified in the +`CMakePresets.json` file. The `release` preset will compile the library and driver +with optimizations, build the documentation site, and run all unit tests. The `debug` preset +will skip building the documentation site, driver, and driver tests, which can be useful for +rapid development and testing. Additionally, the Debug configuration will attempt to pass +debug flags to the compiler. Finally, the "docsOnly" preset skips all steps except for +generating the Doxygen documentation site. + +| Option | `release` preset | `debug` preset | `docsOnly` preset | +|--------------------|------------------|----------------|-------------------| +| `DOCS_ONLY` | `OFF` | `OFF` | `ON` | +| `RUN_TESTS` | `ON` | `ON` | `OFF` | +| `CMAKE_BUILD_TYPE` | `Release` | `Debug` | not set | +| `BUILD_DOCS` | `ON` | `OFF` | `ON` | +| `BUILD_DRIVER` | `ON` | `OFF` | `OFF` | +| `RUN_DRIVER_TESTS` | `ON` | `OFF` | `OFF` | + +Below are some examples of how CMake can be called to compile this software. + +```bash +# Configure and compile in 64-bit release configuration +cmake --preset release64 +cmake --build --preset release64 + +# Use the 64-bit release configuration but don't build Doxygen docs +cmake --preset release64 -DBUILD_DOCS=OFF +cmake --build --preset release64 + +# Configure and compile in 32-bit debug configuration +cmake --preset debug32 +cmake --build --preset debug32 + +# Use the 64-bit release configuration but don't run driver tests +cmake --preset release64 -DRUN_DRIVER_TESTS=OFF +cmake --build --preset release64 +``` + +### Supported Platforms and Build Options + +The provided `CMakeLists.txt` and `CMakePresets.json` files aim to be flexible +for development from the platform of your choosing. The approach taken is to make +few assumptions about your toolchain to implicitly enable cross-platform and +multi-environment development as much as possible. However, we cannot guarantee +that all compilers, tools, and platforms will work without requiring some additional +configuration which is not documented here. If you find an issue or would like to +see a change to support your chosen platform or tools, open an issue or create a +pull request! + +## Documenting Code + +### C++ Base Libraries + +The C++ source code is documented with Doxygen. A GitHub Action is configured to +build and deploy the documentation using GitHub Pages. This action will ensure +that any new code has been accompanied by Doxygen-formatted documentation. Code +will not be merged until and unless it is completely documented using Doxygen, +and the GitHub action successfully generates the documentation site. Below is an +example showing the expected documentation formats. Except for inline documentation, +use the JavaDoc banner style [described by Doxygen](https://www.doxygen.nl/manual/docblocks.html) + +```cpp +constexpr double = PI 3.1415; /**< Inline format, e.g. for constants */ + +/******************************************************************************* + * This is a brief description of the function. + * + * This is an optional, longer description of the function. It can include + * LaTeX formatting, for example: this function doubles its input @f$ x @f$ and + * returns a value @f$ y @f$ with @f$ y = 2x @f$. This whole documentation block + * is using the JavaDoc banner style! + * + * @param[in] x The input and its expected units + * @return The result @f$ y = 2x @f$ + ******************************************************************************/ +double doubleTheInput(double x) +{ + return 2 * x; +} +``` + +### Doxygen for C++ Libraries + +The base C++ libraries include Doxygen configurations which generate static +websites from code comments. These documentation sites are published as developer +reference documentation using GitHub Pages. When building the Doxygen site locally, +The site is generated in `docs/html/` and the main page can be accessed at `docs/html/index.html`. +When new releases are made, GitHub Actions workflows are triggered which build and deploy +the Doxygen site to GitHub Pages. + +### MATLAB Wrappers + +MATLAB® wrappers are implemented as toolboxes which interface with the shared library +compiled from C++ source code. The project structure is informed by the best practices +provided by MathWorks® in their [`toolboxdesign` repository](https://github.com/mathworks/toolboxdesign). +Here is an example of how a function may be documented in a MATLAB wrapper. Note the +documentation with code, where input and output arguments are provided for autocompletion. + +```matlab +function y = DoubleTheInput(x) +% DoubleTheInput - produces an output which is twice its input. +% +% Syntax: +% y = DoubleTheInput(x) +% +% Input Arguments: +% x (double) - A number which needs doubling +% +% Output Arguments: +% y (double) - The result, 2*x +% +% Description: +% Functions more complex than this one may warrant an additional, +% longer description. +arguments (Input) + x double +end +arguments (Output) + y double +end +... +``` + +### Python Wrappers + +The Python wrapper code is documented in the [Sphinx](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html) +format. It is recommended to include docstrings for all primary functions, classes, +or structures provided by the Python wrapper. Further, function signatures should +include [type annotation](https://docs.python.org/3/library/typing.html) for inputs +and returned values. Inline or other comments should be included to explain other +variables or functionalities of the code. Below is an example showing the recommended +documentation format. + +```python + +CONSTANT_EXPOSED_BY_MODULE = 42 # A brief comment could explain what this is + +def double_the_input(x: float) -> float: + """This is a brief description of the function. + + This is an optional, longer description of the function. + It can span multiple lines. + + :param x: The input value, and its expected units. + :return: The result y = 2*x + """ + return 2 * x +``` + +### .NET Wrappers + +PropLib .NET wrappers are written in C# and documentation comments are written in +[XML format](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments) +and are used to generate documentation through tools like Visual Studio. Use `` tags to +provide brief descriptions of classes, constants, functions, etc. Functions should +include `` and `` elements for all inputs and outputs. An example +of this documentation style is shown below. + +```csharp +/// +/// Represents a class that contains constants and methods related to calculations. +/// +public class CalculationUtils +{ + /// + /// A constant value exposed by the module. + /// + public const int CONSTANT_EXPOSED_BY_MODULE = 42; + + /// + /// Doubles the input value. + /// + /// The input value to be doubled. + /// The doubled value of the input. + public double DoubleTheInput(double x) + { + // Brief comment explaining what this function does. + return 2 * x; + } +} +``` + +## Testing Code + +When modifying or extending this software, ensure that unit tests are added to +cover your new code. In general, each C++ file in `src/` has a corresponding C++ +file in `tests/` which implements unit tests. If you've added a new file in `tests/`, +make sure to add that file to the executable in `tests/CMakeLists.txt`. + +After compiling the library, you can run unit tests as follows. First, change your +working directory to the `build` directory, then run: + +```bash +ctest +``` diff --git a/GitHubRepoPublicReleaseApproval.md b/GitHubRepoPublicReleaseApproval.md new file mode 100644 index 0000000..c1532e3 --- /dev/null +++ b/GitHubRepoPublicReleaseApproval.md @@ -0,0 +1,32 @@ +# GitHub Repository Public Release Approval + +**Project Name:** NTIA/OSM Research and Development - Propagation Library + +**Software Name:** Recommendation ITU-R P.2108 + +The project identified above, which is contained within the repository this +document is stored in, has met the following criteria for public release: + +1. [x] The project, including the test criteria, meets the requirements defined +in the ITS Software Development Publication Policy for making a repository public. +The major pre-established criteria for publication are listed below, and the check +mark next to each attests that the criterion has been met. + * [x] Unit tests are available and the software has been tested against the unit tests. + * [x] The software can be compiled and/or used on Windows, macOS, and Linux. + * [x] The repository structure and contents follow from the ITS PropLib template, and + all template or placeholder code has been removed. + * [x] The repository includes the appropriate `LICENSE.md` file +2. [x] Any test data necessary for the code and its unit tests to function is included in this +GitHub repository, either directly or as a linked Git submodule. +3. [x] The README.md file has passed editorial review by the ITS Publications Office. +4. [x] The project complies with the ITS Code Style Guide or an appropriate style +guide as agreed to by the sponsor, project lead, or Supervising Division Chief. +5. [x] Approved disclaimer and licensing language has been included. + +In order to complete this approval, please create a new branch, upload and commit +your version of this Markdown document to that branch, and then create a pull request +for that branch. The following must log in to GitHub and approve that pull request +before the pull request can be merged and this repo made public: + +* Project Lead: William Kozma, Jr. +* Supervising Division Chief or Release Authority: Chris Anderson diff --git a/HeightGainTerminalCorrectionModelTestData.csv b/HeightGainTerminalCorrectionModelTestData.csv deleted file mode 100644 index c26e648..0000000 --- a/HeightGainTerminalCorrectionModelTestData.csv +++ /dev/null @@ -1,24 +0,0 @@ -f__ghz,h__meter,w_s__meter,R__meter,clutter_type,rtn,A_h__db -1.5,2,27,10,1,0,16 -1.5,2,27,6,1,0,10.9 -1.5,2,27,10,2,0,16 -1.5,2,27,6,2,0,10.9 -1.5,2,27,10,3,0,20.5 -1.5,2,27,6,3,0,14.6 -1.5,2,27,15,4,0,24.5 -1.5,2,27,6,4,0,14.6 -1.5,2,27,15,5,0,24.5 -1.5,2,27,6,5,0,14.6 -1.5,2,27,20,6,0,27.1 -1.5,2,27,6,6,0,14.6 -3,3,15,15,6,0,29 -0.9,2.3,30,10,2,0,13.7 -1.5,2.5,25,10,3,0,20.2 -0.03,2.1,24.5,9.8,3,0,5.7 -1.7,30,24.5,9.8,3,0,0 -1.7,30,24.5,30,3,0,0 -0.02,2,27,10,3,3100,0 -4,2,27,10,3,3100,0 -1,0,10,9,2,3101,0 -2,1,0,9,6,3102,0 -2,1,27,0,6,3103,0 \ No newline at end of file diff --git a/README.md b/README.md index 34eb571..354dcaa 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,126 @@ -# Recommendation ITU-R P.2108-1 - U.S. Reference Implementation # +# Recommendation ITU-R P.2108 # + +[![NTIA/ITS PropLib][proplib-badge]][proplib-link] +[![GitHub Release][gh-releases-badge]][gh-releases-link] +[![GitHub Actions Unit Test Status][gh-actions-test-badge]][gh-actions-test-link] +[![C++ API Reference][gh-actions-docs-badge]][gh-pages-docs-link] +[![GitHub Issues][gh-issues-badge]][gh-issues-link] +[![DOI][doi-badge]][doi-link] + +[proplib-badge]: https://img.shields.io/badge/PropLib-badge?label=%F0%9F%87%BA%F0%9F%87%B8%20NTIA%2FITS&labelColor=162E51&color=D63E04 +[proplib-link]: https://ntia.github.io/propagation-library-wiki +[gh-actions-test-badge]: https://img.shields.io/github/actions/workflow/status/NTIA/p2108/ctest.yml?branch=main&logo=cmake&label=Build%2FTests&labelColor=162E51 +[gh-actions-test-link]: https://github.com/NTIA/p2108/actions/workflows/ctest.yml +[gh-actions-docs-badge]: https://img.shields.io/github/actions/workflow/status/NTIA/p2108/doxygen.yml?branch=main&logo=c%2B%2B&label=Docs&labelColor=162E51 +[gh-pages-docs-link]: https://ntia.github.io/p2108 +[gh-releases-badge]: https://img.shields.io/github/v/release/NTIA/p2108?logo=github&label=Release&labelColor=162E51&color=D63E04 +[gh-releases-link]: https://github.com/NTIA/p2108/releases +[gh-issues-badge]: https://img.shields.io/github/issues/NTIA/p2108?logo=github&label=Issues&labelColor=162E51 +[gh-issues-link]: https://github.com/NTIA/p2108/issues +[doi-badge]: https://zenodo.org/badge/384267941.svg +[doi-link]: https://zenodo.org/badge/latestdoi/384267941 + +This repository contains the NTIA/ITS implementation of Recommendation ITU-R +P.2108. This Recommendation contains three methods for the prediction of clutter +loss: the Height Gain Terminal Correction Model, the Terrestrial Statistical Model, +and the Aeronautical Statistical Model. The software implements Section 3 of Annex 1 +of the Recommendation. + +Additional bindings to the shared library built from this repository are provided +for .NET, MATLAB®, and Python® in the following repositories: + +- [NTIA/p2108-dotnet](https://github.com/NTIA/p2108-dotnet) +- [NTIA/p2108-matlab](https://github.com/NTIA/p2108-matlab) +- [NTIA/p2108-python](https://github.com/NTIA/p2108-python) + +## Getting Started ## + +To get started using this library, refer to +[its page on the **NTIA/ITS Propagation Library Wiki**](https://ntia.github.io/propagation-library-wiki/models/P2108/). +There, you will find installation instructions, usage information, and code +examples for all supported languages. + +An executable is also provided which can be used to run the functions provided +by this library using plain text input and output files. Installation and usage +details for the command-line driver are also provided on +[the wiki](https://ntia.github.io/propagation-library-wiki/models/P2108/driver). + +If you're a developer and would like to contribute to or extend this repository, +you will find comprehensive documentation of this C++ code +[here](https://ntia.github.io/P2108), and a guide for contributors +[here](CONTRIBUTING.md). -Persistent Identifier: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7114033.svg)](https://doi.org/10.5281/zenodo.7114033) - -This code repository contains the U.S. Reference Software Implementation of -Recommendation ITU-R P.2108. This Recommendation contains three methods for the -prediction of clutter loss: [Height Gain Terminal Correction Model](README.md#height-gain-terminal-correction-model), -[Terrestrial Statistical Model](README.md#terrestrial-statistical-model), -[Aeronautical Statistical Model](README.md#aeronautical-statistical-model). -The software implements Section 3 of Annex 1 of the Recommendation. - -## Height Gain Terminal Correction Model ## - -The height gain terminal correction model is described in Section 3.1. -This end-point clutter method gives the median loss due to different terminal -surroundings for terrestrial paths for frequencies between 0.3 to 3 GHz. This -model can be applied to both transmitting and receiving ends of the path. - -### Inputs (Height Gain Terminal Correction Model) ### - -| Variable | Type | Units | Limits | Description | -|----------------|--------|-------|----------------------|-------------------------------| -| `f__ghz` | double | GHz | 0.3 <= `f__ghz` <= 3 | Frequency | -| `h__meter` | double | meter | 0 <= `h__meter` | Antenna height | -| `w_s__meter` | double | meter | 0 < `w_s__meter` | Street width | -| `R__meter` | double | meter | 0 < `R__meter` | Representative clutter height | -| `clutter_type` | int | | | Clutter type
  • 1 = Water/sea
  • 2 = Open/rural
  • 3 = Suburban
  • 4 = Urban
  • 5 = Trees/forest
  • 6 = Dense urban
| - -Where site-specific values for the representative clutter height are not available, -the following default values are recommended. - -| Clutter Type | `R__meter` | -|--------------|:----------:| -| Water/sea | 10 | -| Open/rural | 10 | -| Suburban | 10 | -| Urban | 15 | -| Trees/forest | 15 | -| Dense urban | 20 | - -### Outputs (Height Gain Terminal Correction Model) ### - -| Variable | Type | Units | Description | -|------------|--------|-------|-------------| -| `A_h__db` | double | dB | Additional loss (clutter loss) | - -### Return Codes (Height Gain Terminal Correction Model) ### - -Possible return codes, including the corresponding defined constant name as -defined in [`Errors.h`](include/Errors.h): - -| Value | Const Name | Description | -|-------|---------------------------|----------------------------------------------------| -| 0 | `SUCCESS` | Successful execution | -| 3100 | `ERROR31__FREQUENCY` | Frequency must be between 0.3 and 3 GHz, inclusive | -| 3101 | `ERROR31__ANTENNA_HEIGHT` | Antenna height must be >= 0 meters | -| 3102 | `ERROR31__STREET_WIDTH` | Street width must be > 0 meters | -| 3103 | `ERROR31__CLUTTER_HEIGHT` | Representative clutter height must be > 0 meters | -| 3104 | `ERROR31__CLUTTER_TYPE` | Invalid value for clutter type | - -## Terrestrial Statistical Model ## - -The statistical clutter loss model for terrestrial paths as described in Section -3.2. This model is valid for urban and suburban clutter environments. For paths -between 0.25 and 1 km, this model can only be applied to one end of the path. -For paths greater than 1 km, the model can be applied to both terminals, if desired. - -### Inputs (Terrestrial Statistical Model) ### - -| Variable | Type | Units | Limits | Description | -|----------|--------|-------|---------------------|---------------| -| `f__ghz` | double | GHz | 2 <= `f__ghz` <= 67 | Frequency | -| `d__km` | double | km | 0.25 <= `d__km` | Path distance | -| `p` | double | % | 0 < `p` < 100 | Percentage of locations clutter loss not exceeded | - -### Outputs (Terrestrial Statistical Model) ### - -| Variable | Type | Units | Description | -|------------|--------|-------|-------------| -| `L_ctt__db` | double | dB | Clutter loss | - -### Return Codes (Terrestrial Statistical Model) ### - -Possible return codes, including the corresponding defined constant name as -defined in [`Errors.h`](include/Errors.h): - -| Value | Const Name | Description | -|-------|-----------------------|---------------------------------------------------| -| 0 | `SUCCESS` | Successful execution | -| 3200 | `ERROR32__FREQUENCY` | Frequency must be between 2 and 67 GHz, inclusive | -| 3201 | `ERROR32__DISTANCE` | Path distance must be >= 0.25 km | -| 3202 | `ERROR32__PERCENTAGE` | Percentage must be between 0 and 100 | +## Configure and Build ## -## Aeronautical Statistical Model ## +The software is designed to be built into a DLL (or corresponding `.so` or `.dylib` +library for non-Windows systems). A CMake build configuration and presets are +provided for cross-platform builds. Presets provide default sets of compiler flags, +and additional set default CMake options to control which parts of the project are +build. Below are a few examples of how this project can be built using provided presets. -The Earth-space and aeronautical statistical clutter loss model as described in -Section 3.3. This model is applicable when one end of the path is within man-made -clutter and the other end is a satellite, aeroplane, or other platform above the -Earth. This model is valid for urban and suburban clutter environments. +```cmd +# From this repository's root directory, try one of the following command pairs: -### Inputs (Aeronautical Statistical Model) ### +# "Release" configurations compile the library and driver, build docs, and configure tests: +cmake --preset release64 +cmake --build --preset release64 -| Variable | Type | Units | Limits | Description | -|--------------|--------|-------|-------------------------|-----------------| -| `f__ghz` | double | GHz | 10 <= `f__ghz` <= 100 | Frequency | -| `theta__deg` | double | deg | 0 <= `theta__deg` <= 90 | Elevation angle | -| `p` | double | % | 0 < `p` < 100 | Percentage of locations clutter loss not exceeded | +# "Debug" configurations skip building the docs, driver, and driver tests: +cmake --preset debug64 +cmake --build --preset debug64 -### Outputs (Aeronautical Statistical Model) ### +# Additional options can override presets: +cmake --preset debug64 -DBUILD_DRIVER=ON -| Variable | Type | Units | Description | -|------------|--------|-------|-------------| -| `L_ces__db` | double | dB | Clutter loss | +# "DocsOnly" configurations only build the docs: +cmake --preset docsOnly +cmake --build --preset docsOnly +``` -### Return Codes (Aeronautical Statistical Model) ### +Note that this repository makes use of several +[Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) +to reference dependencies used for running unit tests and building documentation. +In order to do either, ensure the required submodules are cloned by running: -Possible return codes, including the corresponding defined constant name as defined -in [`Errors.h`](include/Errors.h): +```cmd +# From this repository's root directory +git submodule init +git submodule update +``` -| Value | Const Name | Description | -|-------|-----------------------|----------------------------------------------------------| -| 0 | `SUCCESS` | Successful execution | -| 3300 | `ERROR33__FREQUENCY` | Frequency must be between 10 and 100 GHz, inclusive | -| 3301 | `ERROR33__THETA` | Elevation angle must be between 0 and 100 GHz, inclusive | -| 3302 | `ERROR33__PERCENTAGE` | Percentage must be between 0 and 100, inclusive | +## Running Tests ## -## Example Values ## +If you've configured tests when building the project, for example by using one of +the "Release" or "Debug" CMake presets, you can run the included unit tests as follows: -The [Study Group Clutter Excel Workbook](https://www.itu.int/en/ITU-R/study-groups/rsg3/ionotropospheric/Clutter%20and%20BEL%20workbook_V2.xlsx) -contains an extensive set of validation example values. +```cmd +ctest --preset release64 +``` -## Notes on Code Style ## +Additionally, the [Study Group Clutter Excel Workbook](https://www.itu.int/en/ITU-R/study-groups/rsg3/ionotropospheric/Clutter%20and%20BEL%20workbook_V2.xlsx) +contains an extensive set of example values which are useful as validation cases. -* In general, variables follow the naming convention in which a single underscore -denotes a subscript (pseudo-LaTeX format), where a double underscore is followed -by the units, i.e. `h_1__meter`. -* Variables are named to match their corresponding mathematical variables in the -underlying Recommendation text. -* Wherever possible, equation numbers are provided. It is assumed that a user -reviewing this source code would have a copy of the Recommendation's text available -as a primary reference. +## References ## -## Configure and Build ## +- [ITS Propagation Library Wiki](https://ntia.github.io/propagation-library-wiki) +- [P2108 Wiki Page](https://ntia.github.io/propagation-library-wiki/models/P2108) +- [`ITS.ITU.PSeries.P2108` C++ API Reference](https://ntia.github.io/P2108) +- [Recommendation ITU-R P.2108](https://www.itu.int/rec/R-REC-P.2108/en) +- [Report ITU-R P.2402](https://www.itu.int/pub/R-REP-P.2402) -### C++ Software ### +## License ## -The software is designed to be built into a DLL (or corresponding library for -non-Windows systems). The source code can be built for any OS that supports the -standard C++ libraries. A Visual Studio 2019 project file is provided for Windows -users to support the build process and configuration. +See [`LICENSE.md`](./LICENSE.md). -### C#/.NET Wrapper Software ### +MATLAB is a registered trademark of The MathWorks, Inc. See +[mathworks.com/trademarks](https://mathworks.com/trademarks) for a list of additional trademarks. -The .NET support of P.2108 consists of a simple pass-through wrapper around the -native DLL. It is compiled to target .NET Framework 4.8.1. Distribution and updates -are provided through the published [NuGet package](https://github.com/NTIA/p2108/packages). +"Python" and the Python logos are trademarks or registered trademarks of the Python Software Foundation, used by the National Telecommunications and Information Administration with permission from the Foundation. -## References ## +## Contact ## -* [Recommendation ITU-R P.2108](https://www.itu.int/rec/R-REC-P.2108/en) -* [Report ITU-R P.2402](https://www.itu.int/pub/R-REP-P.2402) +For technical questions, contact . -## Contact ## +## Disclaimer ## -For questions, contact Billy Kozma, +Certain commercial equipment, instruments, or materials are identified in this project were used for the convenience of the developers. In no case does such identification imply recommendation or endorsement by the National Telecommunications and Information Administration, nor does it imply that the material or equipment identified is necessarily the best available for the purpose. diff --git a/TerrestrialStatisticalModelTestData.csv b/TerrestrialStatisticalModelTestData.csv deleted file mode 100644 index 589034c..0000000 --- a/TerrestrialStatisticalModelTestData.csv +++ /dev/null @@ -1,13 +0,0 @@ -f__ghz,d__km,p,rtn,L_ctt__db -0.5,0.25,50,0,17.4 -0.5,0.25,1,0,3.9 -0.5,0.25,99,0,30.9 -26.6,15.8,45,0,32.5 -67,5.4,30.5,0,30.9 -3.5,1,0.1,0,16.8 -3.5,1,99.9,0,42.8 -0.24,2,50,3200,0 -67.1,5,50,3200,0 -10,0.24,50,3201,0 -6,3,0,3202,0 -6,3,100,3202,0 diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..388d4cc --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,24 @@ +########################################### +## CONFIGURE THE COMMAND LINE DRIVER +########################################### +set(DRIVER_NAME "${LIB_NAME}Driver") +set(DRIVER_VERSION ${PROJECT_VERSION}.0) +set(DRIVER_HEADERS "${PROJECT_SOURCE_DIR}/app/include") +set(DRIVER_TEST_NAME "${DRIVER_NAME}Test") + +########################################### +## BUILD THE COMMAND LINE DRIVER +########################################### +proplib_message("Configuring command line driver ${DRIVER_NAME}") +add_subdirectory(src) +proplib_message("Done configuring command line driver ${DRIVER_NAME}") + +########################################### +## BUILD AND RUN THE DRIVER TESTS +########################################### +if (RUN_DRIVER_TESTS) + proplib_message("Configuring command line driver tests ${DRIVER_TEST_NAME}") + set_target_properties(${DRIVER_NAME} PROPERTIES ENABLE_EXPORTS ON) + add_subdirectory(tests) + proplib_message("Done configuring command line driver tests ${DRIVER_TEST_NAME}") +endif() diff --git a/app/include/CommaSeparatedIterator.h b/app/include/CommaSeparatedIterator.h new file mode 100644 index 0000000..a83a0ac --- /dev/null +++ b/app/include/CommaSeparatedIterator.h @@ -0,0 +1,42 @@ +/** @file CommaSeparatedIterator.h + * Iterator class for reading comma-delimited input text streams. + */ +#pragma once + +#include // for std::istream +#include // For std::string +#include // for std::pair + +/******************************************************************************* + * @class CommaSeparatedIterator + * An iterator that reads lines from an input stream, splitting each line + * into two strings based on a comma delimiter. + * + * This iterator can work with both `std::stringstream` and `std::ifstream`. + ******************************************************************************/ +class CommaSeparatedIterator { + public: + /** Type alias for the value returned by the iterator (pair of strings) */ + using value_type = std::pair; + + /*********************************************************************** + * Constructor method + * + * @param[in] stream The input stream which will be read + **********************************************************************/ + CommaSeparatedIterator(std::istream &stream); + + /** Pre-increment operator to advance the iterator to the next line */ + CommaSeparatedIterator &operator++(); + + /** Dereference operator to obtain the current pair of substrings */ + value_type operator*() const; + + /** Conversion to boolean to check if the iterator is valid */ + explicit operator bool() const; + private: + std::istream &stream_; /**< Reference to the input stream */ + std::string line_; /**< Current line read from the stream */ + std::string first_; /**< First string from the current line */ + std::string second_; /**< Second string from the current line */ +}; diff --git a/app/include/Driver.h b/app/include/Driver.h new file mode 100644 index 0000000..2cf519d --- /dev/null +++ b/app/include/Driver.h @@ -0,0 +1,74 @@ +/** @file Driver.h + * Interface header for this driver executable + */ +#pragma once + +#include "CommaSeparatedIterator.h" +#include "P2108.h" +#include "ReturnCodes.h" +#include "Structs.h" + +#include // for std::ofstream +#include // for std::left, std::setw +#include // for std::cout +#include // for std::endl, std::ostream +#include // for std::string +#include // for std::vector + +///////////////////////////// +// Macros + +/** Shortcut for concise print-to-file statements in driver */ +#define PRINT << std::endl << std::left << std::setw(25) << +/** Shortcut for setting fixed whitespace padding in driver file output */ +#define SETW13 << std::setw(13) << + +////////////////////////////// +// Library Namespace +using namespace ITS::ITU::PSeries::P2108; + +///////////////////////////// +// Functions +void Help(std::ostream &os = std::cout); +DrvrReturnCode ParseArguments(int argc, char **argv, DrvrParams ¶ms); +DrvrReturnCode ValidateInputs(const DrvrParams ¶ms); + +// Aeronautical Statistical Model +ReturnCode CallAeronauticalStatisticalModel( + ASMParams &asm_params, std::vector &L_ces__db +); +DrvrReturnCode + ParseASMInputFile(const std::string &in_file, ASMParams &asm_params); +DrvrReturnCode ParseASMInputStream(std::istream &stream, ASMParams &asm_params); +void WriteASMInputs(std::ofstream &fp, const ASMParams ¶ms); + +// Height Gain Terminal Correction Model +ReturnCode CallHeightGainTerminalCorrectionModel( + HGTCMParams &hgtcm_params, std::vector &A_h__db +); +DrvrReturnCode + ParseHGTCMInputFile(const std::string &in_file, HGTCMParams &hgtcm_params); +DrvrReturnCode + ParseHGTCMInputStream(std::istream &stream, HGTCMParams &hgtcm_params); +void WriteHGTCMInputs(std::ofstream &fp, const HGTCMParams ¶ms); + +// Terrestrial Statistical Model +ReturnCode CallTerrestrialStatisticalModel( + TSMParams &tsm_params, std::vector &L_ctt__db +); +DrvrReturnCode + ParseTSMInputFile(const std::string &in_file, TSMParams &tsm_params); +DrvrReturnCode ParseTSMInputStream(std::istream &stream, TSMParams &tsm_params); +void WriteTSMInputs(std::ofstream &fp, const TSMParams ¶ms); + +// Reporting +void PrintClutterTypeLabel(std::ofstream &fp, const ClutterType clutter_type); + +// Driver Utils +std::string GetDatetimeString(); +DrvrReturnCode ParseBoolean(const std::string &str, bool &value); +DrvrReturnCode ParseDouble(const std::string &str, double &value); +DrvrReturnCode ParseInteger(const std::string &str, int &value); +void PrintLabel(std::ostream &os, const std::string &lbl); +void StringToLower(std::string &str); +void Version(std::ostream &os = std::cout); \ No newline at end of file diff --git a/app/include/ReturnCodes.h b/app/include/ReturnCodes.h new file mode 100644 index 0000000..4195c68 --- /dev/null +++ b/app/include/ReturnCodes.h @@ -0,0 +1,39 @@ +/** @file ReturnCodes.h + * Defines return codes for the driver + */ +#pragma once + +#include // for std::string + +/******************************************************************************* + * Return Codes defined by this driver software (128-255) + ******************************************************************************/ +// clang-format off +enum DrvrReturnCode { + // Primary Return Codes + DRVR__SUCCESS = 128, /**< Successful execution */ + DRVR__RETURN_SUCCESS, /**< Indicates driver should exit successfully */ + DRVRERR__MISSING_OPTION, /**< No value provided for given argument */ + DRVRERR__INVALID_OPTION, /**< Unknown option specified */ + DRVRERR__OPENING_INPUT_FILE, /**< Failed to open the input file for reading */ + DRVRERR__OPENING_OUTPUT_FILE, /**< Failed to open the output file for writing */ + + // Input File Parsing Errors + DRVRERR__PARSE = 160, /**< Failed parsing inputs; unknown parameter */ + DRVRERR__PARSE_FREQ, /**< Failed to parse frequency value */ + DRVRERR__PARSE_THETA, /**< Failed to parse theta value */ + DRVRERR__PARSE_PERCENTAGE, /**< Failed to parse percentage value */ + DRVRERR__PARSE_HEIGHT, /**< Failed to parse antenna height value */ + DRVRERR__PARSE_STREET_WIDTH, /**< Failed to parse street width value */ + DRVRERR__PARSE_REPR_HEIGHT, /**< Failed to parse representative height value */ + DRVRERR__PARSE_CLUTTER_TYPE, /**< Failed to parse clutter type value */ + DRVRERR__PARSE_PATH_DIST, /**< Failed to parse path distance value */ + + // Validation Errors + DRVRERR__VALIDATION_IN_FILE = 192, /**< Input file not specified */ + DRVRERR__VALIDATION_OUT_FILE, /**< Output file not specified */ + DRVRERR__VALIDATION_MODEL, /**< Model not specified */ +}; +// clang-format on + +std::string GetDrvrReturnStatusMsg(int code); diff --git a/app/include/Structs.h b/app/include/Structs.h new file mode 100644 index 0000000..bd6dd35 --- /dev/null +++ b/app/include/Structs.h @@ -0,0 +1,76 @@ +/** @file Structs.h + * Contains data structures and type macros used by this software +*/ +#pragma once + +#include "P2108.h" // For ClutterType enum + +#include // for std::string + +///////////////////////////// +// Enums +/** Valid values of "model" command line option */ +enum class P2108Model { + NOT_SET = -1, /**< Invalid model selection */ + HGTCM = 1, /**< Height Gain Terminal Correction Model */ + TSM = 2, /**< Terrestrial Statistical Model */ + ASM = 3, /**< Aeronautical Statistical Model */ +}; + +///////////////////////////// +// Data Structures + +/** Parameters provided to the command line driver */ +struct DrvrParams { + std::string in_file = ""; /**< Input file */ + std::string out_file = ""; /**< Output file */ + P2108Model model = P2108Model::NOT_SET; /**< Model selection */ +}; + +/** Input parameters for the Height Gain Terminal Correction Model */ +struct HGTCMParams { + double f__ghz; /**< Frequency, in GHz */ + double h__meter; /**< Antenna height, in meters */ + double w_s__meter; /**< Street width, in meters */ + double R__meter; /**< Representative clutter height, in meters */ + ITS::ITU::PSeries::P2108::ClutterType + clutter_type; /**< Clutter type (enum value) */ +}; + +/** Key names for Height Gain Terminal Correction Model input file parameters */ +struct HGTCMInputKeys { + static const std::string f__ghz; /**< Frequency, in GHz */ + static const std::string h__meter; /**< Antenna height, in meters */ + static const std::string w_s__meter; /**< Street width, in meters */ + static const std::string + R__meter; /**< Representative clutter height, in meters */ + static const std::string clutter_type; /**< Clutter type (enum value) */ +}; // Constants defined in app/src/HeightGainTerminalCorrectionModel.cpp + +/** Input parameters for the Terrestrial Statistical Model */ +struct TSMParams { + double f__ghz; /**< Frequency, in GHz */ + double d__km; /**< Path distance, in km */ + double p; /**< Percentage of locations */ +}; + +/** Key names for Terrestrial Statistical Model input file parameters */ +struct TSMInputKeys { + static const std::string f__ghz; /**< Frequency, in GHz */ + static const std::string d__km; /**< Path distance, in km */ + static const std::string p; /**< Percentage of locations */ +}; // Constants defined in app/src/TerrestrialStatisticalModel.cpp + +/** Input parameters for the Aeronautical Statistical Model */ +struct ASMParams { + double f__ghz; /**< Frequency, in GHz */ + double theta__deg; /**< Elevation angle, in degrees */ + double p; /**< Percentage of locations */ +}; + +/** Key names for Aeronautical Statistical Model input file parameters */ +struct ASMInputKeys { + static const std::string f__ghz; /**< Frequency, in GHz */ + static const std::string theta__deg; /**< Elevation angle, in degrees */ + static const std::string p; /**< Percentage of locations */ +}; // Constants defined in app/src/AeronauticalStatisticalModel.cpp \ No newline at end of file diff --git a/app/src/AeronauticalStatisticalModel.cpp b/app/src/AeronauticalStatisticalModel.cpp new file mode 100644 index 0000000..ad556ec --- /dev/null +++ b/app/src/AeronauticalStatisticalModel.cpp @@ -0,0 +1,106 @@ +/** @file AeronauticalStatisticalModel.cpp + * Implements top-level functions for running the Aeronautical Statistical Model. + */ +#include "Driver.h" + +#include // for std::ifstream, std::ofstream +#include // for std::cerr +#include // for std::istream +#include // for std::endl +#include // for std::string +#include // for std::tie +#include // for std::vector + +// Define the input keys +const std::string ASMInputKeys::f__ghz = "f__ghz"; +const std::string ASMInputKeys::theta__deg = "theta__deg"; +const std::string ASMInputKeys::p = "p"; + +/******************************************************************************* + * Top-level control function for Aeronautical Statistical Model operation + * + * @param[in] asm_params Aeronautical Statistical Model input parameter struct + * @param[out] L_ces__db Basic transmission loss, in dB + * @return Return code + ******************************************************************************/ +ReturnCode CallAeronauticalStatisticalModel( + ASMParams &asm_params, std::vector &L_ces__db +) { + ReturnCode rtn; + double L_ces; + rtn = AeronauticalStatisticalModel( + asm_params.f__ghz, asm_params.theta__deg, asm_params.p, L_ces + ); + L_ces__db.push_back(L_ces); + + return rtn; +} + +/******************************************************************************* + * Parse input stream (file or string stream) to ASM parameter struct. + * + * @param[in] stream Input stream containing ASM parameters + * @param[out] asm_params ASM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseASMInputStream(std::istream &stream, ASMParams &asm_params) { + CommaSeparatedIterator it(stream); + DrvrReturnCode rtn = DRVR__SUCCESS; + std::string key, value, errMsg; + while (it) { + std::tie(key, value) = *it; + if (key.compare(ASMInputKeys::f__ghz) == 0) { + rtn = ParseDouble(value, asm_params.f__ghz); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_FREQ; + } else if (key.compare(ASMInputKeys::theta__deg) == 0) { + rtn = ParseDouble(value, asm_params.theta__deg); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_THETA; + } else if (key.compare(ASMInputKeys::p) == 0) { + rtn = ParseDouble(value, asm_params.p); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_PERCENTAGE; + } else { + std::cerr << "Unknown parameter: " << key << std::endl; + rtn = DRVRERR__PARSE; + } + + if (rtn != DRVR__SUCCESS) { + std::cerr << GetDrvrReturnStatusMsg(rtn) << std::endl; + return rtn; + } + ++it; + } + return rtn; +} + +/******************************************************************************* + * Parse Aeronautical Statistical Model input parameter file + * + * @param[in] in_file Path to ASM input parameter file + * @param[out] asm_params ASM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseASMInputFile(const std::string &in_file, ASMParams &asm_params) { + std::ifstream file(in_file); + if (!file) { + std::cerr << "Failed to open file " << in_file << std::endl; + return DRVRERR__OPENING_INPUT_FILE; + } + return ParseASMInputStream(file, asm_params); +} + +/******************************************************************************* + * Write Aeronautical Statistical Model inputs to the report file + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] params ASM input parameter struct + ******************************************************************************/ +void WriteASMInputs(std::ofstream &fp, const ASMParams ¶ms) { + fp PRINT ASMInputKeys::f__ghz SETW13 params.f__ghz << "(gigahertz)"; + fp PRINT ASMInputKeys::theta__deg SETW13 params.theta__deg << "(degrees)"; + fp PRINT ASMInputKeys::p SETW13 params.p << "(%)"; +} \ No newline at end of file diff --git a/app/src/CMakeLists.txt b/app/src/CMakeLists.txt new file mode 100644 index 0000000..fe530e9 --- /dev/null +++ b/app/src/CMakeLists.txt @@ -0,0 +1,44 @@ +########################################### +## BUILD THE DRIVER +########################################### +# Driver name set in CMakeLists.txt one level above this one. +add_executable( + ${DRIVER_NAME} + "AeronauticalStatisticalModel.cpp" + "CommaSeparatedIterator.cpp" + "Driver.cpp" + "DriverUtils.cpp" + "HeightGainTerminalCorrectionModel.cpp" + "Reporting.cpp" + "ReturnCodes.cpp" + "TerrestrialStatisticalModel.cpp" + "${DRIVER_HEADERS}/CommaSeparatedIterator.h" + "${DRIVER_HEADERS}/Driver.h" + "${DRIVER_HEADERS}/ReturnCodes.h" + "${DRIVER_HEADERS}/Structs.h" +) + +# Add the include directory +target_include_directories(${DRIVER_NAME} PUBLIC "${DRIVER_HEADERS}") + +# Link the library to the executable +target_link_libraries(${DRIVER_NAME} ${LIB_NAME}) + +# Set PropLib compiler option defaults +configure_proplib_target(${DRIVER_NAME}) + +# Add definitions to enable version identification inside the driver +add_compile_definitions( + LIBRARY_VERSION="${PROJECT_VERSION}" + DRIVER_VERSION="${DRIVER_VERSION}" + LIBRARY_NAME="${LIB_NAME}" + DRIVER_NAME="${DRIVER_NAME}" +) + +# Set some target metadata +set_target_properties( + ${DRIVER_NAME} PROPERTIES + OUTPUT_NAME ${DRIVER_NAME}-${DRIVER_VERSION}-${CMAKE_SYSTEM_NAME}- + DEBUG_POSTFIX ${ARCH_SUFFIX} + RELEASE_POSTFIX ${ARCH_SUFFIX} +) \ No newline at end of file diff --git a/app/src/CommaSeparatedIterator.cpp b/app/src/CommaSeparatedIterator.cpp new file mode 100644 index 0000000..022a952 --- /dev/null +++ b/app/src/CommaSeparatedIterator.cpp @@ -0,0 +1,78 @@ +/** @file CommaSeparatedIterator.cpp + * Implementation of class to read comma-delimited input text streams. +*/ +#include "CommaSeparatedIterator.h" + +#include "Driver.h" + +#include // for std::size_t +#include // for std::istream +#include // for std::runtime_error +#include // for std::getline, std::string + +CommaSeparatedIterator::CommaSeparatedIterator(std::istream &stream): + stream_(stream) { + ++(*this); // Move to the first line +} + +/*********************************************************************** + * Pre-increment operator. + * + * Advances the iterator to the next line and splits it into two substrings. + * If the end of the stream is reached, both substrings will be empty. Both + * parsed substrings are converted to lowercase. + * + * @return A reference to the updated iterator. + **********************************************************************/ +CommaSeparatedIterator &CommaSeparatedIterator::operator++() { + if (std::getline(stream_, line_)) { + // Skip line if empty + if (line_.empty()) { + return ++(*this); + } + + // Parse line by comma delimiter + std::size_t pos = line_.find(','); + if (pos != std::string::npos) { + first_ = line_.substr(0, pos); + second_ = line_.substr(pos + 1); + } else { + first_ = line_; + second_ = ""; + } + + // Convert both substrings to lowercase + StringToLower(first_); + StringToLower(second_); + } else { + if (stream_.bad()) { + throw std::runtime_error("Error reading stream."); + } + // End of stream reached + first_ = second_ = ""; + } + + return *this; +} + +/*********************************************************************** + * Dereference operator. + * + * Returns the current pair of substrings (first and second). + * + * @return A pair containing the two substrings from the current line. + **********************************************************************/ +CommaSeparatedIterator::value_type CommaSeparatedIterator::operator*() const { + return {first_, second_}; +} + +/*********************************************************************** + * Conversion to boolean. + * + * Checks if the iterator is still valid (not at the end of the input). + * + * @return True if there are still lines to read, otherwise false. + **********************************************************************/ +CommaSeparatedIterator::operator bool() const { + return stream_.good() || !first_.empty() || !second_.empty(); +} \ No newline at end of file diff --git a/app/src/Driver.cpp b/app/src/Driver.cpp new file mode 100644 index 0000000..d371e68 --- /dev/null +++ b/app/src/Driver.cpp @@ -0,0 +1,251 @@ +/** @file Driver.cpp + * Implements the main function of the executable, and other high-level functions + */ +#include "Driver.h" + +#include // for std::find +#include // for std::ifstream, std::ofstream +#include // for std::setw +#include // for std::left +#include // for std::cerr +#include // for std::endl +#include // for std::string +#include // for std::vector + +/******************************************************************************* + * Main function of the driver executable + * + * @param[in] argc Number of arguments entered on the command line + * @param[in] argv Array containing the provided command-line arguments + * @return Return code + ******************************************************************************/ +int main(int argc, char **argv) { + int rtn; + DrvrParams params; + + // Parse command line arguments + rtn = ParseArguments(argc, argv, params); + if (rtn == DRVR__RETURN_SUCCESS) { + return SUCCESS; + } else if (rtn != DRVR__SUCCESS) { + Help(); + return rtn; + } + + // Ensure required options were provided + rtn = ValidateInputs(params); + if (rtn != DRVR__SUCCESS) { + Help(); + return rtn; + } + + // Initialize model inputs/outputs + HGTCMParams hgtcm_params; + TSMParams tsm_params; + ASMParams asm_params; + std::vector loss__db; // Use for any model + + switch (params.model) { + case P2108Model::HGTCM: + rtn = ParseHGTCMInputFile(params.in_file, hgtcm_params); + if (rtn != DRVR__SUCCESS) { + return rtn; + } + rtn = CallHeightGainTerminalCorrectionModel(hgtcm_params, loss__db); + break; + case P2108Model::TSM: + rtn = ParseTSMInputFile(params.in_file, tsm_params); + if (rtn != DRVR__SUCCESS) { + return rtn; + } + rtn = CallTerrestrialStatisticalModel(tsm_params, loss__db); + break; + case P2108Model::ASM: + rtn = ParseASMInputFile(params.in_file, asm_params); + if (rtn != DRVR__SUCCESS) { + return rtn; + } + rtn = CallAeronauticalStatisticalModel(asm_params, loss__db); + break; + default: + rtn = DRVRERR__VALIDATION_MODEL; + } + + // Return driver error code if one was returned + if (rtn > DRVR__RETURN_SUCCESS) { + std::cerr << GetDrvrReturnStatusMsg(rtn) << std::endl; + return rtn; + } + + // Open output file for writing + std::ofstream fp(params.out_file); + if (!fp) { + std::cerr << "Error opening output file. Exiting." << std::endl; + return DRVRERR__OPENING_OUTPUT_FILE; + } + + // Print generator information to file + fp << std::left << std::setw(25) << "Model" << LIBRARY_NAME; + fp PRINT "Model Variant"; + switch (params.model) { + case P2108Model::HGTCM: + fp << "Height Gain Terminal Correction Model"; + break; + case P2108Model::TSM: + fp << "Terrestrial Statistical Model"; + break; + case P2108Model::ASM: + fp << "Aeronautical Statistical Model"; + break; + // Validation above ensures one of the above cases evaluates + default: + break; + } + fp PRINT "Library Version" << "v" << LIBRARY_VERSION; + fp PRINT "Driver Version" << "v" << DRIVER_VERSION; + fp PRINT "Date Generated" << GetDatetimeString(); + fp PRINT "Input Arguments"; + for (int i = 1; i < argc; i++) { + fp << argv[i] << " "; + } + fp << std::endl << std::endl; + + // Print inputs to file + fp << "Inputs"; + switch (params.model) { + case P2108Model::HGTCM: + WriteHGTCMInputs(fp, hgtcm_params); + break; + case P2108Model::TSM: + WriteTSMInputs(fp, tsm_params); + break; + case P2108Model::ASM: + WriteASMInputs(fp, asm_params); + break; + // Validation above ensures one of these cases evaluates + default: + break; + } + + // Print results to file + fp << std::endl << std::endl << "Results"; + fp PRINT "Return Code" SETW13 rtn; + PrintLabel(fp, GetReturnStatus(rtn)); + if (rtn == SUCCESS) { + fp PRINT "Clutter loss" SETW13 std::fixed << std::setprecision(1) + << loss__db.front() << "(dB)"; + } + fp.close(); + return SUCCESS; +} + +/******************************************************************************* + * Parse the command line arguments + * + * @param[in] argc Number of arguments + * @param[in] argv Command line arguments + * @param[out] params Structure with user input params + * @return Return code + ******************************************************************************/ +DrvrReturnCode ParseArguments(int argc, char **argv, DrvrParams ¶ms) { + const std::vector validArgs + = {"-i", "-o", "-model", "-h", "--help", "-v", "--version"}; + + for (int i = 1; i < argc; i++) { + // Parse arg to lowercase string + std::string arg(argv[i]); + StringToLower(arg); + + // Check if provided flag is valid + if (std::find(validArgs.begin(), validArgs.end(), arg) + == validArgs.end()) { + // Invalid argument provided + std::cerr << "Unknown option: " << argv[i] << std::endl; + return DRVRERR__INVALID_OPTION; + } + + // Handle simple flags which don't have associated values (e.g., "-v", "-DBG") + if (arg == "-v" || arg == "--version") { + Version(); + return DRVR__RETURN_SUCCESS; + } else if (arg == "-h" || arg == "--help") { + Help(); + return DRVR__RETURN_SUCCESS; + } + + // Check if end of arguments reached or next argument is another flag + if (i + 1 >= argc || argv[i + 1][0] == '-') { + std::cerr << "Error: no value given for " << arg << std::endl; + return DRVRERR__MISSING_OPTION; + } + + // Handle inputs which provide values (e.g. "-i in.txt"). + if (arg == "-i") { + params.in_file = argv[i + 1]; + i++; + } else if (arg == "-o") { + params.out_file = argv[i + 1]; + i++; + } else if (arg == "-model") { + std::string argval(argv[i + 1]); + StringToLower(argval); + if (argval == "asm") { + params.model = P2108Model::ASM; + } else if (argval == "hgtcm") { + params.model = P2108Model::HGTCM; + } else if (argval == "tsm") { + params.model = P2108Model::TSM; + } + i++; + } + } + + return DRVR__SUCCESS; +} + +/******************************************************************************* + * Print help instructions to the terminal + * + * @param[in] os Output stream for writing; defaults to `std::cout` + ******************************************************************************/ +void Help(std::ostream &os) { + os << std::endl << "Usage: .\\ [Options]" << std::endl; + os << "Options (not case sensitive)" << std::endl; + os << "\t-i :: Input file name" << std::endl; + os << "\t-o :: Output file name" << std::endl; + os << "\t-model :: Model to run [HGTCM, TSM, ASM]" << std::endl; + os << std::endl << "Examples:" << std::endl; + os << "\t[WINDOWS] " << DRIVER_NAME + << ".exe -i inputs.txt -model ASM -o results.txt" << std::endl; + os << "\t[LINUX] .\\" << DRIVER_NAME + << " -i in.txt -model ASM -o results.txt" << std::endl; + os << "Other Options (which don't run the model)" << std::endl; + os << "\t-h :: Display this help message" << std::endl; + os << "\t-v :: Display program version information" << std::endl; + os << std::endl; +}; + +/******************************************************************************* + * Validate that required inputs are present for the mode specified by the user. + * + * This function DOES NOT check the validity of the parameter values, only that + * required parameters have been specified by the user + * + * @param[in] params Structure with user input parameters + * @return Return code + ******************************************************************************/ +DrvrReturnCode ValidateInputs(const DrvrParams ¶ms) { + DrvrParams not_set; + DrvrReturnCode rtn = DRVR__SUCCESS; + if (params.in_file == not_set.in_file) + rtn = DRVRERR__VALIDATION_IN_FILE; + if (params.out_file == not_set.out_file) + rtn = DRVRERR__VALIDATION_OUT_FILE; + if (params.model == not_set.model) + rtn = DRVRERR__VALIDATION_MODEL; + + if (rtn != DRVR__SUCCESS) + std::cerr << GetDrvrReturnStatusMsg(rtn) << std::endl; + + return rtn; +} diff --git a/app/src/DriverUtils.cpp b/app/src/DriverUtils.cpp new file mode 100644 index 0000000..fe9f69b --- /dev/null +++ b/app/src/DriverUtils.cpp @@ -0,0 +1,151 @@ +/** @file DriverUtils.cpp + * Implements various model-agnostic utility functions for the driver + */ +#include "Driver.h" + +#ifdef _WIN32 + // Ensure localtime_s is available on Windows + #ifndef __STDC_LIB_EXT1__ + #define __STDC_LIB_EXT1__ + #endif + #ifndef __STDC_WANT_LIB_EXT1__ + #define __STDC_WANT_LIB_EXT1__ 1 + #endif +#endif + +#include // for std::transform +#include // for std::tolower +#include // for std::size_t +#include // for localtime_{s,r}, std::{time, time_t, tm, strftime} +#include // for std::setfill, std::setw +#include // for std::cerr, std::endl +#include // for std::ostream +#include // for std::stod, std::stoi, std::string + +/****************************************************************************** + * Get a string containing the current date and time information. + * + * @return A localized standard date and time string (locale dependent) + ******************************************************************************/ +std::string GetDatetimeString() { + std::time_t now = std::time(nullptr); + struct std::tm localTime; + +#ifdef _WIN32 + localtime_s(&localTime, &now); +#else + if (localtime_r(&now, &localTime) == nullptr) { + return "Date and time unknown"; + } +#endif + char mbstr[100]; + if (std::strftime(mbstr, sizeof(mbstr), "%a %b %d %H:%M:%S %Y", &localTime) + == 0) { + return "Could not format datetime string"; + } + return std::string(mbstr); +} + +/******************************************************************************* + * Parse a boolean value read from the input parameter file. + * + * Supports either "true" or "false" (case-insensitive) or "0" or "1" + * + * @param[in] str Input file value as string + * @param[out] value Input file value converted to bool + * @return Return code + ******************************************************************************/ +DrvrReturnCode ParseBoolean(const std::string &str, bool &value) { + try { + std::string str_lower = str; + StringToLower(str_lower); + if (str_lower == "0" || str_lower == "false") { + value = false; + } else if (str_lower == "1" || str_lower == "true") { + value = true; + } else { + return DRVRERR__PARSE; + } + } catch (...) { + return DRVRERR__PARSE; + } + return DRVR__SUCCESS; +} + +/******************************************************************************* + * Parse a double value read from the input parameter file + * + * @param[in] str Input file value as string + * @param[out] value Input file value converted to double + * @return Return code + ******************************************************************************/ +DrvrReturnCode ParseDouble(const std::string &str, double &value) { + try { + value = std::stod(str); + } catch (...) { + // error parsing the input string value + return DRVRERR__PARSE; + } + + return DRVR__SUCCESS; +} + +/******************************************************************************* + * Parse an integer value read from the input parameter file + * + * @param[in] str Input file value as string + * @param[out] value Input file value converted to int + * @return Return code + ******************************************************************************/ +DrvrReturnCode ParseInteger(const std::string &str, int &value) { + try { + std::size_t pos; + value = std::stoi(str, &pos, 10); + + // Verify the entire string was parsed + if (pos != str.size()) { + return DRVRERR__PARSE; + } + } catch (...) { + // error parsing the input string value + return DRVRERR__PARSE; + }; + + return DRVR__SUCCESS; +} + +/******************************************************************************* + * Helper function to standardize printing of text labels to file + * + * @param[in] os Output stream for writing + * @param[in] lbl Text message + ******************************************************************************/ +void PrintLabel(std::ostream &os, const std::string &lbl) { + os << "[" << lbl << "]"; +} + + +/****************************************************************************** + * Convert a string to lowercase. + * + * @param[in, out] str The string to convert + ******************************************************************************/ +void StringToLower(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), [](const char c) { + return static_cast(std::tolower(c)); + }); +} + +/******************************************************************************* + * Print version information to the specified output stream + * + * @param[in] os Output stream for writing; defaults to `std::cout` + ******************************************************************************/ +void Version(std::ostream &os) { + os << std::setfill('*') << std::setw(55) << "" << std::endl; + os << "Institute for Telecommunication Sciences - Boulder, CO" << std::endl; + os << "\tDriver Version: " << DRIVER_VERSION << std::endl; + os << "\t" << LIBRARY_NAME << " Version: " << LIBRARY_VERSION << std::endl; + os << "Time: " << GetDatetimeString() << std::endl; + os << std::setfill('*') << std::setw(55) << "" << std::endl; +} diff --git a/app/src/HeightGainTerminalCorrectionModel.cpp b/app/src/HeightGainTerminalCorrectionModel.cpp new file mode 100644 index 0000000..1583e55 --- /dev/null +++ b/app/src/HeightGainTerminalCorrectionModel.cpp @@ -0,0 +1,131 @@ +/** @file HeightGainTerminalCorrectionModel.cpp + * Implements functions for running the Height Gain Terminal Correction Model. + */ +#include "Driver.h" + +#include // for std::ifstream, std::ofstream +#include // for std::cerr +#include // for std::istream +#include // for std::endl +#include // for std::string +#include // for std::tie +#include // for std::vector + +// Define the input keys +const std::string HGTCMInputKeys::f__ghz = "f__ghz"; +const std::string HGTCMInputKeys::h__meter = "h__meter"; +const std::string HGTCMInputKeys::w_s__meter = "w_s__meter"; +const std::string HGTCMInputKeys::R__meter = "r__meter"; +const std::string HGTCMInputKeys::clutter_type = "clutter_type"; + +/******************************************************************************* + * Top-level control function for Height Gain Terminal Correction Model + * + * @param[in] hgtcm_params Height Gain Terminal Correction Model input struct + * @param[out] A_h__db Additional loss (clutter loss), in dB + * @return Return code + ******************************************************************************/ +ReturnCode CallHeightGainTerminalCorrectionModel( + HGTCMParams &hgtcm_params, std::vector &A_h__db +) { + ReturnCode rtn; + double A_h; + rtn = HeightGainTerminalCorrectionModel( + hgtcm_params.f__ghz, + hgtcm_params.h__meter, + hgtcm_params.w_s__meter, + hgtcm_params.R__meter, + hgtcm_params.clutter_type, + A_h + ); + A_h__db.push_back(A_h); + + return rtn; +} + +/******************************************************************************* + * Parse input stream (file or string stream) to HGTCM parameter struct. + * + * @param[in] stream Input stream containing HGTCM parameters + * @param[out] hgtcm_params HGTCM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseHGTCMInputStream(std::istream &stream, HGTCMParams &hgtcm_params) { + CommaSeparatedIterator it(stream); + DrvrReturnCode rtn = DRVR__SUCCESS; + std::string key, value; + while (it) { + std::tie(key, value) = *it; + if (key.compare(HGTCMInputKeys::f__ghz) == 0) { + rtn = ParseDouble(value, hgtcm_params.f__ghz); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_FREQ; + } else if (key.compare(HGTCMInputKeys::h__meter) == 0) { + rtn = ParseDouble(value, hgtcm_params.h__meter); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_HEIGHT; + } else if (key.compare(HGTCMInputKeys::w_s__meter) == 0) { + rtn = ParseDouble(value, hgtcm_params.w_s__meter); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_STREET_WIDTH; + } else if (key.compare(HGTCMInputKeys::R__meter) == 0) { + rtn = ParseDouble(value, hgtcm_params.R__meter); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_REPR_HEIGHT; + } else if (key.compare(HGTCMInputKeys::clutter_type) == 0) { + int clutter_type_int; + rtn = ParseInteger(value, clutter_type_int); + if (rtn == DRVRERR__PARSE) { + rtn = DRVRERR__PARSE_CLUTTER_TYPE; + } else { + hgtcm_params.clutter_type + = static_cast(clutter_type_int); + } + } else { + std::cerr << "Unknown parameter: " << key << std::endl; + rtn = DRVRERR__PARSE; + } + + if (rtn != DRVR__SUCCESS) { + std::cerr << GetDrvrReturnStatusMsg(rtn) << std::endl; + return rtn; + } + ++it; + } + return rtn; +} + +/******************************************************************************* + * Parse Height Gain Terminal Correction Model input parameter file + * + * @param[in] in_file Path to HGTCM input parameter file + * @param[out] hgtcm_params HGTCM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseHGTCMInputFile(const std::string &in_file, HGTCMParams &hgtcm_params) { + std::ifstream file(in_file); + if (!file) { + std::cerr << "Failed to open file " << in_file << std::endl; + return DRVRERR__OPENING_INPUT_FILE; + } + return ParseHGTCMInputStream(file, hgtcm_params); +} + +/******************************************************************************* + * Write Height Gain Terminal Correction Model inputs to the report file + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] params HGTCM input parameter struct + ******************************************************************************/ +void WriteHGTCMInputs(std::ofstream &fp, const HGTCMParams ¶ms) { + fp PRINT HGTCMInputKeys::f__ghz SETW13 params.f__ghz << "(gigahertz)"; + fp PRINT HGTCMInputKeys::h__meter SETW13 params.h__meter << "(meters)"; + fp PRINT HGTCMInputKeys::w_s__meter SETW13 params.w_s__meter << "(meters)"; + fp PRINT HGTCMInputKeys::R__meter SETW13 params.R__meter << "(meters)"; + fp PRINT HGTCMInputKeys::clutter_type SETW13 static_cast( + params.clutter_type + ); + PrintClutterTypeLabel(fp, params.clutter_type); +} diff --git a/app/src/Reporting.cpp b/app/src/Reporting.cpp new file mode 100644 index 0000000..4d5da02 --- /dev/null +++ b/app/src/Reporting.cpp @@ -0,0 +1,41 @@ +/** @file Reporting.cpp + * Implements utility functions for printing driver results + */ +#include "Driver.h" + +#include // for std::ofstream +#include // for std::string + +/******************************************************************************* + * Print text message corresponding to clutter type enum value + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] clutter_type Height Gain Terminal Correction Model clutter type + ******************************************************************************/ +void PrintClutterTypeLabel(std::ofstream &fp, const ClutterType clutter_type) { + std::string label; + switch (clutter_type) { + case ClutterType::WATER_SEA: + label = "Water/sea clutter type"; + break; + case ClutterType::OPEN_RURAL: + label = "Open/rural clutter type"; + break; + case ClutterType::SUBURBAN: + label = "Suburban clutter type"; + break; + case ClutterType::URBAN: + label = "Urban clutter type"; + break; + case ClutterType::TREES_FOREST: + label = "Trees/forest clutter type"; + break; + case ClutterType::DENSE_URBAN: + label = "Dense urban clutter type"; + break; + default: + label = "Invalid clutter type"; + break; + } + PrintLabel(fp, label); +} \ No newline at end of file diff --git a/app/src/ReturnCodes.cpp b/app/src/ReturnCodes.cpp new file mode 100644 index 0000000..626064e --- /dev/null +++ b/app/src/ReturnCodes.cpp @@ -0,0 +1,60 @@ +/** @file ReturnCodes.cpp + * Maps status message strings to driver return codes. + */ + +#include "ReturnCodes.h" + +#include // for std::string +#include // for std::unordered_map + +/******************************************************************************* + * Get an error message string from a return code. + * + * @param[in] code Driver return code. + * @return A status message corresponding to the input code. + ******************************************************************************/ +std::string GetDrvrReturnStatusMsg(int code) { + static const std::unordered_map messages + = {{DRVR__SUCCESS, "Successful execution"}, + {DRVR__RETURN_SUCCESS, "Internal driver success"}, + {DRVRERR__MISSING_OPTION, "No value provided for given argument"}, + {DRVRERR__INVALID_OPTION, "Unknown option specified"}, + {DRVRERR__OPENING_INPUT_FILE, + "Failed to open the input file for reading"}, + {DRVRERR__OPENING_OUTPUT_FILE, + "Failed to open the output file for writing"}, + {DRVRERR__PARSE, "Failed parsing inputs; unknown parameter"}, + {DRVRERR__PARSE_FREQ, "Failed to parse frequency value"}, + {DRVRERR__PARSE_THETA, "Failed to parse theta value"}, + {DRVRERR__PARSE_PERCENTAGE, "Failed to parse percentage value"}, + {DRVRERR__PARSE_HEIGHT, "Failed to parse antenna height value"}, + {DRVRERR__PARSE_STREET_WIDTH, "Failed to parse street width value"}, + {DRVRERR__PARSE_REPR_HEIGHT, + "Failed to parse representative height value"}, + {DRVRERR__PARSE_CLUTTER_TYPE, "Failed to parse clutter type value"}, + {DRVRERR__PARSE_PATH_DIST, "Failed to parse path distance value"}, + {DRVRERR__VALIDATION_IN_FILE, + "Option -i is required but was not provided"}, + {DRVRERR__VALIDATION_OUT_FILE, + "Option -o is required but was not provided"}, + {DRVRERR__VALIDATION_MODEL, + "Option -model is required but was not provided"}}; + + // Construct status message + std::string msg = DRIVER_NAME; + msg += " v"; + msg += DRIVER_VERSION; + if (code == DRVR__SUCCESS) { + msg += " Status: "; + } else { + msg += " Error: "; + } + + auto it = messages.find(static_cast(code)); + if (it != messages.end()) { + msg += it->second; + } else { + msg += "Undefined return code"; + } + return msg; +} diff --git a/app/src/TerrestrialStatisticalModel.cpp b/app/src/TerrestrialStatisticalModel.cpp new file mode 100644 index 0000000..1489910 --- /dev/null +++ b/app/src/TerrestrialStatisticalModel.cpp @@ -0,0 +1,106 @@ +/** @file TerrestrialStatisticalModel.cpp + * Implements top-level functions for running the Terrestrial Statistical Model. + */ +#include "Driver.h" + +#include // for std::ifstream, std::ofstream +#include // for std::cerr +#include // for std::istream +#include // for std::endl +#include // for std::string +#include // for std::tie +#include // for std::vector + +// Define the input keys +const std::string TSMInputKeys::f__ghz = "f__ghz"; +const std::string TSMInputKeys::d__km = "d__km"; +const std::string TSMInputKeys::p = "p"; + +/******************************************************************************* + * Top-level control function for Terrestrial Statistical Model operation + * + * @param[in] tsm_params Terrestrial Statistical Model input parameter struct + * @param[out] L_ctt__db Additional loss (clutter loss), in dB + * @return Return code + ******************************************************************************/ +ReturnCode CallTerrestrialStatisticalModel( + TSMParams &tsm_params, std::vector &L_ctt__db +) { + ReturnCode rtn; + double L_ctt; + rtn = TerrestrialStatisticalModel( + tsm_params.f__ghz, tsm_params.d__km, tsm_params.p, L_ctt + ); + L_ctt__db.push_back(L_ctt); + + return rtn; +} + +/******************************************************************************* + * Parse input stream (file or string stream) to TSM parameter struct. + * + * @param[in] stream Input stream containing TSM parameters + * @param[out] tsm_params TSM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseTSMInputStream(std::istream &stream, TSMParams &tsm_params) { + CommaSeparatedIterator it(stream); + DrvrReturnCode rtn = DRVR__SUCCESS; + std::string key, value; + while (it) { + std::tie(key, value) = *it; + if (key.compare(TSMInputKeys::f__ghz) == 0) { + rtn = ParseDouble(value, tsm_params.f__ghz); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_FREQ; + } else if (key.compare(TSMInputKeys::d__km) == 0) { + rtn = ParseDouble(value, tsm_params.d__km); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_PATH_DIST; + } else if (key.compare(TSMInputKeys::p) == 0) { + rtn = ParseDouble(value, tsm_params.p); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_PERCENTAGE; + } else { + std::cerr << "Unknown parameter: " << key << std::endl; + rtn = DRVRERR__PARSE; + } + + if (rtn != DRVR__SUCCESS) { + std::cerr << GetDrvrReturnStatusMsg(rtn) << std::endl; + return rtn; + } + ++it; + } + return rtn; +} + +/******************************************************************************* + * Parse Terrestrial Statistical Model input parameter file + * + * @param[in] in_file Path to TSM input parameter file + * @param[out] tsm_params TSM input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseTSMInputFile(const std::string &in_file, TSMParams &tsm_params) { + std::ifstream file(in_file); + if (!file) { + std::cerr << "Failed to open file " << in_file << std::endl; + return DRVRERR__OPENING_INPUT_FILE; + } + return ParseTSMInputStream(file, tsm_params); +} + +/******************************************************************************* + * Write Terrestrial Statistical Model inputs to the report file + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] params TSM input parameter struct + ******************************************************************************/ +void WriteTSMInputs(std::ofstream &fp, const TSMParams ¶ms) { + fp PRINT TSMInputKeys::f__ghz SETW13 params.f__ghz << "(gigahertz)"; + fp PRINT TSMInputKeys::d__km SETW13 params.d__km << "(kilometers)"; + fp PRINT TSMInputKeys::p SETW13 params.p << "(%)"; +} \ No newline at end of file diff --git a/app/tests/CMakeLists.txt b/app/tests/CMakeLists.txt new file mode 100644 index 0000000..66d93f9 --- /dev/null +++ b/app/tests/CMakeLists.txt @@ -0,0 +1,38 @@ +############################################ +## CONFIGURE COMMAND LINE DRIVER TESTS +############################################ +add_executable( + ${DRIVER_TEST_NAME} + "TempTextFile.cpp" + "TestDriver.cpp" + "TestDriverASM.cpp" + "TestDriverHGTCM.cpp" + "TestDriverTSM.cpp" + "TempTextFile.h" + "TestDriver.h" + "${DRIVER_HEADERS}/Driver.h" +) + +# Add the include directories +target_include_directories( + ${DRIVER_TEST_NAME} PUBLIC + "${DRIVER_HEADERS}" + "${PROJECT_SOURCE_DIR}/app/tests" +) + +# Link the library to the executable +target_link_libraries(${DRIVER_TEST_NAME} ${LIB_NAME}) + +# Set PropLib compiler option defaults +configure_proplib_target(${DRIVER_TEST_NAME}) + +# Make driver executable location available to source +add_compile_definitions(DRIVER_LOCATION="$") + +########################################### +## SET UP AND DISCOVER TESTS +########################################### +include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) +target_link_libraries(${DRIVER_TEST_NAME} ${LIB_NAME} GTest::gtest_main) +include(GoogleTest) +gtest_discover_tests(${DRIVER_TEST_NAME}) diff --git a/app/tests/TempTextFile.cpp b/app/tests/TempTextFile.cpp new file mode 100644 index 0000000..f814e3d --- /dev/null +++ b/app/tests/TempTextFile.cpp @@ -0,0 +1,61 @@ +/** @file TempTextFile.cpp + * Implements a class to create and write to temporary text files + */ +#include "TempTextFile.h" + +#ifdef _WIN32 + // Ensure tmpnam_s is available on Windows + #ifndef __STDC_LIB_EXT1__ + #define __STDC_LIB_EXT1__ + #endif + #ifndef __STDC_WANT_LIB_EXT1__ + #define __STDC_WANT_LIB_EXT1__ 1 + #endif + #include // for L_tmpnam_s, tmpnam_s +#else // macOS and Linux + #include // for mkstemp + #include // for close +#endif + +#include // for std::remove +#include // for std::ofstream +#include // for std::cerr, std::ios::trunc +#include // for std::endl +#include // for std::runtime_error +#include // for std::string + +TempTextFile::TempTextFile(const std::string &content) { +#ifdef _WIN32 + // Generate and store a temporary file name + char tempFileName[L_tmpnam_s]; + if (tmpnam_s(tempFileName, sizeof(tempFileName)) != 0) { + throw std::runtime_error("Failed to create temporary file name."); + } +#else + // Safer implementation for POSIX platforms + char tempFileName[] = "/tmp/proplib-tempfile.XXXXXX"; + int fd = mkstemp(tempFileName); + if (fd == -1) { + throw std::runtime_error("Failed to create temporary file."); + } + close(fd); +#endif + filename = tempFileName; // Store generated filename + std::ofstream tempFile(tempFileName, std::ios::trunc); + if (!tempFile.is_open()) { + std::cerr << "Temp file name is: " << filename << std::endl; + throw std::runtime_error("Failed to open temporary file for writing."); + } + tempFile << content; + tempFile.close(); +} + +TempTextFile::~TempTextFile() { + // Delete the temporary file upon destruction + std::remove(filename.c_str()); +} + +std::string TempTextFile::getFileName() const { + // Return the name of the temporary file. + return filename; +} \ No newline at end of file diff --git a/app/tests/TempTextFile.h b/app/tests/TempTextFile.h new file mode 100644 index 0000000..f70867d --- /dev/null +++ b/app/tests/TempTextFile.h @@ -0,0 +1,37 @@ +/** @file TempTextFile.h + * Header for a class which manages temporary text files. + */ +#pragma once + +#include // for std::string + +/******************************************************************************* + * A class to manage a temporary text file. + * + * The TempTextFile class creates a temporary text file from a string that is + * automatically deleted when the object is destroyed. + ******************************************************************************/ +class TempTextFile { + public: + /*********************************************************************** + * Constructor that creates a temporary file and writes content to it. + * + * @param[in] content String content to write to the file. + * @throws std::runtime_error On failure to create or write to file. + **********************************************************************/ + TempTextFile(const std::string &content); + + /*********************************************************************** + * Destructor that closes (and deletes) the temporary file. + **********************************************************************/ + ~TempTextFile(); + + /*********************************************************************** + * Retrieve the name of the temporary file + * + * @return A string containing the name of the temporary file. + **********************************************************************/ + std::string getFileName() const; + private: + std::string filename; /**< Name of the temporary file */ +}; diff --git a/app/tests/TestDriver.cpp b/app/tests/TestDriver.cpp new file mode 100644 index 0000000..4cc79ad --- /dev/null +++ b/app/tests/TestDriver.cpp @@ -0,0 +1,68 @@ +/** @file TestDriver.cpp + * General tests for the driver executable + */ +#include "TestDriver.h" + +#include // for std::string + +TEST_F(DriverTest, MissingOptionError1) { + // Test case: missing option between two provided flags + std::string cmd = executable + " -i -o out.txt"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__MISSING_OPTION, rtn); +} + +TEST_F(DriverTest, MissingOptionError2) { + // Test case: missing option at the end of command + std::string cmd = executable + " -i"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__MISSING_OPTION, rtn); +} + +TEST_F(DriverTest, InvalidOptionError) { + std::string cmd = executable + " -X"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__INVALID_OPTION, rtn); +} + +TEST_F(DriverTest, OpeningInputFileError) { + params.in_file = "/invalid/path/input.xyz"; + params.model = P2108Model::ASM; + int rtn = RunDriver(params); + EXPECT_EQ(DRVRERR__OPENING_INPUT_FILE, rtn); +} + +TEST_F(DriverTest, OpeningOutputFileError) { + // Provide valid inputs but invalid output file path + std::string inputs = "f__ghz,10\ntheta__deg,10.5\np,45"; + params.model = P2108Model::ASM; + params.out_file = "/invalid/path/output.xyz"; + int rtn = RunDriverWithInputFile(inputs, params); + EXPECT_EQ(DRVRERR__OPENING_OUTPUT_FILE, rtn); +} + +TEST_F(DriverTest, ValidationInFileError) { + std::string cmd = executable + " -o out.txt -model ASM"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__VALIDATION_IN_FILE, rtn); +} + +TEST_F(DriverTest, ValidationOutFileError) { + // Input file does not need to exist here, just has to be specified + std::string cmd = executable + " -i in.txt -model ASM"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__VALIDATION_OUT_FILE, rtn); +} + +TEST_F(DriverTest, ValidationModelError) { + // Input file does not need to exist here, just has to be specified + std::string cmd = executable + " -i in.txt -o out.txt"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(rtn, DRVRERR__VALIDATION_MODEL); +} diff --git a/app/tests/TestDriver.h b/app/tests/TestDriver.h new file mode 100644 index 0000000..2d3fe64 --- /dev/null +++ b/app/tests/TestDriver.h @@ -0,0 +1,185 @@ +/** @file TestDriver.h + * Primary header and test fixture for command line driver tests. + */ +#pragma once + +// clang-format off +// GoogleTest must be included first +#include // GoogleTest +// clang-format on + +#include "Driver.h" +#include "TempTextFile.h" + +#include // for std::remove, std::perror +#include // for std::system +#include // for std::cout +#include // for std::endl, std::flush +#include // for std::string + +#ifndef _WIN32 + #include // for WEXITSTATUS +#endif + +/******************************************************************************* + * @class DriverTest + * Test fixture for running the driver executable tests. + * + * This class extends the Google Test framework's Test class and provides + * utilities to set up, execute, and manage the output of the driver executable. + ******************************************************************************/ +class DriverTest: public ::testing::Test { + protected: + /*********************************************************************** + * Sets up the test environment. + **********************************************************************/ + void SetUp() override { + // Set the default driver params + params.out_file = "tmp_out.txt"; + + // Get the name of the executable to test + executable = std::string(DRIVER_LOCATION); + } + + /*********************************************************************** + * Suppresses the output of the command. + * + * Appends redirection to suppress standard output and error based on + * the platform. + * + * @param[in, out] cmd The command string to modify + **********************************************************************/ + void SuppressOutputs(std::string &cmd) { +#ifdef _WIN32 + cmd += " > nul"; +#else + cmd += " > /dev/null"; +#endif + cmd += " 2>&1"; + } + + /*********************************************************************** + * Builds the command string to run the driver. + * + * Constructs a command string using the provided driver parameters + * struct. Optionally, the command can be written such that stdout and + * are suppressed. + * + * @param[in] dParams The driver parameters + * @param[in] suppressOutputs Whether to suppress outputs (default: true) + * @return The constructed command string + **********************************************************************/ + std::string BuildCommand( + const DrvrParams &dParams, const bool suppressOutputs = true + ) { + // Construct command from parameters + std::string command = executable; + command += " -i " + dParams.in_file; + switch (dParams.model) { + case P2108Model::HGTCM: + command += " -model HGTCM"; + break; + case P2108Model::TSM: + command += " -model TSM"; + break; + case P2108Model::ASM: + command += " -model ASM"; + break; + default: // avoid compile-time warning. command will be invalid if this runs. + break; + } + command += " -o " + dParams.out_file; + + // Suppress text output of the driver, to avoid cluttering + // test outputs. + if (suppressOutputs) { + SuppressOutputs(command); + } + // Return the full command string + return command; + } + + /*********************************************************************** + * Runs the provided command (cross-platform) + * + * Note that on POSIX platforms the exit code of the command should be + * between 0 and 255. Exit codes outside this range will be shifted into + * this range and cannot be unambiguously compared to expectations. + * + * @param[in] cmd The command to run + * @return The exit code of the command. + **********************************************************************/ + int RunCommand(const std::string &cmd) { + std::cout << std::flush; + int rtn = std::system(cmd.c_str()); +#ifndef _WIN32 + rtn = WEXITSTATUS(rtn); // Get child process exit code on POSIX +#endif + return rtn; + } + + /*********************************************************************** + * Runs the driver executable. + * + * @param[in] dParams Parameters to parse as command line arguments + * @return Return code from the driver execution + **********************************************************************/ + int RunDriver(const DrvrParams &dParams) { + std::string cmd = BuildCommand(dParams); + return RunCommand(cmd); + } + + /*********************************************************************** + * Runs the driver using the specified input file contents. + * + * This method creates a temporary text file containing the contents + * of `inFileContents` and then runs the driver using the temporary + * file as the input file. The rest of the required driver parameters + * are provided in the `params` input; the `params.in_file` value is + * ignored and can be unset. If an output file was produced by the + * driver, it is deleted before this method returns. + * + * @param[in] inFileContents The contents to write to the input file + * @param[in] dParams A populated driver parameters struct (see above) + * @return Return code from the driver execution + **********************************************************************/ + int RunDriverWithInputFile( + const std::string &inFileContents, const DrvrParams &dParams + ) { + DrvrParams updated_params = dParams; + TempTextFile tempFile(inFileContents); + updated_params.in_file = tempFile.getFileName(); + int rtn = RunDriver(updated_params); + // Cleanup: delete output file if it was created + DeleteOutputFile(updated_params.out_file); + return rtn; + } + + /*********************************************************************** + * Deletes the specified output file if it exists. + * + * Checks if the file exists and attempts to delete it. Reports any + * errors encountered during deletion. + * + * @param[in] fileName The name of the file to delete. + **********************************************************************/ + void DeleteOutputFile(const std::string &fileName) { + bool fileExists = false; +#ifdef _WIN32 + fileExists = _access(fileName.c_str(), 0) == 0; +#else + fileExists = access(fileName.c_str(), F_OK) == 0; +#endif + if (fileExists) { + if (std::remove(fileName.c_str()) != 0) { + std::perror("Error deleting output file"); + } + } + } + + /** Platform-dependent string to call the executable */ + std::string executable; + + /** Driver parameters struct which may be used by tests */ + DrvrParams params; +}; diff --git a/app/tests/TestDriverASM.cpp b/app/tests/TestDriverASM.cpp new file mode 100644 index 0000000..e2bcb8e --- /dev/null +++ b/app/tests/TestDriverASM.cpp @@ -0,0 +1,48 @@ +#include "TestDriver.h" + +#include // for std::string + +/******************************************************************************* + * Driver test fixture for the Aeronautical Statistical Model + ******************************************************************************/ +class ASMDriverTest: public DriverTest { + protected: + void SetUp() override { + DriverTest::SetUp(); + asm_params.model = P2108Model::ASM; + asm_params.out_file = params.out_file; + } + void TestASM(const std::string &inputs, const int expected_rtn) { + int asm_rtn; + asm_rtn = RunDriverWithInputFile(inputs, asm_params); + EXPECT_EQ(asm_rtn, expected_rtn); + } + + std::string ASMInputs; /**< String to hold input file contents */ + DrvrParams asm_params; /**< Default command line arguments */ +}; + +TEST_F(ASMDriverTest, TestSuccess) { + ASMInputs = "f__ghz,10\ntheta__deg,10.5\np,45"; + TestASM(ASMInputs, SUCCESS); +} + +TEST_F(ASMDriverTest, TestParseError) { + ASMInputs = "unknown_param,0.0"; + TestASM(ASMInputs, DRVRERR__PARSE); +} + +TEST_F(ASMDriverTest, TestParseFrequencyError) { + ASMInputs = "f__ghz,invalid"; + TestASM(ASMInputs, DRVRERR__PARSE_FREQ); +} + +TEST_F(ASMDriverTest, TestParseThetaError) { + ASMInputs = "theta__deg,invalid"; + TestASM(ASMInputs, DRVRERR__PARSE_THETA); +} + +TEST_F(ASMDriverTest, TestParsePercentageError) { + ASMInputs = "p,invalid"; + TestASM(ASMInputs, DRVRERR__PARSE_PERCENTAGE); +} \ No newline at end of file diff --git a/app/tests/TestDriverHGTCM.cpp b/app/tests/TestDriverHGTCM.cpp new file mode 100644 index 0000000..3fd5d92 --- /dev/null +++ b/app/tests/TestDriverHGTCM.cpp @@ -0,0 +1,59 @@ +#include "TestDriver.h" + +#include // for std::string + +/******************************************************************************* + * Driver test fixture for the Height Gain Terminal Correction Model + ******************************************************************************/ +class HGTCMDriverTest: public DriverTest { + protected: + void SetUp() override { + DriverTest::SetUp(); + hgtcm_params.model = P2108Model::HGTCM; + hgtcm_params.out_file = params.out_file; + } + void TestHGTCM(const std::string &inputs, const int expected_rtn) { + int hgtcm_rtn; + hgtcm_rtn = RunDriverWithInputFile(inputs, hgtcm_params); + EXPECT_EQ(hgtcm_rtn, expected_rtn); + } + + std::string HGTCMInputs; /**< String to hold input file contents */ + DrvrParams hgtcm_params; /**< Default command line arguments */ +}; + +TEST_F(HGTCMDriverTest, TestSuccess) { + HGTCMInputs + = "f__ghz,1.5\nh__meter,2\nw_s__meter,27\nR__meter,15\nclutter_type,4"; + TestHGTCM(HGTCMInputs, SUCCESS); +} + +TEST_F(HGTCMDriverTest, TestParseError) { + HGTCMInputs = "unknown_param,0.0"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE); +} + +TEST_F(HGTCMDriverTest, TestParseFrequencyError) { + HGTCMInputs = "f__ghz,invalid"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE_FREQ); +} + +TEST_F(HGTCMDriverTest, TestParseHeightError) { + HGTCMInputs = "h__meter,invalid"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE_HEIGHT); +} + +TEST_F(HGTCMDriverTest, TestParseStreetWidthError) { + HGTCMInputs = "w_s__meter,invalid"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE_STREET_WIDTH); +} + +TEST_F(HGTCMDriverTest, TestParseReprHeightError) { + HGTCMInputs = "R__meter,invalid"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE_REPR_HEIGHT); +} + +TEST_F(HGTCMDriverTest, TestParseClutterTypeError) { + HGTCMInputs = "clutter_type,invalid"; + TestHGTCM(HGTCMInputs, DRVRERR__PARSE_CLUTTER_TYPE); +} \ No newline at end of file diff --git a/app/tests/TestDriverTSM.cpp b/app/tests/TestDriverTSM.cpp new file mode 100644 index 0000000..4872360 --- /dev/null +++ b/app/tests/TestDriverTSM.cpp @@ -0,0 +1,48 @@ +#include "TestDriver.h" + +#include // for std::string + +/******************************************************************************* + * Driver test fixture for the Terrestrial Statistical Model + ******************************************************************************/ +class TSMDriverTest: public DriverTest { + protected: + void SetUp() override { + DriverTest::SetUp(); + tsm_params.model = P2108Model::TSM; + tsm_params.out_file = params.out_file; + } + void TestTSM(const std::string &inputs, const int expected_rtn) { + int tsm_rtn; + tsm_rtn = RunDriverWithInputFile(inputs, tsm_params); + EXPECT_EQ(tsm_rtn, expected_rtn); + } + + std::string TSMInputs; /**< String to hold input file contents */ + DrvrParams tsm_params; /**< Default command line arguments */ +}; + +TEST_F(TSMDriverTest, TestSuccess) { + TSMInputs = "f__ghz,26.6\nd__km,15.8\np,45"; + TestTSM(TSMInputs, SUCCESS); +} + +TEST_F(TSMDriverTest, TestParseError) { + TSMInputs = "unknown_param,0.0"; + TestTSM(TSMInputs, DRVRERR__PARSE); +} + +TEST_F(TSMDriverTest, TestParseFrequencyError) { + TSMInputs = "f__ghz,invalid"; + TestTSM(TSMInputs, DRVRERR__PARSE_FREQ); +} + +TEST_F(TSMDriverTest, TestParsePercentageError) { + TSMInputs = "p,invalid"; + TestTSM(TSMInputs, DRVRERR__PARSE_PERCENTAGE); +} + +TEST_F(TSMDriverTest, TestParsePathDistanceError) { + TSMInputs = "d__km,invalid"; + TestTSM(TSMInputs, DRVRERR__PARSE_PATH_DIST); +} \ No newline at end of file diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000..aeb6125 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,68 @@ +########################################### +## FIND DOXYGEN AND DOXYGEN-AWESOME-CSS +########################################### +# Doxygen >=1.11.0 is required to properly render the header +set(MINIMUM_DOXYGEN_VERSION "1.11") +find_package(Doxygen ${MINIMUM_DOXYGEN_VERSION} REQUIRED doxygen) +# find_package will cause an error and exit if package is not found + +# Ensure doxygen-awesome-css submodule has been initialized +set(EXTRA_STYLESHEET "${PROJECT_SOURCE_DIR}/extern/doxygen-awesome-css/doxygen-awesome.css") +if (NOT EXISTS ${EXTRA_STYLESHEET}) + message(FATAL_ERROR + "External Doxygen stylesheet is missing! " + "Run `git submodule init extern/doxygen-awesome-css`, then " + "`git submodule update` and try again." + ) +endif () + +########################################### +## CONFIGURE DOXYGEN +########################################### +set(DOCS_DIR "${PROJECT_SOURCE_DIR}/docs") +set(DOXYGEN_ALIASES "libname=${LIB_NAME}") # Used to populate library name on main page +set(DOXYGEN_PROJECT_NAME "${CMAKE_PROJECT_NAME}") +set(DOXYGEN_BUILTIN_STL_SUPPORT "YES") +set(DOXYGEN_DISABLE_INDEX "NO") +set(DOXYGEN_EXCLUDE "${PROJECT_SOURCE_DIR}/tests/*") +set(DOXYGEN_FULL_SIDEBAR "NO") +set(DOXYGEN_GENERATE_LATEX "NO") +set(DOXYGEN_GENERATE_TREEVIEW "NO") +set(DOXYGEN_HTML_COLORSTYLE "LIGHT") # Required for doxygen-awesome-css +set(DOXYGEN_HTML_EXTRA_FILES + "${DOCS_DIR}/images/ITSlogoOnly400.png" + "${DOCS_DIR}/images/favicon-16x16.png" + "${DOCS_DIR}/images/favicon-32x32.png" + "${DOCS_DIR}/images/apple-touch-icon.png") +set(DOXYGEN_HTML_EXTRA_STYLESHEET "${EXTRA_STYLESHEET}" "${DOCS_DIR}/doxy_custom.css") +set(DOXYGEN_HTML_FOOTER "${DOCS_DIR}/doxy_footer.html") +set(DOXYGEN_HTML_HEADER "${DOCS_DIR}/doxy_header.html") +set(DOXYGEN_JAVADOC_AUTOBRIEF "YES") +set(DOXYGEN_JAVADOC_BANNER "YES") +set(DOXYGEN_OUTPUT_DIRECTORY "${DOCS_DIR}") +set(DOXYGEN_PREDEFINED "DOXYGEN_SHOULD_SKIP") +set(DOXYGEN_PROJECT_BRIEF "Part of the NTIA/ITS Propagation Library") +set(DOXYGEN_PROJECT_LOGO "${DOCS_DIR}/images/ntia-logo-400px.png") +set(DOXYGEN_REPEAT_BRIEF "YES") +set(DOXYGEN_SHOW_INCLUDE_FILES "NO") +set(DOXYGEN_USE_MATHJAX "YES") +set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${DOCS_DIR}/doxy_mainpage.md") +set(DOXYGEN_WARN_AS_ERROR "YES") +set(DOXYGEN_WARN_IF_UNDOC_ENUM_VAL "YES") +set(DOXYGEN_WARN_NO_PARAMDOC "YES") + +# Doxygen docs are a developer, not user, reference. +# Therefore, document private and internal code +set(DOXYGEN_EXTRACT_PRIVATE "YES") +set(DOXYGEN_INTERNAL_DOCS "YES") + +doxygen_add_docs( + "${LIB_NAME}Docs" + "${PROJECT_SOURCE_DIR}/app/src" + "${PROJECT_SOURCE_DIR}/app/include" + "${PROJECT_SOURCE_DIR}/src" + "${PROJECT_SOURCE_DIR}/include" + "${DOCS_DIR}/doxy_mainpage.md" + ALL + COMMENT "Generate HTML documentation with Doxygen" +) \ No newline at end of file diff --git a/docs/doxy_custom.css b/docs/doxy_custom.css new file mode 100644 index 0000000..d2ccfca --- /dev/null +++ b/docs/doxy_custom.css @@ -0,0 +1,44 @@ +.footer-content { + display: flex; + flex-wrap: wrap; /* Allow items to wrap to the next line */ + justify-content: space-between; /* Center the flex items horizontally */ + max-width: 1040px; /* Optional: Set a max-width for the container */ + margin: 0 auto; /* Auto margin horizontally centers the container */ + padding: 0px; /* Optional: Add padding around the container */ + box-sizing: border-box; /* Include padding and border in width calculation */ +} + +.footer-column-left, +.footer-column-right { + width: calc( + 50% - 10px + ); /* Each column takes up 50% of the container width minus padding */ + box-sizing: border-box; /* Include padding and border in width calculation */ + padding: 20px; /* Example padding for columns */ + + h2, + p { + margin-bottom: 0; + margin-top: 0; + } +} + +.footer-column-right { + text-align: right; /* Align text to the right within elements in the right column */ + h2 { + text-align: right; + margin-right: 0; + } +} + +/* Media query for mobile devices */ +@media (max-width: 768px) { + .footer-content { + flex-direction: column; /* Stack items vertically on smaller screens */ + } + .footer-column-left, + .footer-column-right { + width: 100%; /* Each column takes up 100% of the container width (stacked vertically) */ + padding: 20px; /* Reset padding for mobile layout */ + } +} diff --git a/docs/doxy_footer.html b/docs/doxy_footer.html new file mode 100644 index 0000000..6bb41ea --- /dev/null +++ b/docs/doxy_footer.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/docs/doxy_header.html b/docs/doxy_header.html new file mode 100644 index 0000000..ebea68f --- /dev/null +++ b/docs/doxy_header.html @@ -0,0 +1,113 @@ + + + + + + + + +$projectname API Reference: $title +$title + + + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + + \ No newline at end of file diff --git a/docs/doxy_mainpage.md b/docs/doxy_mainpage.md new file mode 100644 index 0000000..9084066 --- /dev/null +++ b/docs/doxy_mainpage.md @@ -0,0 +1,33 @@ +# Main Page + +This website is an information-oriented API reference document for the @libname +C++ library and associated command-line driver, a part of the NTIA/ITS Propagation +Library. This site is primarily useful for developers wishing to contribute to this +library or take it as a dependency. + +**For most users, the best place to start is the** +[**NTIA/ITS Propagation Library Wiki**](https://ntia.github.io/propagation-library-wiki). + +On the wiki, you'll find installation instructions, usage guides, and code examples +for this and other software within the NTIA/ITS Propagation Library. Further, the +wiki includes instructions for using this library from other software languages, +including bindings for Python, MATLAB, and .NET. + +## Site Navigation + +Please use the navigation menu and search functionality to explore this reference +documentation. The "Files" navigation menu includes the following notable options: + +- [File List](files.html) provides a browsable overview of the source code directories. +- [File Members - All](globals.html) lists all documents file members alphabetically. + +Additional pages listed under "File Members" allow for browsing based on member types, +e.g. classes, functions, etc. + +## Generating this Documentation + +This site is generated with [Doxygen](https://www.doxygen.nl/), which is configured +in the source project using [CMake](https://cmake.org/). The documentation is generated +by default when building the project in its release configuration. Additionally, +the documentation can be generated without compiling the source project by using +the `DOCS_ONLY` CMake option. diff --git a/docs/images/ITSlogoOnly400.png b/docs/images/ITSlogoOnly400.png new file mode 100644 index 0000000..45a7ba3 Binary files /dev/null and b/docs/images/ITSlogoOnly400.png differ diff --git a/docs/images/apple-touch-icon.png b/docs/images/apple-touch-icon.png new file mode 100644 index 0000000..d6371ec Binary files /dev/null and b/docs/images/apple-touch-icon.png differ diff --git a/docs/images/favicon-16x16.png b/docs/images/favicon-16x16.png new file mode 100644 index 0000000..cc0687f Binary files /dev/null and b/docs/images/favicon-16x16.png differ diff --git a/docs/images/favicon-32x32.png b/docs/images/favicon-32x32.png new file mode 100644 index 0000000..db8b740 Binary files /dev/null and b/docs/images/favicon-32x32.png differ diff --git a/docs/images/ntia-logo-400px.png b/docs/images/ntia-logo-400px.png new file mode 100644 index 0000000..5e93a96 Binary files /dev/null and b/docs/images/ntia-logo-400px.png differ diff --git a/dotnet/ITS.ITU.PSeries.P2108/ITS.ITU.PSeries.P2108.csproj b/dotnet/ITS.ITU.PSeries.P2108/ITS.ITU.PSeries.P2108.csproj deleted file mode 100644 index 517e36a..0000000 --- a/dotnet/ITS.ITU.PSeries.P2108/ITS.ITU.PSeries.P2108.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Debug - AnyCPU - {6AA0C335-F019-4818-97B6-7B32BBE190E4} - Library - Properties - ITS.ITU.PSeries - ITS.ITU.PSeries.P2108 - v4.8.1 - 512 - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\ITS.ITU.PSeries.P2108.xml - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/ITS.ITU.PSeries.P2108/P2108.cs b/dotnet/ITS.ITU.PSeries.P2108/P2108.cs deleted file mode 100644 index e560cc1..0000000 --- a/dotnet/ITS.ITU.PSeries.P2108/P2108.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITS.ITU.PSeries -{ - /// - /// Recommendation ITU-R P.2108-1 - /// - public static class P2108 - { - private const string P2108_x86_DLL_NAME = "p2108_x86.dll"; - private const string P2108_x64_DLL_NAME = "p2108_x64.dll"; - - /// - /// Clutter types for Height Gain Terminal Correction Model - /// - public enum ClutterType : int - { - /// - /// Water/sea - /// - WaterSea = 1, - - /// - /// Open/rural - /// - OpenRural = 2, - - /// - /// Suburban - /// - Suburban = 3, - - /// - /// Urban - /// - Urban = 4, - - /// - /// Trees/forest - /// - TreesForest = 5, - - /// - /// Dense urban - /// - DenseUrban = 6 - } - - #region 32-Bit P/Invoke Definitions - - [DllImport(P2108_x86_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "AeronauticalStatisticalModel")] - private static extern int AeronauticalStatisticalModel_x86(double f__ghz, double theta__deg, double p, out double L_ces__db); - - [DllImport(P2108_x86_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "HeightGainTerminalCorrectionModel")] - private static extern int HeightGainTerminalCorrectionModel_x86(double f__ghz, double h__meter, double w_s__meter, double R__meter, int clutter_type, out double A_h__db); - - [DllImport(P2108_x86_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "TerrestrialStatisticalModel")] - private static extern int TerrestrialStatisticalModel_x86(double f__ghz, double d__km, double p, out double L_ctt__db); - - #endregion - - #region 64-Bit P/Invoke Definitions - - [DllImport(P2108_x64_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "AeronauticalStatisticalModel")] - private static extern int AeronauticalStatisticalModel_x64(double f__ghz, double theta__deg, double p, out double L_ces__db); - - [DllImport(P2108_x64_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "HeightGainTerminalCorrectionModel")] - private static extern int HeightGainTerminalCorrectionModel_x64(double f__ghz, double h__meter, double w_s__meter, double R__meter, int clutter_type, out double A_h__db); - - [DllImport(P2108_x64_DLL_NAME, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "TerrestrialStatisticalModel")] - private static extern int TerrestrialStatisticalModel_x64(double f__ghz, double d__km, double p, out double L_ctt__db); - - #endregion - - private delegate int AeronauticalStatisticalModelDelegate(double f__ghz, double theta__deg, double p, out double L_ces__db); - private delegate int HeightGainTerminalCorrectionModelDelegate(double f__ghz, double h__meter, double w_s__meter, double R__meter, int clutter_type, out double A_h__db); - private delegate int TerrestrialStatisticalModelDelegate(double f__ghz, double d__km, double p, out double L_ctt__db); - - private static readonly AeronauticalStatisticalModelDelegate AeronauticalStatisticalModel_Invoke; - private static readonly HeightGainTerminalCorrectionModelDelegate HeightGainTerminalCorrectionModel_Invoke; - private static readonly TerrestrialStatisticalModelDelegate TerrestrialStatisticalModel_Invoke; - - static P2108() - { - // set the binding to the correct native DLL architecture - - if (Environment.Is64BitProcess) - { - AeronauticalStatisticalModel_Invoke = AeronauticalStatisticalModel_x64; - HeightGainTerminalCorrectionModel_Invoke = HeightGainTerminalCorrectionModel_x64; - TerrestrialStatisticalModel_Invoke = TerrestrialStatisticalModel_x64; - } - else - { - AeronauticalStatisticalModel_Invoke = AeronauticalStatisticalModel_x86; - HeightGainTerminalCorrectionModel_Invoke = HeightGainTerminalCorrectionModel_x86; - TerrestrialStatisticalModel_Invoke = TerrestrialStatisticalModel_x86; - } - } - - /// - /// The Earth-space and aeronautical statistical clutter loss model as described in Section 3.3. - /// - /// Frequency, in GHz - /// Elevation angle, in degrees - /// Percentage of locations, in % - /// Additional loss (clutter loss), in dB - /// Error code - public static int AeronauticalStatisticalModel(double f__ghz, double theta__deg, double p, out double L_ces__db) - => AeronauticalStatisticalModel_Invoke(f__ghz, theta__deg, p, out L_ces__db); - - /// - /// Height gain terminal correction model as described in Section 3.1. - /// - /// Frequency, in GHz - /// Antenna height, in meters - /// Street width, in meters - /// Representative clutter height, in meters - /// Clutter type - /// Additional loss (clutter loss), in dB - /// Error code - public static int HeightGainTerminalCorrectionModel(double f__ghz, double h__meter, double w_s__meter, double R__meter, ClutterType clutter_type, out double A_h__db) - => HeightGainTerminalCorrectionModel_Invoke(f__ghz, h__meter, w_s__meter, R__meter, (int)clutter_type, out A_h__db); - - /// - /// Statistical clutter loss model for terrestrial paths as described in Section 3.2. - /// - /// Frequency, in GHz - /// Path distance, in km - /// Percentage of locations, in % - /// Additional loss (clutter loss), in dB - /// Error code - public static int TerrestrialStatisticalModel(double f__ghz, double d__km, double p, out double L_ctt__db) - => TerrestrialStatisticalModel_Invoke(f__ghz, d__km, p, out L_ctt__db); - } -} diff --git a/dotnet/ITS.ITU.PSeries.P2108/Properties/AssemblyInfo.cs b/dotnet/ITS.ITU.PSeries.P2108/Properties/AssemblyInfo.cs deleted file mode 100644 index 60f50ac..0000000 --- a/dotnet/ITS.ITU.PSeries.P2108/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Recommendation ITU-R P.2108-1")] -[assembly: AssemblyDescription("Recommendation ITU-R P.2108-1")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("The Institute for Telecommunication Sciences")] -[assembly: AssemblyProduct("Recommendation ITU-R P.2108-1")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6aa0c335-f019-4818-97b6-7b32bbe190e4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dotnet/UnitTests/BVT.cs b/dotnet/UnitTests/BVT.cs deleted file mode 100644 index 042a4af..0000000 --- a/dotnet/UnitTests/BVT.cs +++ /dev/null @@ -1,69 +0,0 @@ -using ITS.ITU.PSeries; -using Xunit; - -namespace UnitTests -{ - public class BVT - { - const double EPSILON = 0.1; - - /// - /// Tests for Sec 3.1 Height Gain Terminal Correction clutter model - /// - /// Frequency, in GHz - /// Antenna height, in meters - /// Street width, in meters - /// Representative clutter height, in meters - /// Clutter type - /// Return code - /// Additional loss (clutter loss), in dB - [Theory] - [MemberData(nameof(TestData.HeightGainTerminalCorrectionModelTestData), MemberType = typeof(TestData))] - public void HeightGainTerminalCorrectionModelTest(double f__ghz, - double h__meter, double w_s__meter, double R__meter, - P2108.ClutterType clutter_type, int rtn, double A_h__db) - { - var r = P2108.HeightGainTerminalCorrectionModel(f__ghz, h__meter, w_s__meter, R__meter, clutter_type, out double A__db); - - Assert.Equal(rtn, r); - Assert.Equal(A_h__db, A__db, EPSILON); - } - - /// - /// Tests for Sec 3.2 Terrestrial Statistical clutter model - /// Frequency, in GHz - /// Path distance, in km - /// Percentage of locations, in % - /// Return code - /// Additional loss (clutter loss), in dB - [Theory] - [MemberData(nameof(TestData.TerrestrialStatisticalModelTestData), MemberType = typeof(TestData))] - public void TerrestrialStatisticalModelTest(double f__ghz, - double d__km, double p, int rtn, double L_ctt__db) - { - var r = P2108.TerrestrialStatisticalModel(f__ghz, d__km, p, out double L__db); - - Assert.Equal(rtn, r); - Assert.Equal(L_ctt__db, L__db, EPSILON); - } - - /// - /// Tests for Sec 3.3 Aeronautical Statistical clutter model - /// - /// Frequency, in GHz - /// Elevation angle, in degrees - /// Percentage of locations, in % - /// Return code - /// Additional loss (clutter loss), in dB - [Theory] - [MemberData(nameof(TestData.AeronauticalStatisticalModelTestData), MemberType = typeof(TestData))] - public void AeronauticalStatisticalModelTest(double f__ghz, - double theta__deg, double p, int rtn, double L_ces__db) - { - var r = P2108.AeronauticalStatisticalModel(f__ghz, theta__deg, p, out double L__db); - - Assert.Equal(rtn, r); - Assert.Equal(L_ces__db, L__db, EPSILON); - } - } -} diff --git a/dotnet/UnitTests/Properties/AssemblyInfo.cs b/dotnet/UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index eb90b8c..0000000 --- a/dotnet/UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("UnitTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2023")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("1fb5483e-2eed-4e27-a1d4-16646507ec9f")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dotnet/UnitTests/TestData.cs b/dotnet/UnitTests/TestData.cs deleted file mode 100644 index f3e65c5..0000000 --- a/dotnet/UnitTests/TestData.cs +++ /dev/null @@ -1,108 +0,0 @@ -using ITS.ITU.PSeries; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace UnitTests -{ - public class TestData - { - static readonly string[] _heightGainTestData; - static readonly string[] _terrestrialStatisticalTestData; - static readonly string[] _aeronauticalStatisticalTestData; - - public static IEnumerable HeightGainTerminalCorrectionModelTestData - { - get - { - foreach (var line in _heightGainTestData) - { - var parts = line.Split(','); - - // parse data line - double f__ghz = Convert.ToDouble(parts[0]); - double h__meter = Convert.ToDouble(parts[1]); - double w_s__meter = Convert.ToDouble(parts[2]); - double R__meter = Convert.ToDouble(parts[3]); - P2108.ClutterType clutter_type = (P2108.ClutterType)Convert.ToInt32(parts[4]); - int rtn = Convert.ToInt32(parts[5]); - double A_h__db = Convert.ToDouble(parts[6]); - - yield return new object[] - { - f__ghz, - h__meter, - w_s__meter, - R__meter, - clutter_type, - rtn, - A_h__db - }; - } - } - } - - public static IEnumerable TerrestrialStatisticalModelTestData - { - get - { - foreach (var line in _terrestrialStatisticalTestData) - { - var parts = line.Split(','); - - // parse data line - double f__ghz = Convert.ToDouble(parts[0]); - double d__km = Convert.ToDouble(parts[1]); - double p = Convert.ToDouble(parts[2]); - int rtn = Convert.ToInt32(parts[3]); - double L_ctt__db = Convert.ToDouble(parts[4]); - - yield return new object[] - { - f__ghz, - d__km, - p, - rtn, - L_ctt__db - }; - } - } - } - - public static IEnumerable AeronauticalStatisticalModelTestData - { - get - { - foreach (var line in _aeronauticalStatisticalTestData) - { - var parts = line.Split(','); - - // parse data line - double f__ghz = Convert.ToDouble(parts[0]); - double theta__deg = Convert.ToDouble(parts[1]); - double p = Convert.ToDouble(parts[2]); - int rtn = Convert.ToInt32(parts[3]); - double L_ces__db = Convert.ToDouble(parts[4]); - - yield return new object[] - { - f__ghz, - theta__deg, - p, - rtn, - L_ces__db - }; - } - } - } - - static TestData() - { - // load test data from file - _heightGainTestData = File.ReadAllLines("HeightGainTerminalCorrectionModelTestData.csv").Skip(1).ToArray(); - _terrestrialStatisticalTestData = File.ReadAllLines("TerrestrialStatisticalModelTestData.csv").Skip(1).ToArray(); - _aeronauticalStatisticalTestData = File.ReadAllLines("AeronauticalStatisticalModelTestData.csv").Skip(1).ToArray(); - } - } -} diff --git a/dotnet/UnitTests/Unit-Test-Instructions.txt b/dotnet/UnitTests/Unit-Test-Instructions.txt deleted file mode 100644 index 5ec611b..0000000 --- a/dotnet/UnitTests/Unit-Test-Instructions.txt +++ /dev/null @@ -1,7 +0,0 @@ -Instructions for running unit tests ------------------------------------ -1. Build C++ code in x64 and x86 configuration. -2. Rename C++ DLLs with corresponding suffix '_x64' or '_x86', accordingly. These - are the DLL names that .NET class is looking for. -3. Move C++ DLLs to the Debug directory where the UnitTests project is built. -4. Use Visual Studio Text Explorer to run the tests. \ No newline at end of file diff --git a/dotnet/UnitTests/UnitTests.csproj b/dotnet/UnitTests/UnitTests.csproj deleted file mode 100644 index 7866218..0000000 --- a/dotnet/UnitTests/UnitTests.csproj +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - Debug - AnyCPU - {1FB5483E-2EED-4E27-A1D4-16646507EC9F} - Library - Properties - UnitTests - UnitTests - v4.8.1 - 512 - true - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.assert.2.4.2\lib\netstandard1.1\xunit.assert.dll - - - ..\packages\xunit.extensibility.core.2.4.2\lib\net452\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.4.2\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - {6aa0c335-f019-4818-97b6-7b32bbe190e4} - ITS.ITU.PSeries.P2108 - - - - - AeronauticalStatisticalModelTestData.csv - Always - - - HeightGainTerminalCorrectionModelTestData.csv - Always - - - TerrestrialStatisticalModelTestData.csv - Always - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file diff --git a/dotnet/UnitTests/packages.config b/dotnet/UnitTests/packages.config deleted file mode 100644 index 1be12c9..0000000 --- a/dotnet/UnitTests/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/nuget/build/net481/p2108.targets b/dotnet/nuget/build/net481/p2108.targets deleted file mode 100644 index 6e0352c..0000000 --- a/dotnet/nuget/build/net481/p2108.targets +++ /dev/null @@ -1,16 +0,0 @@ - - - - - PreserveNewest - p2108_x86.dll - - - - - - PreserveNewest - p2108_x64.dll - - - \ No newline at end of file diff --git a/dotnet/nuget/images/itslogo.png b/dotnet/nuget/images/itslogo.png deleted file mode 100644 index 3d2f319..0000000 Binary files a/dotnet/nuget/images/itslogo.png and /dev/null differ diff --git a/dotnet/nuget/p2108.nuspec b/dotnet/nuget/p2108.nuspec deleted file mode 100644 index 7722383..0000000 --- a/dotnet/nuget/p2108.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - P2108 - 1.0.0 - The Institute for Telecommunication Sciences - The Institute for Telecommunication Sciences - LICENSE.md - https://github.com/NTIA/p2108 - images\itslogo.png - true - Recommendation ITU-R P.2108: The prediction of clutter loss for terrestrial and airborne paths. This software package implements the three clutter prediction methods defined in the Section 3 of Annex 1. - None - ITU SG3 Propagation P2108 P.2108 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/p2108_dotnet.sln b/dotnet/p2108_dotnet.sln deleted file mode 100644 index 1d1f703..0000000 --- a/dotnet/p2108_dotnet.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33516.290 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ITS.ITU.PSeries.P2108", "ITS.ITU.PSeries.P2108\ITS.ITU.PSeries.P2108.csproj", "{6AA0C335-F019-4818-97B6-7B32BBE190E4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{1FB5483E-2EED-4E27-A1D4-16646507EC9F}" - ProjectSection(ProjectDependencies) = postProject - {6AA0C335-F019-4818-97B6-7B32BBE190E4} = {6AA0C335-F019-4818-97B6-7B32BBE190E4} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6AA0C335-F019-4818-97B6-7B32BBE190E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AA0C335-F019-4818-97B6-7B32BBE190E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AA0C335-F019-4818-97B6-7B32BBE190E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AA0C335-F019-4818-97B6-7B32BBE190E4}.Release|Any CPU.Build.0 = Release|Any CPU - {1FB5483E-2EED-4E27-A1D4-16646507EC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FB5483E-2EED-4E27-A1D4-16646507EC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FB5483E-2EED-4E27-A1D4-16646507EC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FB5483E-2EED-4E27-A1D4-16646507EC9F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {629E6553-3837-4848-B309-2AB6476F731A} - EndGlobalSection -EndGlobal diff --git a/extern/doxygen-awesome-css b/extern/doxygen-awesome-css new file mode 160000 index 0000000..568f56c --- /dev/null +++ b/extern/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit 568f56cde6ac78b6dfcc14acd380b2e745c301ea diff --git a/extern/googletest b/extern/googletest new file mode 160000 index 0000000..58d77fa --- /dev/null +++ b/extern/googletest @@ -0,0 +1 @@ +Subproject commit 58d77fa8070e8cec2dc1ed015d66b454c8d78850 diff --git a/extern/test-data b/extern/test-data new file mode 160000 index 0000000..e46db67 --- /dev/null +++ b/extern/test-data @@ -0,0 +1 @@ +Subproject commit e46db673fe14732cb3b25d0b37b35316de7b9d73 diff --git a/include/Consts.h b/include/Consts.h deleted file mode 100644 index c81d127..0000000 --- a/include/Consts.h +++ /dev/null @@ -1,21 +0,0 @@ - -/////////////////////////////////////////////// -// CONSTANTS -// - -// Clutter Types -#define CLUTTER_TYPE__WATER_SEA 1 -#define CLUTTER_TYPE__OPEN_RURAL 2 -#define CLUTTER_TYPE__SUBURBAN 3 -#define CLUTTER_TYPE__URBAN 4 -#define CLUTTER_TYPE__TREES_FOREST 5 -#define CLUTTER_TYPE__DENSE_URBAN 6 - -// Default Clutter Heights -#define DEFAULT_CLUTTER_HEIGHT__WATER_SEA 10 -#define DEFAULT_CLUTTER_HEIGHT__OPEN_RURAL 10 -#define DEFAULT_CLUTTER_HEIGHT__SUBURBAN 10 -#define DEFAULT_CLUTTER_HEIGHT__URBAN 15 -#define DEFAULT_CLUTTER_HEIGHT__TREES_FOREST 15 -#define DEFAULT_CLUTTER_HEIGHT__DENSE_URBAN 20 - diff --git a/include/Errors.h b/include/Errors.h deleted file mode 100644 index 63d9d98..0000000 --- a/include/Errors.h +++ /dev/null @@ -1,32 +0,0 @@ - -/////////////////////////////////////////////// -// GENERAL -// - -#define SUCCESS 0 - -/////////////////////////////////////////////// -// SECTION 3.1 ERROR CODES -// - -#define ERROR31__FREQUENCY 3100 -#define ERROR31__ANTENNA_HEIGHT 3101 -#define ERROR31__STREET_WIDTH 3102 -#define ERROR31__CLUTTER_HEIGHT 3103 -#define ERROR31__CLUTTER_TYPE 3104 - -/////////////////////////////////////////////// -// SECTION 3.2 ERROR CODES -// - -#define ERROR32__FREQUENCY 3200 -#define ERROR32__DISTANCE 3201 -#define ERROR32__PERCENTAGE 3202 - -/////////////////////////////////////////////// -// SECTION 3.3 ERROR CODES -// - -#define ERROR33__FREQUENCY 3300 -#define ERROR33__THETA 3301 -#define ERROR33__PERCENTAGE 3302 \ No newline at end of file diff --git a/include/P2108.h b/include/P2108.h index 0983b57..76ccf16 100644 --- a/include/P2108.h +++ b/include/P2108.h @@ -1,28 +1,136 @@ -#include "math.h" +/** @file P2108.h + * Interface header for this library + */ +#pragma once -#define DLLEXPORT extern "C" __declspec(dllexport) +#include // for std::string +#include // for std::unordered_map -#define PI 3.1415926535897932384 +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { -/////////////////////////////////////////////// -// FUNCTIONS -// +// Define cross-platform PROPLIB_API to export functions +#ifndef DOXYGEN_SHOULD_SKIP + #ifndef PROPLIB_API + #ifdef _WIN32 + #define PROPLIB_API extern "C" __declspec(dllexport) + #else + #define PROPLIB_API extern "C" + #endif + #endif +#endif +//////////////////////////////////////////////////////////////////////////////// +// Enums + +/** Clutter type enum, based on Table 3 in Section 3.1 */ +enum ClutterType { + WATER_SEA = 1, /**< Water/sea clutter type */ + OPEN_RURAL = 2, /**< Open/rural clutter type */ + SUBURBAN = 3, /**< Suburban clutter type */ + URBAN = 4, /**< Urban clutter type */ + TREES_FOREST = 5, /**< Trees/forest clutter type */ + DENSE_URBAN = 6, /**< Dense urban clutter type */ +}; + +/******************************************************************************* + * Default values of the representative clutter height @f$ R @f$, + * in meters, by clutter type. + * + * These should be used as inputs to the height gain terminal + * correction model when local information is not available. + ******************************************************************************/ +enum RepresentativeClutterHeight { + R__WATER_SEA = 10, /**< @f$ R @f$ for the trees/forest clutter type */ + R__OPEN_RURAL = 10, /**< @f$ R @f$ for the open/rural clutter type */ + R__SUBURBAN = 10, /**< @f$ R @f$ for the suburban clutter type */ + R__URBAN = 15, /**< @f$ R @f$ for the urban clutter type */ + R__TREES_FOREST = 15, /**< @f$ R @f$ for the trees/forest clutter type */ + R__DENSE_URBAN = 20, /**< @f$ R @f$ for the dense urban clutter type */ +}; + +/******************************************************************************* + * Return Codes defined by this software (0-127) + ******************************************************************************/ +// clang-format off +enum ReturnCode { + SUCCESS = 0, /**< Successful execution */ + + // Section 3.1 Error Codes + ERROR31__FREQUENCY = 32, /**< Frequency must be between 0.3 and 3 GHz */ + ERROR31__ANTENNA_HEIGHT, /**< Antenna height must be @f$ \geq @f$ 0 meters */ + ERROR31__STREET_WIDTH, /**< Street width must be @f$ > @f$ 0 meters */ + ERROR31__CLUTTER_HEIGHT, /**< Representative clutter height must be @f$ > @f$ 0 meters */ + ERROR31__CLUTTER_TYPE, /**< Invalid value for clutter type */ + + // Section 3.2 Error Codes + ERROR32__FREQUENCY = 48, /**< Frequency must be between 2 and 67 GHz */ + ERROR32__DISTANCE, /**< Path distance must be @f$ \geq @f$ 0.25 km */ + ERROR32__PERCENTAGE, /**< Percentage must be between 0 and 100 */ + + // Section 3.3 Error Codes + ERROR33__FREQUENCY = 64, /**< Frequency must be between 10 and 100 GHz */ + ERROR33__THETA, /**< Elevation angle must be between 0 and 100 GHz */ + ERROR33__PERCENTAGE, /**< Percentage must be between 0 and 100 */ +}; +// clang-format on + +//////////////////////////////////////////////////////////////////////////////// +// Constants +/** Approximate value of @f$ \pi @f$ */ +constexpr double PI = 3.14159265358979323846; + +//////////////////////////////////////////////////////////////////////////////// // Public Functions -DLLEXPORT int AeronauticalStatisticalModel(double f__ghz, double theta__deg, - double p, double* L_ces__db); -DLLEXPORT int TerrestrialStatisticalModel(double f__ghz, double d__km, - double p, double* L_ctt__db); -DLLEXPORT int HeightGainTerminalCorrectionModel(double f__ghz, double h__meter, - double w_s__meter, double R__meter, int clutter_type, double* A_h__db); +PROPLIB_API ReturnCode AeronauticalStatisticalModel( + const double f__ghz, + const double theta__deg, + const double p, + double &L_ces__db +); +PROPLIB_API ReturnCode TerrestrialStatisticalModel( + const double f__ghz, const double d__km, const double p, double &L_ctt__db +); +PROPLIB_API ReturnCode HeightGainTerminalCorrectionModel( + const double f__ghz, + const double h__meter, + const double w_s__meter, + const double R__meter, + const ClutterType clutter_type, + double &A_h__db +); +PROPLIB_API char *GetReturnStatusCharArray(const int code); +PROPLIB_API void FreeReturnStatusCharArray(char *c_msg); +//////////////////////////////////////////////////////////////////////////////// // Private Functions -double cot(double x); -double InverseComplementaryCumulativeDistribution(double q); -double Equation_2a(double nu); -double Equation_2b(double K_h2, double h__meter, double R__meter); -int Section3p1_InputValidation(double f__ghz, double h__meter, double w_s__meter, - double R__meter); -int Section3p2_InputValidation(double f__ghz, double d__km, double p); -int Section3p3_InputValidation(double f__ghz, double theta__deg, double p); -double TerrestrialStatisticalModelHelper(double f__ghz, double d__km, double p); +std::string GetReturnStatus(const int code); +double cot(const double x); +double InverseComplementaryCumulativeDistribution(const double q); +double Equation_2a(const double nu); +double Equation_2b( + const double K_h2, const double h__meter, const double R__meter +); +ReturnCode Section3p1_InputValidation( + const double f__ghz, + const double h__meter, + const double w_s__meter, + const double R__meter +); +ReturnCode Section3p2_InputValidation( + const double f__ghz, const double d__km, const double p +); +ReturnCode Section3p3_InputValidation( + const double f__ghz, const double theta__deg, const double p +); +double TerrestrialStatisticalModelHelper( + const double f__ghz, const double d__km, const double p +); + + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS diff --git a/src/AeronauticalStatisticalModel.cpp b/src/AeronauticalStatisticalModel.cpp index bad7d0d..930d8a0 100644 --- a/src/AeronauticalStatisticalModel.cpp +++ b/src/AeronauticalStatisticalModel.cpp @@ -1,57 +1,76 @@ -#include "../include/P2108.h" -#include "../include/Errors.h" +/** @file AeronauticalStatisticalModel.cpp + * Implements the model from ITU-R P.2108 Section 3.3. + */ +#include "P2108.h" -/*============================================================================= - | - | Description: The Earth-space and aeronautical statistical clutter loss - | model as described in Section 3.3. This model is applicable - | when one end of the path is within man-made clutter and the - | other end is a satellite, aeroplane, or other platform - | above the Earth. - | - | Input: f__ghz - Frequency, in GHz - | theta__deg - Elevation angle, in degrees - | p - Percentage of locations, in % - | - | Output: L_ces__db - Additional loss (clutter loss), in dB - | - | Returns: error - Error code - | - *===========================================================================*/ -int AeronauticalStatisticalModel(double f__ghz, double theta__deg, double p, - double* L_ces__db) -{ - int rtn = Section3p3_InputValidation(f__ghz, theta__deg, p); +#include // for std::pow, std::log, std::tan + +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { + +/******************************************************************************* + * The Earth-space and aeronautical statistical clutter loss model as described + * in Section 3.3. + * + * This model is applicable when one end of the path is within man-made clutter + * and the other end is a satellite, aeroplane, or other platform above the + * Earth. + * + * Frequency range: @f$ 10 < f < 100 @f$ (GHz)\n + * Elevation angle range: @f$ 0 < \theta < 90 @f$ (degrees)\n + * Percentage locations range: @f$ 0 < p < 100 @f$ (%) + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] theta__deg Elevation angle, in degrees + * @param[in] p Percentage of locations, in % + * @param[out] L_ces__db Additional loss (clutter loss), in dB + * @return Return code + ******************************************************************************/ +ReturnCode AeronauticalStatisticalModel( + const double f__ghz, + const double theta__deg, + const double p, + double &L_ces__db +) { + ReturnCode rtn = Section3p3_InputValidation(f__ghz, theta__deg, p); if (rtn != SUCCESS) return rtn; - double A_1 = 0.05; - double K_1 = 93 * pow(f__ghz, 0.175); - - double part1 = log(1 - p / 100.0); - double part2 = A_1 * (1 - theta__deg / 90.0) + PI * theta__deg / 180.0; - double part3 = 0.5 * (90.0 - theta__deg) / 90.0; - double part4 = 0.6 * InverseComplementaryCumulativeDistribution(p / 100); + constexpr double A_1 = 0.05; + const double K_1 = 93 * std::pow(f__ghz, 0.175); - *L_ces__db = pow(-K_1 * part1 * cot(part2), part3) - 1 - part4; + const double part1 = std::log(1 - p / 100.0); + const double part2 + = A_1 * (1 - theta__deg / 90.0) + PI * theta__deg / 180.0; + const double part3 = 0.5 * (90.0 - theta__deg) / 90.0; + const double part4 + = 0.6 * InverseComplementaryCumulativeDistribution(p / 100); - return SUCCESS; + L_ces__db = std::pow(-K_1 * part1 * cot(part2), part3) - 1 - part4; + return rtn; } -/*============================================================================= - | - | Description: Input validation for the Earth-space and aeronautical - | statistical clutter loss model (Section 3.3). - | - | Input: f__ghz - Frequency, in GHz - | theta__deg - Elevation angle, in degrees - | p - Percentage of locations, in % - | - | Returns: error code or SUCCESS - | - *===========================================================================*/ -int Section3p3_InputValidation(double f__ghz, double theta__deg, double p) -{ +/******************************************************************************* + * Inputs-in-range validation for the Earth-space and aeronautical statistical + * clutter loss model (Section 3.3). + * + * Returns `SUCCESS` if all parameters are valid. Otherwise, invalid inputs will + * return unique error codes. + * + * Frequency range: @f$ 10 < f < 100 @f$ (GHz)\n + * Elevation angle range: @f$ 0 < \theta < 90 @f$ (degrees)\n + * Percentage locations range: @f$ 0 < p < 100 @f$ (%) + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] theta__deg Elevation angle, in degrees + * @param[in] p Percentage of locations, in % + * @return Return code + ******************************************************************************/ +ReturnCode Section3p3_InputValidation( + const double f__ghz, const double theta__deg, const double p +) { if (f__ghz < 10 || f__ghz > 100) return ERROR33__FREQUENCY; @@ -64,16 +83,19 @@ int Section3p3_InputValidation(double f__ghz, double theta__deg, double p) return SUCCESS; } -/*============================================================================= - | - | Description: Helper function for cotangent operation. - | - | Input: x - Argument - | - | Returns: cot(x) - Cotangent of the argument - | - *===========================================================================*/ -double cot(double x) -{ - return 1 / tan(x); -} \ No newline at end of file +/******************************************************************************* + * Helper function to calculate @f$ \cot(x) @f$. + * + * The calculation is implemented simply as @f$ 1 / \cot(x) @f$. + * + * @param[in] x Argument, in radians + * @return Cotangent of the argument, @f$ \cot(x) @f$ + ******************************************************************************/ +double cot(const double x) { + return 1 / std::tan(x); +} + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..cf5c51d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,55 @@ +########################################### +## BUILD THE LIBRARY +########################################### +proplib_message("Configuring library ${LIB_NAME}") + +set(LIB_HEADERS "${PROJECT_SOURCE_DIR}/include") +set(LIB_FILES + "AeronauticalStatisticalModel.cpp" + "HeightGainTerminalCorrectionModel.cpp" + "InverseComplementaryCumulativeDistribution.cpp" + "TerrestrialStatisticalModel.cpp" + "ReturnCodes.cpp" + "${LIB_HEADERS}/${LIB_NAME}.h" +) + +# By default, create shared library +if (NOT DEFINED BUILD_SHARED_LIBS) + message(STATUS "STATUS: BUILD_SHARED_LIBS is not defined to build the library: " ${LIB_NAME} ".") + add_library(${LIB_NAME} SHARED ${LIB_FILES}) +else () + message(STATUS "STATUS: BUILD_SHARED_LIBS is " ${BUILD_SHARED_LIBS} " to build the library: " ${LIB_NAME} ".") + add_library(${LIB_NAME} ${LIB_FILES}) +endif () + +# Add the include directory +target_include_directories(${LIB_NAME} PUBLIC "${LIB_HEADERS}") + +# Set PropLib compiler option defaults +configure_proplib_target(${LIB_NAME}) + +# Add definition to get the library name and version inside the library +add_compile_definitions( + LIBRARY_NAME="${LIB_NAME}" + LIBRARY_VERSION="${PROJECT_VERSION}" +) + +# Platform-specific configurations +if (WIN32) + set_target_properties(${LIB_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS true) +endif () +if (UNIX) + # avoid prefixing "lib" to the output file, for cross-platform consistency + set(CMAKE_SHARED_LIBRARY_PREFIX "") +endif () + +# Set some target metadata +set_target_properties( + ${LIB_NAME} PROPERTIES + OUTPUT_NAME ${LIB_NAME}-${PROJECT_VERSION}- + VERSION ${PROJECT_VERSION} + DEBUG_POSTFIX ${ARCH_SUFFIX} + RELEASE_POSTFIX ${ARCH_SUFFIX} +) + +proplib_message("Done configuring library ${LIB_NAME}") \ No newline at end of file diff --git a/src/HeightGainTerminalCorrectionModel.cpp b/src/HeightGainTerminalCorrectionModel.cpp index 764383f..09d6678 100644 --- a/src/HeightGainTerminalCorrectionModel.cpp +++ b/src/HeightGainTerminalCorrectionModel.cpp @@ -1,86 +1,103 @@ -#include "../include/P2108.h" -#include "../include/Errors.h" -#include "../include/Consts.h" - -/*============================================================================= - | - | Description: Height gain terminal correction model as described in - | Section 3.1. This method gives the median loss due to - | different terminal surroundings. This model can be - | applied to both transmitting and receiving ends of the path. - | - | Input: f__ghz - Frequency, in GHz - | h__meter - Antenna height, in meters - | w_s__meter - Street width, in meters - | R__meter - Representative clutter height, in meters - | clutter_type - Clutter type - | - | Output: A_h__db - Additional loss (clutter loss), in dB - | - | Returns: error - Error code - | - *===========================================================================*/ -int HeightGainTerminalCorrectionModel(double f__ghz, double h__meter, - double w_s__meter, double R__meter, int clutter_type, double *A_h__db) -{ - int rtn = Section3p1_InputValidation(f__ghz, h__meter, w_s__meter, R__meter); +/** @file HeightGainTerminalCorrectionModel.cpp + * Implements the model from ITU-R P.2108 Section 3.1. + */ +#include "P2108.h" + +#include // for std::atan, std::log10, std::pow, std::sqrt + +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { + +/******************************************************************************* + * Height gain terminal correction model as described in Section 3.1. + * + * This method gives the median loss due to different terminal surroundings. + * This model can be applied to both transmitting and receiving ends of the + * path. + * + * Frequency range: @f$ 0.03 \leq f \leq 3 @f$ (GHz)\n + * Antenna height range: @f$ 0 \leq h @f$ (m)\n + * Street width range: @f$ 0 < w_s @f$ (m)\n + * Representative clutter height range: @f$ 0 < R @f$ (m) + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] h__meter Antenna height, in meters + * @param[in] w_s__meter Street width, in meters + * @param[in] R__meter Representative clutter height, in meters + * @param[in] clutter_type Clutter type + * @param[out] A_h__db Additional loss (clutter loss), in dB + * @return Return code + ******************************************************************************/ +ReturnCode HeightGainTerminalCorrectionModel( + const double f__ghz, + const double h__meter, + const double w_s__meter, + const double R__meter, + const ClutterType clutter_type, + double &A_h__db +) { + const ReturnCode rtn + = Section3p1_InputValidation(f__ghz, h__meter, w_s__meter, R__meter); if (rtn != SUCCESS) return rtn; - if (h__meter >= R__meter) - { - *A_h__db = 0; + if (h__meter >= R__meter) { + A_h__db = 0; return SUCCESS; } - double h_dif__meter = R__meter - h__meter; // Equation (2d) - double theta_clut__deg = atan(h_dif__meter / w_s__meter) * 180.0 / PI; // Equation (2e) - double K_h2 = 21.8 + 6.2 * log10(f__ghz); // Equation (2f) - - switch (clutter_type) - { - case CLUTTER_TYPE__WATER_SEA: - case CLUTTER_TYPE__OPEN_RURAL: - *A_h__db = Equation_2b(K_h2, h__meter, R__meter); - break; - - case CLUTTER_TYPE__SUBURBAN: - case CLUTTER_TYPE__URBAN: - case CLUTTER_TYPE__TREES_FOREST: - case CLUTTER_TYPE__DENSE_URBAN: - { - double K_nu = 0.342 * sqrt(f__ghz); // Equation (2g) - double nu = K_nu * sqrt(h_dif__meter * theta_clut__deg); // Equation (2c) - - *A_h__db = Equation_2a(nu); - break; - } - default: - return ERROR31__CLUTTER_TYPE; + const double h_dif__meter = R__meter - h__meter; // Equation (2d) + const double theta_clut__deg + = std::atan(h_dif__meter / w_s__meter) * 180.0 / PI; // Equation (2e) + const double K_h2 = 21.8 + 6.2 * std::log10(f__ghz); // Equation (2f) + + switch (clutter_type) { + case ClutterType::WATER_SEA: + case ClutterType::OPEN_RURAL: + A_h__db = Equation_2b(K_h2, h__meter, R__meter); + break; + + case ClutterType::SUBURBAN: + case ClutterType::URBAN: + case ClutterType::TREES_FOREST: + case ClutterType::DENSE_URBAN: + { + const double K_nu = 0.342 * std::sqrt(f__ghz); // Equation (2g) + const double nu = K_nu + * std::sqrt( + h_dif__meter * theta_clut__deg + ); // Equation (2c) + + A_h__db = Equation_2a(nu); + break; + } + default: + return ERROR31__CLUTTER_TYPE; } return SUCCESS; } -/*============================================================================= - | - | Description: Input validation for the height gain terminal correction - | model (Section 3.1). - | Note: Input parameter 'clutter_type' is validated in - | the main function's switch statement through the use - | of default to simplify code structure. - | - | Input: f__ghz - Frequency, in GHz - | h__meter - Antenna height, in meters - | w_s__meter - Street width, in meters - | R__meter - Representative clutter height, in meters - | - | Returns: error code or SUCCESS - | - *===========================================================================*/ -int Section3p1_InputValidation(double f__ghz, double h__meter, double w_s__meter, - double R__meter) -{ +/******************************************************************************* + * Input validation for the height gain terminal correction model (Section 3.1). + * + * Note: Input parameter 'clutter_type' is validated in the main function's + * switch statement through the use of default to simplify code structure. + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] h__meter Antenna height, in meters + * @param[in] w_s__meter Street width, in meters + * @param[in] R__meter Representative clutter height, in meters + * @return Return code + ******************************************************************************/ +ReturnCode Section3p1_InputValidation( + const double f__ghz, + const double h__meter, + const double w_s__meter, + const double R__meter +) { if (f__ghz < 0.03 || f__ghz > 3) return ERROR31__FREQUENCY; @@ -96,42 +113,42 @@ int Section3p1_InputValidation(double f__ghz, double h__meter, double w_s__meter return SUCCESS; } -/*============================================================================= - | - | Description: Equation (2a) of Section 3.1 - | - | Input: nu - Dimensionless diffraction parameter - | - | Returns: A_h__db - Additional loss (clutter loss), in dB - | - *===========================================================================*/ -double Equation_2a(double nu) -{ +/******************************************************************************* + * Equation (2a) of Section 3.1 + * + * @param[in] nu Dimensionless diffraction parameter + * @return Additional loss (clutter loss), in dB + ******************************************************************************/ +double Equation_2a(const double nu) { double J_nu__db; - if (nu <= -0.78) + if (nu <= -0.78) { J_nu__db = 0; - else - J_nu__db = 6.9 + 20 * log10(sqrt(pow(nu - 0.1, 2) + 1) + nu - 0.1); - - double A_h__db = J_nu__db - 6.03; + } else { + const double term1 = std::sqrt(std::pow(nu - 0.1, 2) + 1); + J_nu__db = 6.9 + 20 * std::log10(term1 + nu - 0.1); + } + const double A_h__db = J_nu__db - 6.03; return A_h__db; } -/*============================================================================= - | - | Description: Equation (2b) of Section 3.1 - | - | Input: K_h2 - Intermediate parameter - | h__meter - Antenna height, in meters - | R__meter - Representative clutter height, in meters - | - | Returns: A_h__db - Additional loss (clutter loss), in dB - | - *===========================================================================*/ -double Equation_2b(double K_h2, double h__meter, double R__meter) -{ - double A_h__db = -K_h2 * log10(h__meter / R__meter); +/******************************************************************************* + * Equation (2b) of Section 3.1 + * + * @param[in] K_h2 Intermediate parameter + * @param[in] h__meter Antenna height, in meters + * @param[in] R__meter Representative clutter height, in meters + * @return Additional loss (clutter loss), in dB + ******************************************************************************/ +double Equation_2b( + const double K_h2, const double h__meter, const double R__meter +) { + const double A_h__db = -K_h2 * std::log10(h__meter / R__meter); return A_h__db; -} \ No newline at end of file +} + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS \ No newline at end of file diff --git a/src/InverseComplementaryCumulativeDistribution.cpp b/src/InverseComplementaryCumulativeDistribution.cpp index a192806..139d465 100644 --- a/src/InverseComplementaryCumulativeDistribution.cpp +++ b/src/InverseComplementaryCumulativeDistribution.cpp @@ -1,35 +1,48 @@ -#include "../include/P2108.h" - -/*============================================================================= - | - | Description: This function computes the inverse complementary - | cumulative distribution function approximation as - | described in Recommendation ITU-R P.1057. This - | approximation is sourced from Formula 26.2.23 in - | Abramowitz & Stegun. This approximation has an error - | of abs(epsilon(p)) < 4.5e-4 - | - | Input: q - Percentage, 0.0 < q < 1.0 - | - | Returns: Q_q - Q(q)^-1 - | - *===========================================================================*/ -double InverseComplementaryCumulativeDistribution(double q) -{ - double C_0 = 2.515517; - double C_1 = 0.802853; - double C_2 = 0.010328; - double D_1 = 1.432788; - double D_2 = 0.189269; - double D_3 = 0.001308; +/** @internal @file InverseComplementaryCumulativeDistribution.cpp + * @brief Implements a function to calculate the inverse CCDF. + */ +#include "P2108.h" + +#include // for std::log, std::sqrt +#include // for std::out_of_range + +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { + +/******************************************************************************* + * Compute the inverse complementary cumulative distribution function + * approximation as described in Recommendation ITU-R P.1057. + * + * This approximation is sourced from Formula 26.2.23 in Abramowitz & Stegun. + * This approximation has an error of @f$ |\epsilon(p)| < 4.5\times 10^{-4} @f$ + * + * @param q Percentage, @f$ 0.0 < q < 1.0 @f$ + * @return Q(q)^-1 + * @throw std::out_of_range if the input is outside the range [0.0, 1.0] + ******************************************************************************/ +double InverseComplementaryCumulativeDistribution(const double q) { + if (q <= 0.0 || q >= 1.0) { + throw std::out_of_range("Input q must be between 0.0 and 1.0"); + } + + // Constants from Abramowitz & Stegun 26.2.23 + constexpr double C_0 = 2.515517; + constexpr double C_1 = 0.802853; + constexpr double C_2 = 0.010328; + constexpr double D_1 = 1.432788; + constexpr double D_2 = 0.189269; + constexpr double D_3 = 0.001308; double x = q; if (q > 0.5) x = 1.0 - x; - double T_x = sqrt(-2.0 * log(x)); + const double T_x = std::sqrt(-2.0 * std::log(x)); - double zeta_x = ((C_2 * T_x + C_1) * T_x + C_0) / (((D_3 * T_x + D_2) * T_x + D_1) * T_x + 1.0); + const double zeta_x = ((C_2 * T_x + C_1) * T_x + C_0) + / (((D_3 * T_x + D_2) * T_x + D_1) * T_x + 1.0); double Q_q = T_x - zeta_x; @@ -37,4 +50,9 @@ double InverseComplementaryCumulativeDistribution(double q) Q_q = -Q_q; return Q_q; -} \ No newline at end of file +} + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS \ No newline at end of file diff --git a/src/ReturnCodes.cpp b/src/ReturnCodes.cpp new file mode 100644 index 0000000..9d2ac70 --- /dev/null +++ b/src/ReturnCodes.cpp @@ -0,0 +1,95 @@ +/** @file ReturnCodes.cpp + * Maps status messages to library return codes + */ + +#include "P2108.h" + +#ifdef _WIN32 + // Ensure strcpy_s is available on Windows + #ifndef __STDC_LIB_EXT1__ + #define __STDC_LIB_EXT1__ + #endif + #ifndef __STDC_WANT_LIB_EXT1__ + #define __STDC_WANT_LIB_EXT1__ 1 + #endif +#endif + +#include // for strcpy, strcpy_s +#include // for std::string +#include // for std::unordered_map + +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { + +/******************************************************************************* + * Get an error message string from a return code. + * + * @param[in] code Integer return code. + * @return A status message corresponding to the input code. + ******************************************************************************/ +std::string GetReturnStatus(const int code) { + static const std::unordered_map messages + = {{SUCCESS, "Successful execution"}, + {ERROR31__FREQUENCY, "Frequency must be between 0.3 and 3 GHz"}, + {ERROR31__ANTENNA_HEIGHT, "Antenna height must be >= 0 meters"}, + {ERROR31__STREET_WIDTH, "Street width must be > 0 meters"}, + {ERROR31__CLUTTER_HEIGHT, + "Representative clutter height must be > 0 meters"}, + {ERROR31__CLUTTER_TYPE, "Invalid value for clutter type"}, + {ERROR32__FREQUENCY, "Frequency must be between 2 and 67 GHz"}, + {ERROR32__DISTANCE, "Path distance must be >= 0.25 km"}, + {ERROR32__PERCENTAGE, "Percentage must be between 0 and 100"}, + {ERROR33__FREQUENCY, "Frequency must be between 10 and 100 GHz"}, + {ERROR33__THETA, "Elevation angle must be between 0 and 100 GHz"}, + {ERROR33__PERCENTAGE, "Percentage must be between 0 and 100"}}; + // Construct status message + std::string msg = LIBRARY_NAME; + msg += " v"; + msg += LIBRARY_VERSION; + if (code == SUCCESS) { + msg += " Status: "; + } else { + msg += " Error: "; + } + + auto it = messages.find(static_cast(code)); + if (it != messages.end()) { + msg += it->second; + } else { + msg += "Undefined return code"; + } + return msg; +} + +/******************************************************************************* + * Get an error message string (as C-style string) from a return code. + * + * @param[in] code Integer return code. + * @return A status message corresponding to the input code. + ******************************************************************************/ +char *GetReturnStatusCharArray(const int code) { + const std::string msg = GetReturnStatus(code); + char *c_msg = new char[msg.size() + 1]; +#ifdef _WIN32 + strcpy_s(c_msg, msg.size() + 1, msg.c_str()); +#else + strcpy(c_msg, msg.c_str()); +#endif + return c_msg; +} + +/******************************************************************************* + * Free the memory allocated by GetReturnStatusCharArray + * + * @param[in] c_msg The status message C-style string to delete + ******************************************************************************/ +void FreeReturnStatusCharArray(char *c_msg) { + delete[] c_msg; +} + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS \ No newline at end of file diff --git a/src/TerrestrialStatisticalModel.cpp b/src/TerrestrialStatisticalModel.cpp index 4b92b41..faa232a 100644 --- a/src/TerrestrialStatisticalModel.cpp +++ b/src/TerrestrialStatisticalModel.cpp @@ -1,86 +1,103 @@ -#include "../include/P2108.h" -#include "../include/Errors.h" - -/*============================================================================= - | - | Description: Statistical clutter loss model for terrestrial paths as - | described in Section 3.2. This model can be applied - | for urban and suburban clutter loss modelling. - | - | Input: f__ghz - Frequency, in GHz - | d__km - Path distance, in km - | p - Percentage of locations, in % - | - | Output: L_ctt__db - Additional loss (clutter loss), in dB - | - | Returns: error - Error code - | - *===========================================================================*/ -int TerrestrialStatisticalModel(double f__ghz, double d__km, double p, double* L_ctt__db) -{ - int rtn = Section3p2_InputValidation(f__ghz, d__km, p); +/** @file TerrestrialStatisticalModel.cpp + * Implements the model from ITU-R P.2108 Section 3.2. + */ +#include "P2108.h" + +#include // for std::fmin, std::log10, std::pow, std::sqrt + +namespace ITS { +namespace ITU { +namespace PSeries { +namespace P2108 { + +/******************************************************************************* + * Statistical clutter loss model for terrestrial paths as described in + * Section 3.2. + * + * This model can be applied for urban and suburban clutter loss modeling. + * + * Frequency range: @f$ 0.5 \leq f \leq 67 @f$ (GHz)\n + * Path distance range: @f$ 0.25 \leq d @f$ (km) (must be @f$ \geq 1 @f$ to apply + * the correction at both ends of the path)\n + * Percentage locations range: @f$0 < p < 100 @f$ (%) + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] d__km Path distance, in km + * @param[in] p Percentage of locations, in % + * @param[out] L_ctt__db Additional loss (clutter loss), in dB + * @return Return code + ******************************************************************************/ +ReturnCode TerrestrialStatisticalModel( + const double f__ghz, const double d__km, const double p, double &L_ctt__db +) { + const ReturnCode rtn = Section3p2_InputValidation(f__ghz, d__km, p); if (rtn != SUCCESS) return rtn; // compute clutter loss at 2 km - double L_ctt_2km__db = TerrestrialStatisticalModelHelper(f__ghz, 2, p); + const double L_ctt_2km__db + = TerrestrialStatisticalModelHelper(f__ghz, 2, p); // compute clutter loss at requested distance - double L_ctt_d__db = TerrestrialStatisticalModelHelper(f__ghz, d__km, p); + const double L_ctt_d__db + = TerrestrialStatisticalModelHelper(f__ghz, d__km, p); // "clutter loss must not exceed a maximum value given by [Equation 6]" - *L_ctt__db = fmin(L_ctt_2km__db, L_ctt_d__db); + L_ctt__db = std::fmin(L_ctt_2km__db, L_ctt_d__db); return SUCCESS; } -/*============================================================================= - | - | Description: Compute the clutter loss - | - | Input: f__ghz - Frequency, in GHz - | d__km - Path distance, in km - | p - Percentage of locations, in % - | - | Returns: L_ctt__db - Clutter loss, in dB - | - *===========================================================================*/ -double TerrestrialStatisticalModelHelper(double f__ghz, double d__km, double p) -{ +/******************************************************************************* + * Compute the clutter loss + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] d__km Path distance, in km + * @param[in] p Percentage of locations, in % + * @return Clutter loss, in dB + ******************************************************************************/ +double TerrestrialStatisticalModelHelper( + const double f__ghz, const double d__km, const double p +) { // Equations 4a and 4b - double sigma_l__db = 4; - double L_l__db = -2 * log10(pow(10, -5 * log10(f__ghz) - 12.5) + pow(10, -16.5)); + constexpr double sigma_l__db = 4; + const double term1 = std::pow(10, -5 * std::log10(f__ghz) - 12.5); + const double L_l__db = -2 * std::log10(term1 + std::pow(10, -16.5)); // Equations 5a and 5b - double sigma_s__db = 6; - double L_s__db = 32.98 + 23.9 * log10(d__km) + 3 * log10(f__ghz); + constexpr double sigma_s__db = 6; + const double L_s__db + = 32.98 + 23.9 * std::log10(d__km) + 3 * std::log10(f__ghz); // Equation 3b - double numerator = pow(sigma_l__db, 2) * pow(10, -0.2 * L_l__db) + pow(sigma_s__db, 2) * pow(10, -0.2 * L_s__db); - double denominator = pow(10, -0.2 * L_l__db) + pow(10, -0.2 * L_s__db); - double sigma_cb__db = sqrt(numerator / denominator); + const double numerator + = std::pow(sigma_l__db, 2) * std::pow(10, -0.2 * L_l__db) + + std::pow(sigma_s__db, 2) * std::pow(10, -0.2 * L_s__db); + const double denominator + = std::pow(10, -0.2 * L_l__db) + std::pow(10, -0.2 * L_s__db); + const double sigma_cb__db = std::sqrt(numerator / denominator); // Equation 3a - double L_ctt__db = -5 * log10(pow(10, -0.2 * L_l__db) + pow(10, -0.2 * L_s__db)) + const double term2 = std::pow(10, -0.2 * L_l__db); + const double L_ctt__db + = -5 * std::log10(term2 + std::pow(10, -0.2 * L_s__db)) - sigma_cb__db * InverseComplementaryCumulativeDistribution(p / 100); return L_ctt__db; } -/*============================================================================= - | - | Description: Input validation for the statistical clutter loss model - | for terrestrial paths (Section 3.2). - | - | Input: f__ghz - Frequency, in GHz - | d__km - Path distance, in km - | p - Percentage of locations, in % - | - | Returns: error code or SUCCESS - | - *===========================================================================*/ -int Section3p2_InputValidation(double f__ghz, double d__km, double p) -{ +/******************************************************************************* + * Input validation for the statistical clutter loss model for terrestrial paths + * (Section 3.2). + * + * @param[in] f__ghz Frequency, in GHz + * @param[in] d__km Path distance, in km + * @param[in] p Percentage of locations, in % + * @return Return code + ******************************************************************************/ +ReturnCode Section3p2_InputValidation( + const double f__ghz, const double d__km, const double p +) { if (f__ghz < 0.5 || f__ghz > 67) return ERROR32__FREQUENCY; @@ -91,4 +108,9 @@ int Section3p2_InputValidation(double f__ghz, double d__km, double p) return ERROR32__PERCENTAGE; return SUCCESS; -} \ No newline at end of file +} + +} // namespace P2108 +} // namespace PSeries +} // namespace ITU +} // namespace ITS \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..accb6fd --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +############################################ +## CONFIGURE UNIT TESTS +############################################ +set(TEST_NAME "${LIB_NAME}Test") +proplib_message("Configuring library tests ${TEST_NAME}") + +add_executable( + ${TEST_NAME} + "TestAeronauticalStatisticalModel.cpp" + "TestHeightGainTerminalCorrectionModel.cpp" + "TestInverseComplementaryCumulativeDistribution.cpp" + "TestTerrestrialStatisticalModel.cpp" + "TestUtils.cpp" + "TestUtils.h" +) + +# Set PropLib compiler option defaults +configure_proplib_target(${TEST_NAME}) + +########################################### +## SET UP AND DISCOVER TESTS +########################################### +include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) +target_link_libraries(${TEST_NAME} ${LIB_NAME} GTest::gtest_main) +include(GoogleTest) +gtest_discover_tests(${TEST_NAME}) + +proplib_message("Done configuring library tests ${TEST_NAME}") \ No newline at end of file diff --git a/tests/TestAeronauticalStatisticalModel.cpp b/tests/TestAeronauticalStatisticalModel.cpp new file mode 100644 index 0000000..a9065a7 --- /dev/null +++ b/tests/TestAeronauticalStatisticalModel.cpp @@ -0,0 +1,88 @@ +#include "TestUtils.h" + +#include // GoogleTest +#include // for std::numeric_limits +#include // for std::vector + +// Test fixture for the unit tests +class AeronauticalStatisticalModelTest: public ::testing::Test { + protected: + void SetUp() override { + // Load test data from CSV + testData = readAeronauticalStatisticalModelTestData( + "AeronauticalStatisticalModelTestData.csv" + ); + } + + // Vector to hold test data + std::vector testData; +}; + +// Test case to verify the AeronauticalStatisticalModel function +TEST_F(AeronauticalStatisticalModelTest, TestAeronauticalStatisticalModel) { + // Ensure test data was loaded + EXPECT_NE(static_cast(testData.size()), 0); + double L_ces__db; + ReturnCode rtn; + for (const auto &data : testData) { + rtn = AeronauticalStatisticalModel( + data.f__ghz, data.theta__deg, data.p, L_ces__db + ); + EXPECT_EQ(rtn, data.rtn); + if (rtn == SUCCESS) { + EXPECT_NEAR(L_ces__db, data.L_ces__db, ABSTOL__DB); + } + } +} + +TEST(Section3p3_InputValidationTest, Section3p3_FrequencyInvalid) { + EXPECT_EQ( + Section3p3_InputValidation(9, 1, 1), ERROR33__FREQUENCY + ); // Frequency too low + EXPECT_EQ( + Section3p3_InputValidation(101, 1, 1), ERROR33__FREQUENCY + ); // Frequency too high +} + +TEST(Section3p3_InputValidationTest, Section3p3_ThetaInvalid) { + EXPECT_EQ( + Section3p3_InputValidation(20, -1, 1), ERROR33__THETA + ); // Theta negative + EXPECT_EQ( + Section3p3_InputValidation(20, 91, 1), ERROR33__THETA + ); // Theta too large +} + +TEST(Section3p3_InputValidationTest, Section3p3_PercentageInvalid) { + EXPECT_EQ( + Section3p3_InputValidation(20, 1, -1), ERROR33__PERCENTAGE + ); // p < 0 + EXPECT_EQ( + Section3p3_InputValidation(20, 1, 0), ERROR33__PERCENTAGE + ); // p = 0 + EXPECT_EQ( + Section3p3_InputValidation(20, 1, 100), ERROR33__PERCENTAGE + ); // p = 100 + EXPECT_EQ( + Section3p3_InputValidation(20, 1, 101), ERROR33__PERCENTAGE + ); // p > 100 +} + +TEST(Section3p3_InputValidationTest, Section3p3_ValidInputs) { + // Test all edge values plus in-between values + EXPECT_EQ(Section3p3_InputValidation(10, 45, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 45, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(100, 45, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 0, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 45, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 90, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 45, 0.1), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 45, 50), SUCCESS); + EXPECT_EQ(Section3p3_InputValidation(50, 45, 99.9), SUCCESS); +} + +TEST(Section3p3_Cot, Section3p3_TestCot) { + EXPECT_DOUBLE_EQ(cot(0), std::numeric_limits::infinity()); + EXPECT_NEAR(cot(PI / 2), 0.0, 1e-15); + EXPECT_NEAR(cot(PI / 4), 1.0, 1e-15); +} diff --git a/tests/TestHeightGainTerminalCorrectionModel.cpp b/tests/TestHeightGainTerminalCorrectionModel.cpp new file mode 100644 index 0000000..5a2994f --- /dev/null +++ b/tests/TestHeightGainTerminalCorrectionModel.cpp @@ -0,0 +1,124 @@ +#include "TestUtils.h" + +#include // for std::log10 +#include // GoogleTest +#include // for std::numeric_limits +#include // for std::vector + +// Test fixture for the unit tests +class HeightGainTerminalCorrectionModelTest: public ::testing::Test { + protected: + void SetUp() override { + // Load test data from CSV + testData = readHeightGainTerminalCorrectionModelTestData( + "HeightGainTerminalCorrectionModelTestData.csv" + ); + } + + // Vector to hold test data + std::vector testData; +}; + +// Test case to verify the HeightGainTerminalCorrectionModel function +TEST_F( + HeightGainTerminalCorrectionModelTest, TestHeightGainTerminalCorrectionModel +) { + // Ensure test data was loaded + EXPECT_NE(static_cast(testData.size()), 0); + double A_h__db; + ReturnCode rtn; + for (const auto &data : testData) { + rtn = HeightGainTerminalCorrectionModel( + data.f__ghz, + data.h__meter, + data.w_s__meter, + data.R__meter, + data.clutter_type, + A_h__db + ); + EXPECT_EQ(rtn, data.rtn); + if (rtn == SUCCESS) { + EXPECT_NEAR(A_h__db, data.A_h__db, ABSTOL__DB); + } + } +} + +TEST(Section3p1_InputValidationTest, Section3p1_FrequencyInvalid) { + EXPECT_EQ( + Section3p1_InputValidation(0.02, 1, 1, 1), ERROR31__FREQUENCY + ); // Frequency too low + EXPECT_EQ( + Section3p1_InputValidation(3.01, 1, 1, 1), ERROR31__FREQUENCY + ); // Frequency too high +} + +TEST(Section3p1_InputValidationTest, Section3p1_AntennaHeightInvalid) { + EXPECT_EQ( + Section3p1_InputValidation(1, -1, 1, 1), ERROR31__ANTENNA_HEIGHT + ); // Antenna height negative + EXPECT_EQ( + Section3p1_InputValidation(1, 0, 1, 1), ERROR31__ANTENNA_HEIGHT + ); // Antenna height == 0 +} + +TEST(Section3p1_InputValidationTest, Section3p1_StreetWidthInvalid) { + EXPECT_EQ( + Section3p1_InputValidation(1, 1, -1, 1), ERROR31__STREET_WIDTH + ); // Street width negative + EXPECT_EQ( + Section3p1_InputValidation(1, 1, 0, 1), ERROR31__STREET_WIDTH + ); // Street width == 0 +} + +TEST(Section3p1_InputValidationTest, Section3p1_ClutterHeightInvalid) { + EXPECT_EQ( + Section3p1_InputValidation(1, 1, 1, -1), ERROR31__CLUTTER_HEIGHT + ); // Clutter height negative + EXPECT_EQ( + Section3p1_InputValidation(1, 1, 1, 0), ERROR31__CLUTTER_HEIGHT + ); // Clutter height == 0 +} + +TEST(Section3p1_InputValidationTest, Section3p1_ValidInputs) { + // Test edge values plus in-between value for frequency + EXPECT_EQ(Section3p1_InputValidation(0.03, 1, 1, 1), SUCCESS); + EXPECT_EQ(Section3p1_InputValidation(1, 1, 1, 1), SUCCESS); + EXPECT_EQ(Section3p1_InputValidation(3, 1, 1, 1), SUCCESS); + // Test large positive values for other parameters + EXPECT_EQ( + Section3p1_InputValidation(1, std::numeric_limits::max(), 1, 1), + SUCCESS + ); + EXPECT_EQ( + Section3p1_InputValidation(1, 1, std::numeric_limits::max(), 1), + SUCCESS + ); + EXPECT_EQ( + Section3p1_InputValidation(1, 1, 1, std::numeric_limits::max()), + SUCCESS + ); +} + +TEST(Section3p1_Equation_2aTest, Section3p1_NuLessThanLimit) { + // Eq. 2a should return -6.03 for nu <= -0.78. + // In practice, the parameter nu is never negative. + EXPECT_EQ(Equation_2a(-0.78), -6.03); + EXPECT_EQ(Equation_2a(-1), -6.03); +} + +TEST(Section3p1_Equation_2aTest, Section3p1_NuAboveLimit) { + // Test a few values with separately-computed expected results for this + // equation + EXPECT_NEAR(Equation_2a(-0.77), -5.96059383728336638563, 1e-13); + EXPECT_NEAR(Equation_2a(0), 0.00285220856360571492, 1e-13); + EXPECT_NEAR(Equation_2a(1.5), 10.75438646420360661479, 1e-13); +} + +TEST(Section3p1_Equation_2bTest, TestSection3p1_Equation_2b) { + // Test a few values with separately-computed expected results for this + // equation + EXPECT_DOUBLE_EQ(Equation_2b(1, 10, 5), -std::log10(2)); + EXPECT_DOUBLE_EQ(Equation_2b(5, 1, 2), -5 * std::log10(0.5)); + EXPECT_DOUBLE_EQ(Equation_2b(0, 100, 100), 0); + EXPECT_DOUBLE_EQ(Equation_2b(-1, 10, 1), 1); +} diff --git a/tests/TestInverseComplementaryCumulativeDistribution.cpp b/tests/TestInverseComplementaryCumulativeDistribution.cpp new file mode 100644 index 0000000..fdd1979 --- /dev/null +++ b/tests/TestInverseComplementaryCumulativeDistribution.cpp @@ -0,0 +1,34 @@ +#include "TestUtils.h" + +#include // GoogleTest +#include // for std::out_of_range + +TEST(InverseCCDFTest, TestInverseCCDF) { + EXPECT_DOUBLE_EQ( + InverseComplementaryCumulativeDistribution(0.01), 2.3267853325589658 + ); + EXPECT_DOUBLE_EQ( + InverseComplementaryCumulativeDistribution(0.49), 0.024998347218995187 + ); + EXPECT_DOUBLE_EQ( + InverseComplementaryCumulativeDistribution(0.51), -0.024998347218995187 + ); + EXPECT_DOUBLE_EQ( + InverseComplementaryCumulativeDistribution(0.99), -2.3267853325589658 + ); +} + +TEST(InverseCCDFTest, TestInverseCCDFInputInvalid) { + EXPECT_THROW( + InverseComplementaryCumulativeDistribution(-1.0), std::out_of_range + ); + EXPECT_THROW( + InverseComplementaryCumulativeDistribution(0.0), std::out_of_range + ); + EXPECT_THROW( + InverseComplementaryCumulativeDistribution(1.0), std::out_of_range + ); + EXPECT_THROW( + InverseComplementaryCumulativeDistribution(1.1), std::out_of_range + ); +} \ No newline at end of file diff --git a/tests/TestTerrestrialStatisticalModel.cpp b/tests/TestTerrestrialStatisticalModel.cpp new file mode 100644 index 0000000..48b9048 --- /dev/null +++ b/tests/TestTerrestrialStatisticalModel.cpp @@ -0,0 +1,83 @@ +#include "TestUtils.h" + +#include // GoogleTest +#include // for std::vector + +// Test fixture for the unit tests +class TerrestrialStatisticalModelTest: public ::testing::Test { + protected: + void SetUp() override { + // Load test data from CSV + testData = readTerrestrialStatisticalModelTestData( + "TerrestrialStatisticalModelTestData.csv" + ); + } + + // Vector to hold test data + std::vector testData; +}; + +// Test case to verify the TerrestrialStatisticalModel function +TEST_F(TerrestrialStatisticalModelTest, TestTerrestrialStatisticalModel) { + // Ensure test data was loaded + EXPECT_NE(static_cast(testData.size()), 0); + double L_ctt__db; + ReturnCode rtn; + for (const auto &data : testData) { + rtn = TerrestrialStatisticalModel( + data.f__ghz, data.d__km, data.p, L_ctt__db + ); + EXPECT_EQ(rtn, data.rtn); + if (rtn == SUCCESS) { + EXPECT_NEAR(L_ctt__db, data.L_ctt__db, ABSTOL__DB); + } + } +} + +TEST(Section3p2_InputValidationTest, Section3p2_FrequencyInvalid) { + EXPECT_EQ( + Section3p2_InputValidation(0.49, 1, 1), ERROR32__FREQUENCY + ); // Frequency too low + EXPECT_EQ( + Section3p2_InputValidation(67.01, 1, 1), ERROR32__FREQUENCY + ); // Frequency too high +} + +TEST(Section3p2_InputValidationTest, Section3p2_DistanceInvalid) { + EXPECT_EQ( + Section3p2_InputValidation(20, 0.249, 1), ERROR32__DISTANCE + ); // Distance too small + EXPECT_EQ( + Section3p2_InputValidation(20, 0, 1), ERROR32__DISTANCE + ); // Distance is zero + EXPECT_EQ( + Section3p2_InputValidation(20, -1, 1), ERROR32__DISTANCE + ); // Distance is negative +} + +TEST(Section3p2_InputValidationTest, Section3p2_PercentageInvalid) { + EXPECT_EQ( + Section3p2_InputValidation(20, 1, -1), ERROR32__PERCENTAGE + ); // p < 0 + EXPECT_EQ( + Section3p2_InputValidation(20, 1, 0), ERROR32__PERCENTAGE + ); // p = 0 + EXPECT_EQ( + Section3p2_InputValidation(20, 1, 100), ERROR32__PERCENTAGE + ); // p = 100 + EXPECT_EQ( + Section3p2_InputValidation(20, 1, 101), ERROR32__PERCENTAGE + ); // p > 100 +} + +TEST(Section3p2_InputValidationTest, Section3p2_ValidInputs) { + // Test all edge values plus in-between values + EXPECT_EQ(Section3p2_InputValidation(0.5, 1, 1), SUCCESS); // f = 0.5 + EXPECT_EQ(Section3p2_InputValidation(30, 1, 1), SUCCESS); // f = 30 + EXPECT_EQ(Section3p2_InputValidation(67, 1, 1), SUCCESS); // f = 67 + EXPECT_EQ(Section3p2_InputValidation(30, 0.25, 1), SUCCESS); // d = 0.25 + EXPECT_EQ(Section3p2_InputValidation(30, 100, 1), SUCCESS); // d = 100 + EXPECT_EQ(Section3p2_InputValidation(30, 1, 0.01), SUCCESS); // p = 0.01 + EXPECT_EQ(Section3p2_InputValidation(30, 1, 50), SUCCESS); // p = 50 + EXPECT_EQ(Section3p2_InputValidation(30, 1, 99.99), SUCCESS); // p = 99.99 +} \ No newline at end of file diff --git a/tests/TestUtils.cpp b/tests/TestUtils.cpp new file mode 100644 index 0000000..2adda04 --- /dev/null +++ b/tests/TestUtils.cpp @@ -0,0 +1,108 @@ +/** @file TestUtils.cpp + * Primary implementations for fixtures or common functions used by unit tests. + */ +#include "TestUtils.h" + +#include // for std::ifstream +#include // for std::istringstream +#include // for std::string, std::getline +#include // for std::vector + +/******************************************************************************* + * Append a directory separator ('/' or '\') to a string, based on the + * current operating system. + * + * @param[in, out] str String to which the character will be appended. + *****************************************************************************/ +void AppendDirectorySep(std::string &str) { +#ifdef _WIN32 + str += "\\"; +#else + str += "/"; +#endif +} + +/****************************************************************************** + * Get the full path of the directory containing test data files. + * + * @return The path of the test data directory. + *****************************************************************************/ +std::string GetDataDirectory() { + std::string dataDir(__FILE__); + dataDir.resize(dataDir.find_last_of("/\\")); + dataDir.resize(dataDir.find_last_of("/\\")); + AppendDirectorySep(dataDir); + dataDir += "extern"; + AppendDirectorySep(dataDir); + dataDir + += "test-data"; // Name of data directory as cloned in the `extern` directory + AppendDirectorySep(dataDir); + return dataDir; +} + +std::vector + readAeronauticalStatisticalModelTestData(const std::string &filename) { + std::vector testData; + std::string dataDir = GetDataDirectory(); + std::ifstream file(dataDir + filename); + std::string line; + // struct to store data from a single line of CSV: + AeronauticalStatisticalModelTestData d; + char c; // single-character representing the comma (delimiter) + int rtn_value; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (iss >> d.f__ghz >> c >> d.theta__deg >> c >> d.p >> c >> rtn_value + >> c >> d.L_ces__db) { + d.rtn = static_cast(rtn_value); + testData.push_back(d); + } + } + return testData; +} + +std::vector + readHeightGainTerminalCorrectionModelTestData(const std::string &filename) { + std::vector testData; + std::string dataDir = GetDataDirectory(); + std::ifstream file(dataDir + filename); + std::string line; + // struct to store data from a single line of CSV: + HeightGainTerminalCorrectionModelTestData d; + char c; // single-character representing the comma (delimiter) + int clutter_type_value; + int rtn_value; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (iss >> d.f__ghz >> c >> d.h__meter >> c >> d.w_s__meter >> c + >> d.R__meter >> c >> clutter_type_value >> c >> rtn_value >> c + >> d.A_h__db) { + // Convert integers to enum + d.clutter_type = static_cast(clutter_type_value); + d.rtn = static_cast(rtn_value); + testData.push_back(d); + } + } + return testData; +} + +std::vector + readTerrestrialStatisticalModelTestData(const std::string &filename) { + std::vector testData; + std::string dataDir = GetDataDirectory(); + std::ifstream file(dataDir + filename); + std::string line; + // struct to store data from a single line of CSV: + TerrestrialStatisticalModelTestData d; + char c; // single-character representing the comma (delimiter) + int rtn_value; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (iss >> d.f__ghz >> c >> d.d__km >> c >> d.p >> c >> rtn_value >> c + >> d.L_ctt__db) { + d.rtn = static_cast(rtn_value); + testData.push_back(d); + } + } + return testData; +} diff --git a/tests/TestUtils.h b/tests/TestUtils.h new file mode 100644 index 0000000..dff1194 --- /dev/null +++ b/tests/TestUtils.h @@ -0,0 +1,57 @@ +/** @file TestUtils.h + * Primary header for fixtures or common functions used by unit tests. + */ +#pragma once + +// clang-format off +// GoogleTest must be included first +#include // GoogleTest +// clang-format on + +#include "P2108.h" + +#include // for std::string +#include // for std::vector + +using namespace ITS::ITU::PSeries::P2108; + +// Absolute tolerance for checking model outputs against test data +constexpr double ABSTOL__DB = 0.1; + +void AppendDirectorySep(std::string &str); +std::string GetDataDirectory(); + +struct AeronauticalStatisticalModelTestData { + double f__ghz; + double theta__deg; + double p; + ReturnCode rtn; + double L_ces__db; +}; + +struct HeightGainTerminalCorrectionModelTestData { + double f__ghz; + double h__meter; + double w_s__meter; + double R__meter; + ClutterType clutter_type; + ReturnCode rtn; + double A_h__db; +}; + +struct TerrestrialStatisticalModelTestData { + double f__ghz; + double d__km; + double p; + ReturnCode rtn; + double L_ctt__db; +}; + +std::vector + readAeronauticalStatisticalModelTestData(const std::string &filename); + +std::vector + readHeightGainTerminalCorrectionModelTestData(const std::string &filename); + +std::vector + readTerrestrialStatisticalModelTestData(const std::string &filename); diff --git a/win32/p2108.def b/win32/p2108.def deleted file mode 100644 index 233126e..0000000 --- a/win32/p2108.def +++ /dev/null @@ -1,5 +0,0 @@ -LIBRARY -EXPORTS - AeronauticalStatisticalModel - TerrestrialStatisticalModel - HeightGainTerminalCorrectionModel \ No newline at end of file diff --git a/win32/p2108.rc b/win32/p2108.rc deleted file mode 100644 index 8614fde..0000000 --- a/win32/p2108.rc +++ /dev/null @@ -1,99 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,0,0 - PRODUCTVERSION 1,0,0,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x2L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "The Institute for Telecommunication Sciences" - VALUE "FileDescription", "Recommendation ITU-R P.2108-1" - VALUE "FileVersion", "1.0.0.0" - VALUE "InternalName", "p2108.dll" - VALUE "OriginalFilename", "p2108.dll" - VALUE "ProductName", "Recommendation ITU-R P.2108-1" - VALUE "ProductVersion", "1.0.0.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - diff --git a/win32/p2108.sln b/win32/p2108.sln deleted file mode 100644 index a7341f7..0000000 --- a/win32/p2108.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31424.327 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "p2108", "p2108.vcxproj", "{BC9DF51C-EB65-45B2-8A30-3F38576EAA23}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Debug|x64.ActiveCfg = Debug|x64 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Debug|x64.Build.0 = Debug|x64 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Debug|x86.ActiveCfg = Debug|Win32 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Debug|x86.Build.0 = Debug|Win32 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Release|x64.ActiveCfg = Release|x64 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Release|x64.Build.0 = Release|x64 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Release|x86.ActiveCfg = Release|Win32 - {BC9DF51C-EB65-45B2-8A30-3F38576EAA23}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D02843E9-EDB9-4B5E-9D81-6DE87670C67C} - EndGlobalSection -EndGlobal diff --git a/win32/p2108.vcxproj b/win32/p2108.vcxproj deleted file mode 100644 index 8ff091c..0000000 --- a/win32/p2108.vcxproj +++ /dev/null @@ -1,194 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - 16.0 - Win32Proj - {bc9df51c-eb65-45b2-8a30-3f38576eaa23} - p2108 - 10.0 - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)..\bin\x86\$(Configuration)\ - - - false - $(SolutionDir)..\bin\x86\$(Configuration)\ - - - true - $(SolutionDir)..\bin\x64\$(Configuration)\ - - - false - $(SolutionDir)..\bin\x64\$(Configuration)\ - - - - Level3 - true - WIN32;_DEBUG;P2108_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreadedDebug - StdCall - - - Windows - true - false - p2108.def - - - - - Level3 - true - true - true - WIN32;NDEBUG;P2108_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreaded - StdCall - - - Windows - true - true - true - false - p2108.def - - - - - Level3 - true - _DEBUG;P2108_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreadedDebug - StdCall - - - Windows - true - false - p2108.def - - - - - Level3 - true - true - true - NDEBUG;P2108_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreaded - StdCall - - - Windows - true - true - true - false - p2108.def - - - - - - \ No newline at end of file diff --git a/win32/p2108.vcxproj.filters b/win32/p2108.vcxproj.filters deleted file mode 100644 index 9049f0d..0000000 --- a/win32/p2108.vcxproj.filters +++ /dev/null @@ -1,55 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/win32/resource.h b/win32/resource.h deleted file mode 100644 index dbda343..0000000 --- a/win32/resource.h +++ /dev/null @@ -1,14 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by p2108.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif