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..dee5d49 --- /dev/null +++ b/.github/workflows/ctest.yml @@ -0,0 +1,75 @@ +# 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: release64 + configurePresetAdditionalArgs: "['-DBUILD_DOCS=OFF']" + buildPreset: release64 + testPreset: release64 + + - name: "CMake: Build and Test (32-bit)" + if: matrix.architecture == 'x86' + uses: lukka/run-cmake@v10 + with: + configurePreset: release32 + configurePresetAdditionalArgs: "['-DBUILD_DOCS=OFF']" + buildPreset: release32 + testPreset: release32 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..2bdd0b2 --- /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/LFMF.h + if-no-files-found: error + overwrite: true diff --git a/.gitignore b/.gitignore index 156c510..5fed29b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,6 @@ Thumbs.db *.o -# Folder config file -Desktop.ini - ################# ## Visual Studio ################# @@ -16,16 +13,17 @@ Desktop.ini ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files -**/Debug -**/Release -win32/packages/ **/.vs +**/bin +**/Debug/ +**/Release/ +**/dotnet/packages +**/dotnet/nuget *.tlog *.obj *.log *.lastbuildstate *.manifest -*.cache *.users *.user *.res @@ -41,33 +39,35 @@ win32/packages/ *.sdf *.u2d *.suo -*.tiff -*.txt *.aps +**/obj +**/x64 +**/x86 -################ -# Linux and OSX -############### -\.DS* -*.so -*.d - +######### +## 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 -# Created by https://www.gitignore.io -### Xcode ### -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.xcuserstate +########### +## Doxygen +########### +docs/html +########### +## VS Code +########### +.vscode \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9e8e4b4 --- /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/test-data"] + path = extern/test-data + url = https://github.com/NTIA/LFMF-test-data diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..de77a60 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,57 @@ +{ + "upload_type": "software", + "publication_date": "2020-09-07", + "title": "Low Frequency / Medium Frequency Propagation Model", + "creators": [ + { + "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" + } + ], + "description": "This repository contains the NTIA/ITS C++ implementation of Low Frequency / Medium Frequency (LF/MF) Propagation Model. The LF/MF model predicts basic transmission loss in the frequency range 0.01 - 30 MHz for propagation paths over a smooth Earth and antenna heights less than 50 meters.", + "keywords": [ + "LFMF", + "propagation", + "ITS", + "NTIA" + ], + "related_identifiers": [ + { + "identifier": "https://github.com/NTIA/LFMF-dotnet", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/LFMF-matlab", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/LFMF-python", + "relation": "isSupplementedBy", + "resource_type": "software" + }, + { + "identifier": "https://github.com/NTIA/LFMF-test-data", + "relation": "isSupplementedBy", + "resource_type": "dataset" + }, + { + "identifier": "https://ntia.github.io/LFMF/", + "relation": "isDocumentedBy", + "resource_type": "softwaredocumentation" + }, + { + "identifier": "https://ntia.github.io/propagation-library-wiki/models/LFMF/", + "relation": "isDocumentedBy", + "resource_type": "softwaredocumentation" + } + ], + "version": "1.1" +} \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..d8484df --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,35 @@ +cff-version: 1.2.0 +title: >- + Low Frequency / Medium Frequency (LF/MF) Propagation Model +message: Please cite this software using these metadata. +type: software +authors: + - family-names: Kozma + given-names: William + name-suffix: Jr. + affiliation: >- + U.S. Department of Commerce, National + Telecommunications and Information Administration, + Institute for Telecommunication Sciences + orcid: 'https://orcid.org/0000-0002-7417-4009' + email: wkozma@ntia.gov + - 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/LFMF' +url: 'https://ntia.github.io/propagation-library-wiki/models/LFMF' +keywords: + - its + - propagation + - lfmf +version: 1.1 +date-released: '2025-01-21' diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ba7f55b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,188 @@ +############################################################ +## 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 "LFMF") # Name of library/target +set(LIB_NAMESPACE "ITS.Propagation") # Namespace for the named library +project( + "${LIB_NAMESPACE}.${LIB_NAME}" + VERSION 1.1 + DESCRIPTION "Low Frequency / Medium Frequency (LF/MF) Propagation Model" + HOMEPAGE_URL "https://ntia.github.io/propagation-library-wiki/models/LFMF" + 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}:$:/O2;/DNDEBUG;/MT>>>" + # For MSVC compiler in Debug configurations + "$<${msvc_cxx}:$:/Od;/Zi;/MTd>>>" + ) +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 () + + message(STATUS "STATUS: ${PROJECT_NAME} VERSION_MAJOR is " ${PROJECT_VERSION_MAJOR} ", VERSION_MINOR is " ${PROJECT_VERSION_MINOR}) +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..623bd5c --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,209 @@ +{ + "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": "OFF", + "RUN_DRIVER_TESTS": "OFF" + }, + "environment": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "name": "debug64", + "displayName": "Debug, 64-Bit", + "description": "Build library and tests with debug options. Skip building driver, driver tests, and 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 and tests with debug options. Skip building driver, driver tests, and 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 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 and tests with release options, and build docs", + "configurePreset": "release64" + }, + { + "name": "debug32", + "inherits": "proplib-build-debug-base", + "displayName": "Build Debug, 32-Bit", + "description": "Build library 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 and tests with release options, and build docs", + "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", + "configuration": "Debug" + }, + { + "name": "proplib-test-release-base", + "hidden": true, + "inherits": "proplib-test-base", + "description": "Base 'Release' test preset for ITS PropLib libraries", + "configuration": "Release" + }, + { + "name": "debug64", + "inherits": "proplib-test-debug-base", + "displayName": "Test Debug, 64-bit", + "description": "Build library 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 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 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 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/LFMF_Examples.csv b/LFMF_Examples.csv deleted file mode 100644 index 0d036f0..0000000 --- a/LFMF_Examples.csv +++ /dev/null @@ -1,6 +0,0 @@ -h_tx__meter,h_rx__meter,f__mhz,P_tx__watt,N_s,d__km,epsilon,sigma,pol,rtn,A_btl__db,E_dBuVm,P_rx__dbm,method -0,0,0.01,1000,301,1000,15,0.005,0,0,184.5,-82.5,-114.9,1 -0,0,1,5000,301,5000,15,0.005,1,0,536.5,-387.5,-459.9,1 -5.5,1.5,10,500,315,15,15,0.005,0,0,151.3,7.6,-84.8,0 -1,1,0.45,1000,315,3000,15,0.005,1,0,264.3,-129.3,-194.8,1 -10,10,30,10000,315,5000,15,0.005,0,0,1574.9,-1393.3,-1495.3,1 diff --git a/LICENSE.md b/LICENSE.md index 12746a1..add8ca7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,14 +1,34 @@ -SOFTWARE DISCLAIMER / RELEASE +# SOFTWARE DISCLAIMER / RELEASE -This software was developed by employees of the National Telecommunications and Information Administration (NTIA), an agency of the Federal Government and is provided to you as a public service. Pursuant to Title 15 United States Code Section 105, works of NTIA employees are not subject to copyright protection within the United States. +This software was developed by employees of the National Telecommunications and Information +Administration (NTIA), an agency of the Federal Government and is provided to you +as a public service. Pursuant to Title 15 United States Code Section 105, works +of NTIA employees are not subject to copyright protection within the United States. -The software is provided by NTIA “AS IS.” NTIA MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND DATA ACCURACY. NTIA does not warrant or make any representations regarding the use of the software or the results thereof, including but not limited to the correctness, accuracy, reliability or usefulness of the software. +The software is provided by NTIA “AS IS.” NTIA MAKES NO WARRANTY OF ANY KIND, EXPRESS, +IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND DATA ACCURACY. NTIA does +not warrant or make any representations regarding the use of the software or the +results thereof, including but not limited to the correctness, accuracy, reliability +or usefulness of the software. -To the extent that NTIA holds rights in countries other than the United States, you are hereby granted the non-exclusive irrevocable and unconditional right to print, publish, prepare derivative works and distribute the NTIA software, in any medium, or authorize others to do so on your behalf, on a royalty-free basis throughout the World. +To the extent that NTIA holds rights in countries other than the United States, +you are hereby granted the non-exclusive irrevocable and unconditional right to +print, publish, prepare derivative works and distribute the NTIA software, in any +medium, or authorize others to do so on your behalf, on a royalty-free basis throughout +the World. -You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. +You may improve, modify, and create derivative works of the software or any portion +of the software, and you may copy and distribute such modifications or works. Modified +works should carry a notice stating that you changed the software and should note +the date and nature of any such change. -You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. - -Please provide appropriate acknowledgments of NTIA’s creation of the software in any copies or derivative works of this software. +You are solely responsible for determining the appropriateness of using and distributing +the software and you assume all risks associated with its use, including but not +limited to the risks and costs of program errors, compliance with applicable laws, +damage to or loss of data, programs or equipment, and the unavailability or interruption +of operation. This software is not intended to be used in any situation where a failure +could cause risk of injury or damage to property. +Please provide appropriate acknowledgments of NTIA’s creation of the software in +any copies or derivative works of this software. diff --git a/README.md b/README.md index 3a87558..7029679 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,120 @@ -# Low Frequency / Medium Frequency (LF/MF) Propagation Model +# Low Frequency / Medium Frequency (LF/MF) Propagation Model # + +[![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/LFMF/ctest.yml?branch=main&logo=cmake&label=Build%2FTests&labelColor=162E51 +[gh-actions-test-link]: https://github.com/NTIA/LFMF/actions/workflows/ctest.yml +[gh-actions-docs-badge]: https://img.shields.io/github/actions/workflow/status/NTIA/LFMF/doxygen.yml?branch=main&logo=c%2B%2B&label=Docs&labelColor=162E51 +[gh-pages-docs-link]: https://ntia.github.io/LFMF +[gh-releases-badge]: https://img.shields.io/github/v/release/NTIA/LFMF?logo=github&label=Release&labelColor=162E51&color=D63E04 +[gh-releases-link]: https://github.com/NTIA/LFMF/releases +[gh-issues-badge]: https://img.shields.io/github/issues/NTIA/LFMF?logo=github&label=Issues&labelColor=162E51 +[gh-issues-link]: https://github.com/NTIA/LFMF/issues +[doi-badge]: https://zenodo.org/badge/288586266.svg +[doi-link]: https://zenodo.org/badge/latestdoi/288586266 + +This repository contains the NTIA/ITS C++ implementation of +Low Frequency / Medium Frequency (LF/MF) Propagation Model. The LF/MF model +predicts basic transmission loss in the frequency range 0.01 - 30 MHz for propagation +paths over a smooth Earth and antenna heights less than 50 meters. + +Additional bindings to the shared library built from this repository are provided +for .NET, MATLAB®, and Python® in the following repositories: + +- [NTIA/LFMF-dotnet](https://github.com/NTIA/LFMF-dotnet) +- [NTIA/LFMF-matlab](https://github.com/NTIA/LFMF-matlab) +- [NTIA/LFMF-python](https://github.com/NTIA/LFMF-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/LFMF/). +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/LFMF/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/LFMF), and a guide for contributors +[here](CONTRIBUTING.md). -This code repository contains an the NTIA/ITS implementation of the Low Frequency / Medium Frequency (LF/MF) Propagation Model. LF/MF predicts basic transmission loss in the frequency range 0.01 - 30 MHz for propagation paths over a smooth Earth and antenna heights less than 50 meters. +## Configure and Build ## -## Inputs ## +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. -| Variable | Type | Units | Limits | Description | -|-------------------|--------|-------|--------------|--------------| -| `h_tx__meter` | double | meter | 0 <= `h_tx__meter` <= 50 | Height of the transmitter | -| `h_rx__meter` | double | meter | 0 <= `h_rx__meter` <= 50 | Height of the receiver | -| `f__mhz` | double | MHz | 0.01 <= `f__mhz` <= 30 | Frequency | -| `P_tx__watt` | double | Watt | 0 < `P_tx__watt` | Transmitter power | -| `N_s` | double | N-Units | 250 <= `N_s` <= 400 | Surface refractivity | -| `d__km` | double | km | 0 < `d__km` | Path distance | -| `epsilon` | double | | 1 <= `epsilon` | Relative permittivity | -| `sigma` | double | S/m | 0 < `sigma` | Conductivity | -| `pol` | int | | | Polarization
  • 0 = Horizontal
  • 1 = Vertical
| +```cmd +# From this repository's root directory, try one of the following command pairs: -## Outputs ## +# "Release" configurations compile the library and driver, build docs, and configure tests: +cmake --preset release64 +cmake --build --preset release64 -Outputs to LFMF are contained within a defined `Result` structure. +# "Debug" configurations skip building the docs, driver, and driver tests: +cmake --preset debug64 +cmake --build --preset debug64 -| Variable | Type | Units | Description | -|---------------|--------|-------|-------------| -| `A_btl__db` | double | dB | Basic transmission loss | -| `E_dBuVm` | double | dB(uV/m) | Electrice field strength | -| `P_rx__dbm` | double | dBm | Received power | -| `method` | int | | Solution method
  • 0 = Flat earth with curve correction
  • 1 = Residue series
| +# Additional options can override presets: +cmake --preset debug64 -DBUILD_DRIVER=ON -## Return Codes ## +# "DocsOnly" configurations only build the docs: +cmake --preset docsOnly +cmake --build --preset docsOnly +``` -Possible return codes, including the corresponding defined constant name as defined in `LFMF.h`: +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: -| Value | Const Name | Description | -| ------|----------------------------------|--------------| -| 0 | `SUCCESS` | Successful execution | -| 1000 | `ERROR__TX_TERMINAL_HEIGHT` | TX terminal height is out of range | -| 1001 | `ERROR__RX_TERMINAL_HEIGHT` | RX terminal height is out of range | -| 1002 | `ERROR__FREQUENCY` | Frequency is out of range | -| 1003 | `ERROR__TX_POWER` | Transmit power is out of range | -| 1004 | `ERROR__SURFACE_REFRACTIVITY` | Surface refractivity is out of range | -| 1005 | `ERROR__PATH_DISTANCE` | Path distance is out of range | -| 1006 | `ERROR__EPSILON` | Epsilon is out of range | -| 1007 | `ERROR__SIGMA` | Sigma is out of range | -| 1008 | `ERROR__POLARIZATION` | Invalid value for polarization | +```cmd +# From this repository's root directory +git submodule init +git submodule update +``` -## Example Values ## +## Running Tests ## -A set of example inputs and outputs are provided for testing purposes. This is not a comprehensive validation test set. The test set can be found in [LFMF_Examples.csv](LFMF_Examples.csv). +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: -## Notes on Code Style ## +```cmd +ctest --preset release64 +``` -* 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_tx__meter. -* Variables are named to match their corresponding mathematical variables from their publication text. -* Wherever possible, equation numbers are provided. +## References ## -## Configure and Build ## +- [ITS Propagation Library Wiki](https://ntia.github.io/propagation-library-wiki) +- [LFMF Wiki Page](https://ntia.github.io/propagation-library-wiki/models/LFMF) +- [`ITS.Propagation.LFMF` C++ API Reference](https://ntia.github.io/LFMF) -### 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 2022 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 LFMF 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. +"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 ## -* Bremmer, H. "Terrestrial Radio Waves" _Elsevier_, 1949. -* DeMinco, N. "Medium Frequency Propagation Prediction Techniques and Antenna Modeling for Intelligent Transportation Systems (ITS) Broadcast Applications", [_NTIA Report 99-368_](https://www.its.bldrdoc.gov/publications/2399.aspx), August 1999 -* DeMinco, N. "Ground-wave Analysis Model For MF Broadcast System", [_NTIA Report 86-203_](https://www.its.bldrdoc.gov/publications/2226.aspx), September 1986 -* Sommerfeld, A. "The propagation of waves in wireless telegraphy", _Ann. Phys._, 1909, 28, p.665 -* Wait, J. "Radiation From a Vertical Antenna Over a Curved Stratified Ground", _Journal of Research of the National Bureau of Standards_. Vol 56, No. 4, April 1956. Research Paper 2671 +For technical questions, contact . -## Contact ## +## Disclaimer ## -For questions, contact Nick DeMinco, NDeminco@ntia.gov +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/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..d4d63a1 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,26 @@ +########################################### +## 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") + +message(STATUS "STATUS: Start building driver: " ${DRIVER_NAME}) + +########################################### +## 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..089dc32 --- /dev/null +++ b/app/include/Driver.h @@ -0,0 +1,49 @@ +/** @file Driver.h + * Interface header for this driver executable + */ +#pragma once + + +#include "CommaSeparatedIterator.h" +#include "LFMF.h" +#include "ReturnCodes.h" +#include "Structs.h" + +#include // for std::left, std::setw +#include // for std::cout +#include // for std::endl, std::ostream +#include // for std::string + +///////////////////////////// +// Macros + +/** Shortcut for concise print-to-file statements in driver */ +#define PRINT << std::endl << std::left << std::setw(30) << +/** Shortcut for setting fixed whitespace padding in driver file output */ +#define SETW13 << std::setw(13) << + +////////////////////////////// +// Library Namespace +using namespace ITS::Propagation::LFMF; + +///////////////////////////// +// Functions +void Help(std::ostream &os = std::cout); +DrvrReturnCode ParseArguments(int argc, char **argv, DrvrParams ¶ms); +DrvrReturnCode ValidateInputs(const DrvrParams ¶ms); + +// 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); + +// LFMF Model +ReturnCode CallLFMFModel(LFMFParams &lfmf_params, Result &result); +DrvrReturnCode + ParseLFMFInputFile(const std::string &in_file, LFMFParams &lfmf_params); +void WriteLFMFInputs(std::ofstream &fp, const LFMFParams ¶ms); +void WriteLFMFOutputs(std::ofstream &fp, const Result &result); diff --git a/app/include/ReturnCodes.h b/app/include/ReturnCodes.h new file mode 100644 index 0000000..b321b50 --- /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_TX_TERMINAL_HEIGHT, /**< Failed to parse TX terminal height value */ + DRVRERR__PARSE_RX_TERMINAL_HEIGHT, /**< Failed to parse RX terminal height value */ + DRVRERR__PARSE_FREQUENCY, /**< Failed to parse frequency value */ + DRVRERR__PARSE_TX_POWER, /**< Failed to parse transmit power value */ + DRVRERR__PARSE_SURFACE_REFRACTIVITY, /**< Failed to parse surface refractivity value */ + DRVRERR__PARSE_PATH_DISTANCE, /**< Failed to parse path distance value */ + DRVRERR__PARSE_EPSILON, /**< Failed to parse epsilon value */ + DRVRERR__PARSE_SIGMA, /**< Failed to parse sigma value */ + DRVRERR__PARSE_POLARIZATION, /**< Failed to parse polarization value */ + + // Validation Errors + DRVRERR__VALIDATION_IN_FILE = 192, /**< Input file not specified */ + DRVRERR__VALIDATION_OUT_FILE, /**< Output file 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..d29936e --- /dev/null +++ b/app/include/Structs.h @@ -0,0 +1,43 @@ +/** @file Structs.h + * Contains data structures and type macros used by this software +*/ +#pragma once + +#include // for std::string + +///////////////////////////// +// Data Structures + +/** Parameters provided to the command line driver */ +struct DrvrParams { + std::string in_file = ""; /**< Input file */ + std::string out_file = ""; /**< Output file */ +}; + +/** Input parameters for the LFMF Model */ +// clang-format off +struct LFMFParams { + double h_tx__meter; /**< Height of the transmitter, in meters */ + double h_rx__meter; /**< Height of the receiver, in meters */ + double f__mhz; /**< Frequency, in MHz */ + double P_tx__watt; /**< Transmitter power, in watts */ + double N_s; /**< Surface refractivity, in N-Units */ + double d__km; /**< Path distance, in km */ + double epsilon; /**< Relative permittivity */ + double sigma; /**< Conductivity, in siemens per meter */ + int pol; /**< Polarization, 0 = Horizontal, 1 = Vertical */ +}; + +/** Key names for LFMF Model input file parameters */ +struct LFMFInputKeys { + static const std::string h_tx__meter; /**< Height of the transmitter, in meters */ + static const std::string h_rx__meter; /**< Height of the receiver, in meters */ + static const std::string f__mhz; /**< Frequency, in MHz */ + static const std::string P_tx__watt; /**< Transmitter power, in watts */ + static const std::string N_s; /**< Surface refractivity, in N-Units */ + static const std::string d__km; /**< Path distance, in km */ + static const std::string epsilon; /**< Relative permittivity */ + static const std::string sigma; /**< Conductivity, in siemens per meter */ + static const std::string pol; /**< Polarization, 0 = Horizontal, 1 = Vertical */ +}; // Constants defined in app/src/LFMFModel.cpp +// clang-format on \ No newline at end of file diff --git a/app/src/CMakeLists.txt b/app/src/CMakeLists.txt new file mode 100644 index 0000000..1f06c08 --- /dev/null +++ b/app/src/CMakeLists.txt @@ -0,0 +1,41 @@ +########################################### +## BUILD THE DRIVER +########################################### +## Include source AND header files here. Do not include +## source/header files already included in `add_library()` in `src/CMakeLists.txt` +add_executable( + ${DRIVER_NAME} + "CommaSeparatedIterator.cpp" + "Driver.cpp" + "DriverUtils.cpp" + "ReturnCodes.cpp" + "LFMFModel.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}) + +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..76df299 --- /dev/null +++ b/app/src/Driver.cpp @@ -0,0 +1,186 @@ +/** @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 + LFMFParams lfmf_params; + Result result; + + rtn = ParseLFMFInputFile(params.in_file, lfmf_params); + if (rtn != DRVR__SUCCESS) { + return rtn; + } + rtn = CallLFMFModel(lfmf_params, result); + + // 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(30) << "Model" << LIBRARY_NAME; + 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:"; + WriteLFMFInputs(fp, lfmf_params); + + // Print results to file + fp << std::endl << std::endl << "Results:"; + fp PRINT "Return Code" SETW13 rtn; + PrintLabel(fp, GetReturnStatus(rtn)); + if (rtn == SUCCESS) { + WriteLFMFOutputs(fp, result); + } + 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", "-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++; + } + } + + 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 << std::endl << "Examples:" << std::endl; + os << "\t[WINDOWS] " << DRIVER_NAME << ".exe -i inputs.txt -o results.txt" + << std::endl; + os << "\t[LINUX] .\\" << DRIVER_NAME << " -i in.txt -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 (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/LFMFModel.cpp b/app/src/LFMFModel.cpp new file mode 100644 index 0000000..d89cb6f --- /dev/null +++ b/app/src/LFMFModel.cpp @@ -0,0 +1,166 @@ +/** @file LFMFModel.cpp + * Implements top-level functions for running the LF/MF Propagation 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 LFMFInputKeys::h_tx__meter = "h_tx__meter"; +const std::string LFMFInputKeys::h_rx__meter = "h_rx__meter"; +const std::string LFMFInputKeys::f__mhz = "f__mhz"; +const std::string LFMFInputKeys::P_tx__watt = "p_tx__watt"; +const std::string LFMFInputKeys::N_s = "n_s"; +const std::string LFMFInputKeys::d__km = "d__km"; +const std::string LFMFInputKeys::epsilon = "epsilon"; +const std::string LFMFInputKeys::sigma = "sigma"; +const std::string LFMFInputKeys::pol = "pol"; + +/******************************************************************************* + * Top-level control function for LFMF Model + * + * @param[in] lfmf_params LFMF Model input parameter struct + * @param[out] result LFMF Results in Result struct + * @return Return code + ******************************************************************************/ +ReturnCode CallLFMFModel(LFMFParams &lfmf_params, Result &result) { + ReturnCode rtn; + rtn = LFMF( + lfmf_params.h_tx__meter, + lfmf_params.h_rx__meter, + lfmf_params.f__mhz, + lfmf_params.P_tx__watt, + lfmf_params.N_s, + lfmf_params.d__km, + lfmf_params.epsilon, + lfmf_params.sigma, + lfmf_params.pol, + result + ); + + return rtn; +} + +/******************************************************************************* + * Parse input stream (file or string stream) to LFMF parameter struct. + * + * @param[in] stream Input stream containing LFMF parameters + * @param[out] lfmf_params LFMF input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseLFMFInputStream(std::istream &stream, LFMFParams &lfmf_params) { + CommaSeparatedIterator it(stream); + DrvrReturnCode rtn = DRVR__SUCCESS; + std::string key, value, errMsg; + while (it) { + std::tie(key, value) = *it; + if (key.compare(LFMFInputKeys::h_tx__meter) == 0) { + rtn = ParseDouble(value, lfmf_params.h_tx__meter); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_TX_TERMINAL_HEIGHT; + } else if (key.compare(LFMFInputKeys::h_rx__meter) == 0) { + rtn = ParseDouble(value, lfmf_params.h_rx__meter); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_RX_TERMINAL_HEIGHT; + } else if (key.compare(LFMFInputKeys::f__mhz) == 0) { + rtn = ParseDouble(value, lfmf_params.f__mhz); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_FREQUENCY; + } else if (key.compare(LFMFInputKeys::P_tx__watt) == 0) { + rtn = ParseDouble(value, lfmf_params.P_tx__watt); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_TX_POWER; + } else if (key.compare(LFMFInputKeys::N_s) == 0) { + rtn = ParseDouble(value, lfmf_params.N_s); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_SURFACE_REFRACTIVITY; + } else if (key.compare(LFMFInputKeys::d__km) == 0) { + rtn = ParseDouble(value, lfmf_params.d__km); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_PATH_DISTANCE; + } else if (key.compare(LFMFInputKeys::epsilon) == 0) { + rtn = ParseDouble(value, lfmf_params.epsilon); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_EPSILON; + } else if (key.compare(LFMFInputKeys::sigma) == 0) { + rtn = ParseDouble(value, lfmf_params.sigma); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_SIGMA; + } else if (key.compare(LFMFInputKeys::pol) == 0) { + rtn = ParseInteger(value, lfmf_params.pol); + if (rtn == DRVRERR__PARSE) + rtn = DRVRERR__PARSE_POLARIZATION; + } 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 LFMF Model input parameter file + * + * @param[in] in_file Path to LFMF input parameter file + * @param[out] lfmf_params LFMF input parameter struct + * @return Return code + ******************************************************************************/ +DrvrReturnCode + ParseLFMFInputFile(const std::string &in_file, LFMFParams &lfmf_params) { + std::ifstream file(in_file); + if (!file) { + std::cerr << "Failed to open file " << in_file << std::endl; + return DRVRERR__OPENING_INPUT_FILE; + } + return ParseLFMFInputStream(file, lfmf_params); +} + +/******************************************************************************* + * Write LFMF Model inputs to the report file + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] params LFMF input parameter struct + ******************************************************************************/ +void WriteLFMFInputs(std::ofstream &fp, const LFMFParams ¶ms) { + fp PRINT LFMFInputKeys::h_tx__meter SETW13 params.h_tx__meter << "[meters]"; + fp PRINT LFMFInputKeys::h_rx__meter SETW13 params.h_rx__meter << "[meters]"; + fp PRINT LFMFInputKeys::f__mhz SETW13 params.f__mhz << "[MHz]"; + fp PRINT LFMFInputKeys::P_tx__watt SETW13 params.P_tx__watt << "[watts]"; + fp PRINT LFMFInputKeys::N_s SETW13 params.N_s << "[N-Units]"; + fp PRINT LFMFInputKeys::d__km SETW13 params.d__km << "[km]"; + fp PRINT LFMFInputKeys::epsilon SETW13 params.epsilon; + fp PRINT LFMFInputKeys::sigma SETW13 params.sigma; + fp PRINT LFMFInputKeys::pol SETW13 params.pol + << "[0 = Horizontal, 1 = Vertical]"; +} + +/******************************************************************************* + * Write LFMF Model outputs to the report file + * + * @param[in] fp Output stream, a text file open for writing + * @param[in] result LFMF output result struct + ******************************************************************************/ +void WriteLFMFOutputs(std::ofstream &fp, const Result &result) { + fp PRINT "Basic transmission loss" SETW13 std::fixed + << std::setprecision(2) << result.A_btl__db << "[dB]"; + fp PRINT "Electric field strength" SETW13 std::fixed + << std::setprecision(2) << result.E_dBuVm << "[dB(uV/m)]"; + fp PRINT "Received power" SETW13 std::fixed << std::setprecision(2) + << result.P_rx__dbm << "[dB]"; + fp PRINT "Solution method" SETW13 std::fixed + << std::setprecision(0) << result.method + << "[0 = Flat earth with curve correction, 1 = Residue series]"; +} \ No newline at end of file diff --git a/app/src/ReturnCodes.cpp b/app/src/ReturnCodes.cpp new file mode 100644 index 0000000..eafea47 --- /dev/null +++ b/app/src/ReturnCodes.cpp @@ -0,0 +1,61 @@ +/** @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_TX_TERMINAL_HEIGHT, + "Failed to parse TX terminal height value"}, + {DRVRERR__PARSE_RX_TERMINAL_HEIGHT, + "Failed to parse RX terminal height value"}, + {DRVRERR__PARSE_FREQUENCY, "Failed to parse frequency value"}, + {DRVRERR__PARSE_TX_POWER, "Failed to parse transmit power value"}, + {DRVRERR__PARSE_SURFACE_REFRACTIVITY, + "Failed to parse surface refractivity value"}, + {DRVRERR__PARSE_PATH_DISTANCE, "Failed to parse path distance value" }, + {DRVRERR__PARSE_EPSILON, "Failed to parse epsilon value"}, + {DRVRERR__PARSE_SIGMA, "Failed to parse sigma value"}, + {DRVRERR__PARSE_POLARIZATION, "Failed to parse polarization 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"}}; + + // 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/tests/CMakeLists.txt b/app/tests/CMakeLists.txt new file mode 100644 index 0000000..0b22634 --- /dev/null +++ b/app/tests/CMakeLists.txt @@ -0,0 +1,32 @@ +############################################ +## CONFIGURE COMMAND LINE DRIVER TESTS +############################################ +add_executable( + ${DRIVER_TEST_NAME} + "TempTextFile.cpp" + "TestDriver.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}) + +# 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..2b35963 --- /dev/null +++ b/app/tests/TestDriver.cpp @@ -0,0 +1,60 @@ +/** @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"; + 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 + = "h_tx__meter,0\nh_rx__meter,0\nf__mhz,0.01\nP_tx__watt,1000\nN_s," + "301\nd__km,1000\nepsilon,15\nsigma,0.005\npol,0"; + 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"; + 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"; + SuppressOutputs(cmd); + int rtn = RunCommand(cmd); + EXPECT_EQ(DRVRERR__VALIDATION_OUT_FILE, rtn); +} diff --git a/app/tests/TestDriver.h b/app/tests/TestDriver.h new file mode 100644 index 0000000..bf6795d --- /dev/null +++ b/app/tests/TestDriver.h @@ -0,0 +1,172 @@ +/** @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; + 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/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.Propagation.LFMF/ITS.Propagation.LFMF.csproj b/dotnet/ITS.Propagation.LFMF/ITS.Propagation.LFMF.csproj deleted file mode 100644 index c9a7908..0000000 --- a/dotnet/ITS.Propagation.LFMF/ITS.Propagation.LFMF.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Debug - AnyCPU - {1C761232-C4C0-4403-AA3A-7160DCDF48C8} - Library - Properties - ITS.Propagation - ITS.Propagation.LFMF - v4.8.1 - 512 - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\ITS.Propagation.LFMF.xml - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\ITS.Propagation.LFMF.xml - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/ITS.Propagation.LFMF/ITS.Propagation.LFMF.sln b/dotnet/ITS.Propagation.LFMF/ITS.Propagation.LFMF.sln deleted file mode 100644 index 46e729b..0000000 --- a/dotnet/ITS.Propagation.LFMF/ITS.Propagation.LFMF.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30320.27 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ITS.Propagation.LFMF", "ITS.Propagation.LFMF.csproj", "{1C761232-C4C0-4403-AA3A-7160DCDF48C8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1C761232-C4C0-4403-AA3A-7160DCDF48C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C761232-C4C0-4403-AA3A-7160DCDF48C8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C761232-C4C0-4403-AA3A-7160DCDF48C8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C761232-C4C0-4403-AA3A-7160DCDF48C8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {ECF2AF6A-510E-4A3F-BAA6-51FDC7F311C6} - EndGlobalSection -EndGlobal diff --git a/dotnet/ITS.Propagation.LFMF/LFMF.cs b/dotnet/ITS.Propagation.LFMF/LFMF.cs deleted file mode 100644 index e72c476..0000000 --- a/dotnet/ITS.Propagation.LFMF/LFMF.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITS.Propagation -{ - /// - /// The Low Frequency / Medium Frequency (LF/MF) Propagation Model - /// - public static partial class LFMF - { - #region 32-Bit P/Invoke Definitions - - [DllImport("LFMF_x86.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "LFMF")] - private static extern int LFMF_x86(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, out Result result); - - #endregion - - #region 64-Bit P/Invoke Definitions - - [DllImport("LFMF_x64.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "LFMF")] - private static extern int LFMF_x64(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, out Result result); - - #endregion - - private delegate int LFMF_Delegate(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, out Result result); - - private static LFMF_Delegate LFMF_Invoke; - - static LFMF() - { - if (Environment.Is64BitProcess) - LFMF_Invoke = LFMF_x64; - else - LFMF_Invoke = LFMF_x86; - } - - /// - /// Compute the LFMF propagation prediction - /// - /// Transmitter height, in meters - /// Receiver height, in meters - /// Frequency, in MHz - /// Transmit power, in Watts - /// Surface refractivity, in N-Units - /// Path distance, in km - /// Relative permittivity - /// Conductivity - /// Polarization - /// Prediction result - /// Error code - public static int Invoke(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, Polarization pol, out Result result) - { - return LFMF_Invoke(h_tx__meter, h_rx__meter, f__mhz, P_tx__watt, N_s, - d__km, epsilon, sigma, (int)pol, out result); - } - - /// - /// Compute the LFMF propagation prediction - /// - /// Transmitter height, in meters - /// Receiver height, in meters - /// Frequency, in MHz - /// Transmit power, in Watts - /// Surface refractivity, in N-Units - /// Path distance, in km - /// Relative permittivity - /// Conductivity - /// Polarization - /// Prediction result - /// Error code - public static int Invoke(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, out Result result) - { - return LFMF_Invoke(h_tx__meter, h_rx__meter, f__mhz, P_tx__watt, N_s, - d__km, epsilon, sigma, pol, out result); - } - } -} diff --git a/dotnet/ITS.Propagation.LFMF/Properties/AssemblyInfo.cs b/dotnet/ITS.Propagation.LFMF/Properties/AssemblyInfo.cs deleted file mode 100644 index ccf0355..0000000 --- a/dotnet/ITS.Propagation.LFMF/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("ITS.Propagation.LFMF")] -[assembly: AssemblyDescription("Low Frequency / Medium Frequency (LF/MF) Propagation Model")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("The Institute for Telecommunication Sciences")] -[assembly: AssemblyProduct("ITS.Propagation.LFMF")] -[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("1c761232-c4c0-4403-aa3a-7160dcdf48c8")] - -// 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.1.0.0")] -[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/dotnet/ITS.Propagation.LFMF/Result.cs b/dotnet/ITS.Propagation.LFMF/Result.cs deleted file mode 100644 index c083281..0000000 --- a/dotnet/ITS.Propagation.LFMF/Result.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace ITS.Propagation -{ - public static partial class LFMF - { - /// - /// Polarization - /// - public enum Polarization : int - { - /// - /// Horizontal polarization - /// - Horizontal = 0, - - /// - /// Vertical polarization - /// - Vertical = 1 - } - - /// - /// Solution method used - /// - public enum Method : int - { - /// - /// Flat earth with curve correction - /// - FlatEarthCurveCorrection = 0, - - /// - /// Residue series - /// - ResidueSeries = 1 - } - - /// - /// LF/MF prediction results - /// - [StructLayout(LayoutKind.Sequential)] - public struct Result - { - /// - /// Basic transmission loss, in dB - /// - public double A_btl__db; - - /// - /// Electic field strength, in db(uV/m) - /// - public double E_dBuVm; - - /// - /// Received power, in dBm - /// - public double P_rx__dbm; - - /// - /// Solution method used - /// - public Method Method; - } - } -} diff --git a/dotnet/nuget/LFMF.nuspec b/dotnet/nuget/LFMF.nuspec deleted file mode 100644 index 51c21e2..0000000 --- a/dotnet/nuget/LFMF.nuspec +++ /dev/null @@ -1,30 +0,0 @@ - - - - LFMF - 1.1.0 - The Institute for Telecommunication Sciences - The Institute for Telecommunication Sciences - LICENSE.md - https://github.com/NTIA/LFMF - images\itslogo.png - true - Low Frequency / Medium Frequency (LF/MF) Propagation Model - None - Propagation - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/nuget/build/net481/LFMF.targets b/dotnet/nuget/build/net481/LFMF.targets deleted file mode 100644 index d4c1b1d..0000000 --- a/dotnet/nuget/build/net481/LFMF.targets +++ /dev/null @@ -1,16 +0,0 @@ - - - - - PreserveNewest - LFMF_x86.dll - - - - - - PreserveNewest - LFMF_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/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..d3cc4d6 --- /dev/null +++ b/extern/test-data @@ -0,0 +1 @@ +Subproject commit d3cc4d6efff973b1a4fac4dbf3074439aa3246ba diff --git a/include/LFMF.h b/include/LFMF.h index 700fa02..4af730c 100644 --- a/include/LFMF.h +++ b/include/LFMF.h @@ -1,87 +1,207 @@ +/** @file LFMF.h + * Interface header for this library + */ +#pragma once -#include +#include // for DBL_EPSILON +#include // for std::complex +#include // for std::string -// Export the DLL functions as "C" and not C++ -#define DLLEXPORT extern "C" __declspec(dllexport) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define DIM(x, y) (((x) > (y)) ? (x - y) : (0)) - -using std::complex; - -#define PI 3.1415926535897932384 -#define epsilon_0 8.854187817e-12 // Vacuum permittivity (F/m) -#define a_0__km 6370 // Earth radius, in km -#define C 299792458.0 // Speed of light (m/s) -#define SQRTPI sqrt(PI) -#define THIRD 1.0 / 3.0 -#define FALSE 0 -#define TRUE 1 -#define D2R PI/180.0 -#define R2D 180.0/PI -#define ETA 119.9169832*PI // Intrinsic impedance of free space (ohms) - -#define POLARIZATION__HORIZONTAL 0 -#define POLARIZATION__VERTICAL 1 - -#define METHOD__FLAT_EARTH_CURVE 0 -#define METHOD__RESIDUE_SERIES 1 - -#define YES 1 // Find the derivative i.e., Ai'() or Bi'() -#define NO 0 // Find Ai() or Bi() -// kind -#define AIRY 1 // Find the Airy Function -#define AIRYD 2 // Find the Airy function Derivative -#define BAIRY 3 // Find the Bairy Function -#define BAIRYD 4 // Find the Bairy function Derivative -#define WTWO 5 // find Hufford Wi(2) or Wait W2 -#define DWTWO 6 // find Hufford Wi'(2) or Wait W2' -#define WONE 7 // find Hufford Wi(1) or Wait W1 -#define DWONE 8 // find Hufford Wi'(1) or Wait W1' -// scaling -#define HUFFORD 9 // Use Hufford scaling -#define WAIT 10 // Use Wait scaling -#define NONE 11 // No Scaling - -// Return codes -#define SUCCESS 0 - -// Error codes -#define ERROR__TX_TERMINAL_HEIGHT 1000 // TX terminal height is out of range -#define ERROR__RX_TERMINAL_HEIGHT 1001 // RX terminal height is out of range -#define ERROR__FREQUENCY 1002 // Frequency is out of range -#define ERROR__TX_POWER 1003 // Transmit power is out of range -#define ERROR__SURFACE_REFRACTIVITY 1004 // Surface refractivity is out of range -#define ERROR__PATH_DISTANCE 1005 // Path distance is out of range -#define ERROR__EPSILON 1006 // Epsilon is out of range -#define ERROR__SIGMA 1007 // Sigma is out of range -#define ERROR__POLARIZATION 1008 // Invalid value for polarization - -////////////////////////////////////// -// Data Structures +namespace ITS { +namespace Propagation { +namespace LFMF { -struct Result -{ - double A_btl__db; - double E_dBuVm; - double P_rx__dbm; +// Define cross-platform DLLEXPORT +// Export the DLL functions as "C" and not C++ +#ifndef DOXYGEN_SHOULD_SKIP + #ifdef _WIN32 + #define DLLEXPORT extern "C" __declspec(dllexport) + #else + #define DLLEXPORT extern "C" + #endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Enums + +/** Valid RF polarizations for use of this model */ +enum Polarization { + HORIZONTAL = 0, /**< Horizontal polarization */ + VERTICAL = 1, /**< Vertical polarization */ +}; - int method; +/** Solution method used to generate model result */ +enum SolutionMethod { + FLAT_EARTH_CURVE, /**< Flat earth curve method */ + RESIDUE_SERIES, /**< Residue series method */ }; -////////////////////////////////////// -// Main LFMF Function +/******************************************************************************* + * Kinds of Airy functions to specify for `Airy()` and `WiRoot()` operation. + * + * The members of this enum allow specification of Airy functions of the first + * (`AIRY`), second (`BAIRY`), or third (`WONE` and `WTWO`) kind. These Airy + * functions of a third kind are specified by Hufford in NTIA Report 87-219. The + * enum also allows selection of derivatives of any of these functions. + * + * @note Hufford's Airy functions of a third kind @f$ \mathrm{Wi}^{(1)} @f$ + * and @f$ \mathrm{Wi}^{(2)} @f$ are, aside from a multiplicative constant, + * the same as Wait's @f$ w_2 @f$ and @f$ w_1 @f$. + * @see ITS::Propagation::LFMF::Airy + * @see ITS::Propagation::LFMF::WiRoot + * @see ITS::Propagation::LFMF::AiryFunctionScaling + ******************************************************************************/ +// clang-format off +enum AiryFunctionKind { + AIRY = 1, /**< Airy function of the first kind, @f$ \mathrm{Ai}(x) @f$ */ + AIRYD, /**< Derivative of `AIRY`, @f$ \mathrm{Ai}'(x) @f$ */ + BAIRY, /**< Airy function of the second kind, @f$ \mathrm{Bi}(x) @f$ */ + BAIRYD, /**< Derivative of `BAIRY`, @f$ \mathrm{Bi}'(x) @f$ */ + WTWO, /**< Hufford's Airy function of a third kind, @f$ \mathrm{Wi}^{(2)} @f$ (Wait's @f$ w_1 @f$) */ + DWTWO, /**< Derivative of `WTWO`, @f$ \mathrm{Wi}'^{(2)} @f$ (Wait's @f$ w_1' @f$) */ + WONE, /**< Hufford's Airy function of a third kind, @f$ \mathrm{Wi}'^{(1)} @f$ (Wait's @f$ w_2 @f$)*/ + DWONE, /**< Derivative of `WONE`, @f$ \mathrm{Wi}'^{(1)} @f$ (Wait's @f$ w_2' @f$) */ +}; +// clang-format on + +/******************************************************************************* + * Multiplicative scaling to use when dealing with Airy functions. + * + * The differences in these scaling options are discussed at length in the + * documentation for ITS::Propagation::LFMF::Airy. + * + * @see ITS::Propagation::LFMF::Airy + * @see ITS::Propagation::LFMF::WiRoot + * @see ITS::Propagation::LFMF::AiryFunctionKind + ******************************************************************************/ +enum AiryFunctionScaling { + HUFFORD, /**< Use Hufford scaling */ + WAIT, /**< Use Wait scaling */ + NONE, /**< No Scaling */ +}; -DLLEXPORT int LFMF(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, Result *result); +/******************************************************************************* + * Return Codes defined by this software (0-127) + ******************************************************************************/ +// clang-format off +enum ReturnCode { + SUCCESS = 0, /**< Return Success */ + + // Invalid Inputs + ERROR__TX_TERMINAL_HEIGHT = 32, /**< TX terminal height is out of range */ + ERROR__RX_TERMINAL_HEIGHT, /**< RX terminal height is out of range */ + ERROR__FREQUENCY, /**< Frequency is out of range */ + ERROR__TX_POWER, /**< Transmit power is out of range */ + ERROR__SURFACE_REFRACTIVITY, /**< Surface refractivity is out of range */ + ERROR__PATH_DISTANCE, /**< Path distance is out of range */ + ERROR__EPSILON, /**< Epsilon is out of range */ + ERROR__SIGMA, /**< Sigma is out of range */ + ERROR__POLARIZATION, /**< Invalid value for polarization */ +}; +// clang-format on + +//////////////////////////////////////////////////////////////////////////////// +// Constants +// clang-format off +constexpr double PI = 3.1415926535897932384; /**< Approximate value of @f$ \pi @f$ */ +constexpr double epsilon_0 = 8.854187817e-12; /**< Vacuum permittivity (F/m) */ +constexpr double a_0__km = 6370; /**< Earth radius, in km */ +constexpr double C = 299792458.0; /**< Speed of light (m/s) */ +constexpr double ETA = 119.9169832 * PI; /**< Intrinsic impedance of free space (ohms) */ +// clang-format on + +//////////////////////////////////////////////////////////////////////////////// +// Data Structures -////////////////////////////////////// -// Helper Functions +/** Structure to hold LF/MF model outputs. */ +struct Result { + double A_btl__db; /**< Basic transmission loss, in dB */ + double E_dBuVm; /**< Electric field strength, in dB(uV/m) */ + double P_rx__dbm; /**< Received power, in dBm */ + SolutionMethod method; /**< Method used to obtain results */ +}; -double FlatEarthCurveCorrection(complex delta, complex q, double h_1__km, double h_2__km, double d, double k, double a_e__km); -double ResidueSeries(double d, double k, double h_1__km, double h_2__km, double nu, double theta, complex q); -complex wofz(complex qi); -complex Airy(complex Z, int kind, int scaling); -complex WiRoot(int i, complex *DWi, complex q, complex *Wi, int kind, int scaling); -int ValidateInput(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol); +//////////////////////////////////////////////////////////////////////////////// +// Public Functions + +DLLEXPORT ReturnCode LFMF( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma, + const int pol, + Result &result +); + +DLLEXPORT char *GetReturnStatusCharArray(const int code); +DLLEXPORT void FreeReturnStatusCharArray(char *c_msg); + +//////////////////////////////////////////////////////////////////////////////// +// Private Functions + +ReturnCode LFMF_CPP( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma, + const Polarization pol, + Result &result +); +std::string GetReturnStatus(const int code); +double FlatEarthCurveCorrection( + const std::complex delta, + const std::complex q, + const double h_1__km, + const double h_2__km, + const double d, + const double k, + const double a_e__km +); +double ResidueSeries( + const double k, + const double h_1__km, + const double h_2__km, + const double nu, + const double theta, + const std::complex q +); +std::complex wofz(const std::complex z); +std::complex Airy( + const std::complex Z, + const AiryFunctionKind kind, + const AiryFunctionScaling scaling +); +std::complex WiRoot( + const int i, + std::complex &DWi, + const std::complex q, + std::complex &Wi, + const AiryFunctionKind kind, + const AiryFunctionScaling scaling +); +ReturnCode ValidateInput( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma +); +ReturnCode ValidatePolarization(const int pol); +bool AlmostEqualRelative( + const double A, const double B, const double maxRelDiff = DBL_EPSILON +); + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS diff --git a/src/Airy.cpp b/src/Airy.cpp index 37355a1..7a0c434 100644 --- a/src/Airy.cpp +++ b/src/Airy.cpp @@ -1,160 +1,169 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: This routine finds the Airy, Bariy, Wi(1) and Wi(2) - | functions and their derivatives for a complex input argument - | from a shifted Taylor series or by asymptotic approximation - | depending of the location of the input argument. - | - | This routine determines the so-called "Airy Functions of the - | third kind" Wi(1) and Wi(2) that are found in equation 38 - | of NTIA Report 87-219 "A General Theory of Radio - | Propagation through a Stratified Atmosphere", George - | Hufford, July 1987 - | - | The Airy function that appeared in the original GWINT and - | GWRES had the switches all mangled from what George Hufford - | had in mind this routine has the corrected switches. Please - | see the Airy function code that appears in the appendix of - | OT/ITS RR 11 "A Wave Hop Propagation Program for an - | Anisotropic Ionosphere" L. A. Berry and J. E. Herman - | April 1971 - | - | Input: Z - Input argument - | kind - Switch that indicates what type of Airy - | function to solve for - | scaling - - | - | Outputs: [None] - | - | Returns: Ai - The desired Airy function calculated at Z - | - | Note: A note on scaling the output from this program - | - | There is a definitional problem with the Airy function - | which is inevitable relative to how it was defined in the - | original LFMF code originated with the Hufford's AIRY - | subroutine. - | - | Using the scaling equal to HUFFORD in this program follows - | the definitions of Wi(1) and Wi(2) as defined by Hufford - | (87-219) - | - | Using the scaling equal to WAIT in this program uses the - | definitions of W1 and W2 defined in Deminco (99-368) and - | in the original LFMF code following Berry via Wait. - | - | The two solutions differ by a constant. As Hufford notes - | concerning Wi(1) and Wi(2) in 87-219 - | - | "Except for multiplicative constants they correspond to - | what Fock (1965) calls w1 and w2 and to what Wait (1962) - | calls w2 and w1" - | - | The following are the multiplicative constants that allow - | for the translation between Hufford Wi(2) and Wi(1) with - | Wait W1 and W2, respectively. These are given here as a - | reference if this function is used for programs other - | than LFMF. - | - | // Wait - | complex WW2 = complex( sqrt(3.0*PI), sqrt(PI)); - | complex WDW2 = complex(-1.0*sqrt(3.0*PI), sqrt(PI)); - | complex WW1 = complex( sqrt(3.0*PI), -1.0*sqrt(PI)); - | complex WDW1 = complex(-1.0*sqrt(3.0*PI), -1.0*sqrt(PI)); - | - | // Hufford - | complex HW2 = 2.0*complex(cos( PI/3.0), sin( PI/3.0)); - | complex HDW2 = 2.0*complex(cos(-PI/3.0), sin(-PI/3.0)); - | complex HW1 = 2.0*complex(cos(-PI/3.0), sin(-PI/3.0)); - | complex HDW1 = 2.0*complex(cos( PI/3.0), sin( PI/3.0)); - | - | // (Multiplicative constant) * Huffords Wi'(1) = Wait W1' - | // So the multiplicative constants are: - | complex uDW2 = WDW2/HDW1; // uDW2 = complex(0.0, sqrt(PI)) - | complex uW2 = WW2/HW1; // uW2 = complex(0.0, sqrt(PI)) - | complex uDW1 = WDW1/HDW2; // uDW1 = complex(0.0, -sqrt(PI)) - | complex uW1 = WW1/HW2; // uW1 = complex(0.0, -sqrt(PI)) - | - | To make the solutions that are generated by this program - | for the Hufford Airy functions of the "3rd kind" abundantly - | clear please examine the following examples. - | - | For Z = 8.0 + 8.0 i the Asymptotic Solution is used - | - | Ai( 8.0 + 8.0 i) = 6.576933e-007 + 9.312331e-006 i - | Ai'(8.0 + 8.0 i) = 9.79016e-006 + -2.992170e-005 i - | Bi( 8.0 + 8.0 i) = -1.605154e+003 + -4.807200e+003 i - | Bi'(8.0 + 8.0 i) = 1301.23 + -16956 i - | Wi(1)(8.0 + 8.0 i) = -4.807200e+003 + 1.605154e+003 i - | Wi(2)(8.0 + 8.0 i) = 4.807200e+003 + -1.605154e+003 i - | Ai(z) - j*Bi(z) = -4.807200e+003 + 1.605154e+003 i - | Ai(z) + j*Bi(z) = 4.807200e+003 + -1.605154e+003 i - | - | For Z = 1.0 - 2.0 i the Taylor series with a shifted - | center of expansion solution used. - | - | Ai( 1.0 - 2.0 i) = -2.193862e-001 + 1.753859e-001 i - | Ai'(1.0 - 2.0 i) = 0.170445 + -0.387622 i - | Bi( 1.0 - 2.0 i) = 4.882205e-002 + -1.332740e-001 i - | Bi'(1.0 - 2.0 i) = -0.857239 + -0.495506 i - | Wi(1)(1.0 - 2.0 i) = -3.526603e-001 + 1.265638e-001 i - | Wi(2)(1.0 - 2.0 i) = -8.611221e-002 + 2.242079e-001 i - | Ai(z) - j*Bi(z) = -3.526603e-001 + 1.265639e-001 i - | Ai(z) + j*Bi(z) = -8.611221e-002 + 2.242080e-001 i - | - *===========================================================================*/ -complex Airy(complex Z, int kind, int scaling) -{ +/** @file Airy.cpp + * Implements a function to calculate Airy functions and their derivatives. + */ + +#include "LFMF.h" + +#include // for abs, cos, exp, pow, sin, sqrt +#include // for std::arg, std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Finds the functions and their derivatives for Airy functions of the first, + * second, and third (following Hufford) kind. + * + * The function accepts a complex input argument and computes the result from a + * shifted Taylor series or by asymptotic approximation depending of the + * location of the input argument. + * + * This routine determines the so-called "Airy Functions of the third kind" + * @f$ Wi(1) @f$ and @f$ Wi(2) @f$ that are found in equation 38 of NTIA Report + * 87-219 "A General Theory of Radio Propagation through a Stratified + * Atmosphere", George Hufford, July 1987. + * + * The Airy function that appeared in the original FORTRAN `GWINT` and `GWRES` + * implementations had the switches all mangled from what George Hufford had in + * mind. This routine has the corrected switches. Please see the Airy function + * code that appears in the appendix of OT/ITS RR 11 "A Wave Hop Propagation + * Program for an Anisotropic Ionosphere" L. A. Berry and J. E. Herman, April 1971. + * + * @param[in] Z Complex input argument + * @param[in] kind Switch indicating the type of Airy function to solve + * @param[in] scaling Type of scaling to use + * @return The desired Airy function calculated at Z + * + * @note The following is a note on scaling the output from this program. + * + * There is a definitional problem with the Airy function which is inevitable + * relative to how it was defined in the original LFMF code originated with + * the Hufford's `AIRY` subroutine. + * + * Using the scaling equal to `HUFFORD` in this program follows the definitions + * of @f$ \mathrm{Wi}^{(1)} @f$ and @f$ \mathrm{Wi}^{(2)} @f$ as defined by + * Hufford (87-219). + * + * Using the scaling equal to `WAIT` in this program uses the definitions of + * @f$ W_1 @f$ and @f$ W_2 @f$ defined in DeMinco (99-368) and in the original + * LFMF code following Berry via Wait. + * + * The two solutions differ by a constant. As Hufford notes concerning + * @f$ \mathrm{Wi}^{(1)} @f$ and @f$ \mathrm{Wi}^{(2)} @f$ in 87-219: + * + * > "Except for multiplicative constants they correspond to what Fock (1965) + * > calls W1 and W2 and to what Wait (1962) calls W2 and W1. + * + * The following are the multiplicative constants that allow for the translation + * between Hufford @f$ \mathrm{Wi}^{(1)} @f$ and @f$ \mathrm{Wi}^{(2)} @f$ with + * Wait @f$ w_1 @f$ and @f$ w_2 @f$, respectively. These are given here as a + * reference if this function is used for programs other than LFMF. + * + * ```cpp + * // Wait + * complex WW2 = complex( sqrt(3.0*PI), sqrt(PI)); + * complex WDW2 = complex(-1.0*sqrt(3.0*PI), sqrt(PI)); + * complex WW1 = complex( sqrt(3.0*PI), -1.0*sqrt(PI)); + * complex WDW1 = complex(-1.0*sqrt(3.0*PI), -1.0*sqrt(PI)); + * + * // Hufford + * complex HW2 = 2.0*complex(cos( PI/3.0), sin( PI/3.0)); + * complex HDW2 = 2.0*complex(cos(-PI/3.0), sin(-PI/3.0)); + * complex HW1 = 2.0*complex(cos(-PI/3.0), sin(-PI/3.0)); + * complex HDW1 = 2.0*complex(cos( PI/3.0), sin( PI/3.0)); + * + * // (Multiplicative constant) * Hufford's Wi'(1) = Wait W1' + * // So the multiplicative constants are: + * complex uDW2 = WDW2/HDW1; // uDW2 = complex(0.0, sqrt(PI)) + * complex uW2 = WW2/HW1; // uW2 = complex(0.0, sqrt(PI)) + * complex uDW1 = WDW1/HDW2; // uDW1 = complex(0.0, -sqrt(PI)) + * complex uW1 = WW1/HW2; // uW1 = complex(0.0, -sqrt(PI)) + * ``` + * + * To make the solutions that are generated by this program for the Hufford + * Airy functions of the "3rd kind" abundantly clear please examine the + * following examples. + * + * For @f$ z = 8.0 + 8.0i @f$ the Asymptotic Solution is used. + * + * ```text + * Ai( 8.0 + 8.0 i) = 6.576933e-007 + 9.312331e-006 i + * Ai'(8.0 + 8.0 i) = 9.79016e-006 + -2.992170e-005 i + * Bi( 8.0 + 8.0 i) = -1.605154e+003 + -4.807200e+003 i + * Bi'(8.0 + 8.0 i) = 1301.23 + -16956 i + * Wi(1)(8.0 + 8.0 i) = -4.807200e+003 + 1.605154e+003 i + * Wi(2)(8.0 + 8.0 i) = 4.807200e+003 + -1.605154e+003 i + * Ai(z) - j*Bi(z) = -4.807200e+003 + 1.605154e+003 i + * Ai(z) + j*Bi(z) = 4.807200e+003 + -1.605154e+003 i + * ``` + * + * For @f$ z = 1.0 - 2.0i @f$ the Taylor series with a shifted center of + * expansion solution is used. + * + * ```text + * Ai( 1.0 - 2.0 i) = -2.193862e-001 + 1.753859e-001 i + * Ai'(1.0 - 2.0 i) = 0.170445 + -0.387622 i + * Bi( 1.0 - 2.0 i) = 4.882205e-002 + -1.332740e-001 i + * Bi'(1.0 - 2.0 i) = -0.857239 + -0.495506 i + * Wi(1)(1.0 - 2.0 i) = -3.526603e-001 + 1.265638e-001 i + * Wi(2)(1.0 - 2.0 i) = -8.611221e-002 + 2.242079e-001 i + * Ai(z) - j*Bi(z) = -3.526603e-001 + 1.265639e-001 i + * Ai(z) + j*Bi(z) = -8.611221e-002 + 2.242080e-001 i + * ``` + * @see ITS::Propagation::LFMF::AiryFunctionKind + * @see ITS::Propagation::LFMF::AiryFunctionScaling + * @see ITS::Propagation::LFMF::WiRoot + ******************************************************************************/ +std::complex Airy( + const std::complex Z, + const AiryFunctionKind kind, + const AiryFunctionScaling scaling +) { // NQTT, ASLT data - int NQTT[15] = { 1,3,7,12,17,23,29,35,41,47,53,59,64,68,71 }; // Centers of Expansion of Taylor series on real axis indices into the - // AV, APV, BV and BPV arrays - int N; // Index into NQTT[] array - int NQ8; // Index that indicates the radius of convergence of the Taylor series solution - int CoERealidx; // Center of Expansion of the Taylor Series real index - int CoEImagidx; // Center of Expansion of the Taylor Series imaginary index - int cnt; // loop counter for the Taylor series calculation - int derivative; // index for derivative - - bool reflection; // Flag to indicate that the answer needs to be flipped over since this routine only finds solutions in quadrant 1 and 2 - - complex A[2], ZT, B0, B1, B2, B3, AN, U, AIRYW, ZA, ZB, ZE, ZR, V, ZV, ZU, PHZU; // Temps - complex CoE; // Center of Expansion of the Taylor series - complex Ai; // Ai is either Ai(at the center of expansion of the Taylor series) or Bi( at the center of expansion of the Taylor series ) - complex Aip; // Aip is the derivative of the above - complex sum1; // Temp Sum for the asymptotic solution - complex sum2; // Temp Sum for the asymptotic solution - complex ZB2, ZB1; - - double one; // Used in the calculation of the asymptotic solution is either -1 or 1 + int NQTT[15] = { + 1, 3, 7, 12, 17, 23, 29, 35, 41, 47, 53, 59, 64, 68, 71 + }; // Centers of Expansion of Taylor series on real axis indices into the + // AV, APV, BV and BPV arrays + int N; // Index into NQTT[] array + int NQ8; // Index that indicates the radius of convergence of the Taylor series solution + int CoERealidx; // Center of Expansion of the Taylor Series real index + int CoEImagidx; // Center of Expansion of the Taylor Series imaginary index + int cnt; // loop counter for the Taylor series calculation + + bool + reflection; // Flag to indicate that the answer needs to be flipped over since this routine only finds solutions in quadrant 1 and 2 + + std::complex A[2], ZT, B0, B1, B2, B3, AN, U, ZA, ZB, ZU; // Temps + std::complex CoE; // Center of Expansion of the Taylor series + std::complex + Ai; // Ai is either Ai(at the center of expansion of the Taylor series) or Bi( at the center of expansion of the Taylor series ) + std::complex Aip; // Aip is the derivative of the above + std::complex sum1; // Temp Sum for the asymptotic solution + std::complex sum2; // Temp Sum for the asymptotic solution + std::complex ZB2, ZB1; + + double + one; // Used in the calculation of the asymptotic solution is either -1 or 1 // terms for asymptotic series. second column is for derivative - int SIZE_OF_ASV = 15; - double ASV[15][2] = { {0.5989251E+5, -0.6133571E+5}, - {0.9207207E+4, -0.9446355E+4}, - {0.1533169E+4, -0.1576357E+4}, - {0.2784651E+3, -0.2870332E+3}, - {0.5562279E+2, -0.5750830E+2}, - {0.1234157E+2, -0.1280729E+2}, - {0.3079453E+1, -0.3210494E+1}, - {0.8776670E+0, -0.9204800E+0}, - {0.2915914E+0, -0.3082538E+0}, - {0.1160991E+0, -0.1241059E+0}, - {0.5764919E-1, -0.6266216E-1}, - {0.3799306E-1, -0.4246283E-1}, - {0.3713349E-1, -0.4388503E-1}, - {0.6944444E-1, -0.9722222E-1}, - {0.1000000E+1, 0.1000000E+1} }; - - // Complex array of the value Ai(a) Airy function - // where a is the complex location of the center of expansion of the Taylor series - complex AV[70]; - - // Complex array of the value Ai'(a) derivative of the Airy function - // where a is the complex location of the center of expansion of the Taylor series - // presumably for Ai'[z] - complex APV[70]; + constexpr int SIZE_OF_ASV = 15; + constexpr double ASV[SIZE_OF_ASV][2] + = {{0.5989251E+5, -0.6133571E+5}, + {0.9207207E+4, -0.9446355E+4}, + {0.1533169E+4, -0.1576357E+4}, + {0.2784651E+3, -0.2870332E+3}, + {0.5562279E+2, -0.5750830E+2}, + {0.1234157E+2, -0.1280729E+2}, + {0.3079453E+1, -0.3210494E+1}, + {0.8776670E+0, -0.9204800E+0}, + {0.2915914E+0, -0.3082538E+0}, + {0.1160991E+0, -0.1241059E+0}, + {0.5764919E-1, -0.6266216E-1}, + {0.3799306E-1, -0.4246283E-1}, + {0.3713349E-1, -0.4388503E-1}, + {0.6944444E-1, -0.9722222E-1}, + {0.1000000E+1, 0.1000000E+1}}; ////////////////////////////////////////////////////////////////////////// // Initialize the center of expansion arrays. // @@ -168,384 +177,397 @@ complex Airy(complex Z, int kind, int scaling) ////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////// Center of expansion /////////////////////////////////////////////////// ( Real, Imaginary) - AV[0] = complex(-3.2914520e-001, +0.0000000e+000);// (-6,0) - AV[1] = complex(-2.6780040e+000, +1.4774590e+000);// (-6,1/sin(pi/3)) - AV[2] = complex(+3.5076100e-001, +0.0000000e+000);// (-5,0) - AV[3] = complex(+2.4122260e+000, +6.9865120e-001);// (-5,1/sin(pi/3)) - AV[4] = complex(+3.3635530e+001, -3.4600960e+000);// (-5,2/sin(pi/3)) - AV[5] = complex(+3.4449740e+002, -3.3690890e+002);// (-5,3/sin(pi/3)) - AV[6] = complex(-7.0265530e-002, +0.0000000e+000);// (-4,0) - AV[7] = complex(-5.4818220e-001, -1.9207370e+000);// (-4,1/sin(pi/3)) - AV[8] = complex(-1.3383400e+001, -1.6022590e+001);// (-4,2/sin(pi/3)) - AV[9] = complex(-2.2967800e+002, -3.2072450e+001);// (-4,3/sin(pi/3)) - AV[10] = complex(-1.8040780e+003, +2.1917680e+003);// (-4,4/sin(pi/3)) - AV[11] = complex(-3.7881430e-001, +0.0000000e+000);// (-3,0) - AV[12] = complex(-1.3491840e+000, +8.4969080e-001);// (-3,1/sin(pi/3)) - AV[13] = complex(-6.0453340e+000, +1.0623180e+001);// (-3,2/sin(pi/3)) - AV[14] = complex(+3.1169620e+001, +9.8813520e+001);// (-3,3/sin(pi/3)) - AV[15] = complex(+9.8925350e+002, +1.3905290e+002);// (-3,4/sin(pi/3)) - AV[16] = complex(+2.2740740e-001, +0.0000000e+000);// (-2,0) - AV[17] = complex(+7.1857400e-001, +9.7809090e-001);// (-2,1/sin(pi/3)) - AV[18] = complex(+6.0621090e+000, +2.7203010e+000);// (-2,2/sin(pi/3)) - AV[19] = complex(+3.6307080e+001, -2.0961360e+001);// (-2,3/sin(pi/3)) - AV[20] = complex(-6.7139790e+001, -3.0904640e+002);// (-2,4/sin(pi/3)) - AV[21] = complex(-2.8001650e+003, +4.6649370e+002);// (-2,5/sin(pi/3)) - AV[22] = complex(+5.3556090e-001, +0.0000000e+000);// (-1,0) - AV[23] = complex(+9.2407370e-001, -1.9106560e-001);// (-1,1/sin(pi/3)) - AV[24] = complex(+1.8716190e+000, -2.5743310e+000);// (-1,2/sin(pi/3)) - AV[25] = complex(-7.2188440e+000, -1.2924200e+001);// (-1,3/sin(pi/3)) - AV[26] = complex(-8.1787380e+001, +3.2087010e+001);// (-1,4/sin(pi/3)) - AV[27] = complex(+2.9933950e+002, +5.6922180e+002);// (-1,5/sin(pi/3)) - AV[28] = complex(+3.5502810e-001, +0.0000000e+000);// ( 0,0) - AV[29] = complex(+3.1203440e-001, -3.8845390e-001);// ( 0,1/sin(pi/3)) - AV[30] = complex(-5.2840000e-001, -1.0976410e+000);// ( 0,2/sin(pi/3)) - AV[31] = complex(-4.2009350e+000, +1.1940150e+000);// ( 0,3/sin(pi/3)) - AV[32] = complex(+7.1858830e+000, +1.9600910e+001);// ( 0,4/sin(pi/3)) - AV[33] = complex(+1.0129120e+002, -7.5951230e+001);// ( 0,5/sin(pi/3)) - AV[34] = complex(+1.3529240e-001, +0.0000000e+000);// ( 1,0) - AV[35] = complex(+3.2618480e-002, -1.7084870e-001);// ( 1,1/sin(pi/3)) - AV[36] = complex(-3.4215380e-001, -8.9067650e-002);// ( 1,2/sin(pi/3)) - AV[37] = complex(-1.4509640e-001, +1.0328020e+000);// ( 1,3/sin(pi/3)) - AV[38] = complex(+4.1001970e+000, -6.8936910e-001);// ( 1,4/sin(pi/3)) - AV[39] = complex(-1.3030120e+001, -1.6910540e+001);// ( 1,5/sin(pi/3)) - AV[40] = complex(+3.4924130e-002, +0.0000000e+000);// ( 2,0) - AV[41] = complex(-8.4464730e-003, -4.2045150e-002);// ( 2,1/sin(pi/3)) - AV[42] = complex(-6.9313270e-002, +3.5364800e-002);// ( 2,2/sin(pi/3)) - AV[43] = complex(+1.5227620e-001, +1.2848450e-001);// ( 2,3/sin(pi/3)) - AV[44] = complex(+1.0681370e-001, -6.7766150e-001);// ( 2,4/sin(pi/3)) - AV[45] = complex(-2.6193430e+000, +1.5699860e+000);// ( 2,5/sin(pi/3)) - AV[46] = complex(+6.5911390e-003, +0.0000000e+000);// ( 3,0) - AV[47] = complex(-3.9443990e-003, -6.8060110e-003);// ( 3,1/sin(pi/3)) - AV[48] = complex(-5.9820130e-003, +1.1799010e-002);// ( 3,2/sin(pi/3)) - AV[49] = complex(+2.9922500e-002, -5.9772930e-003);// ( 3,3/sin(pi/3)) - AV[50] = complex(-7.7464130e-002, -5.2292400e-002);// ( 3,4/sin(pi/3)) - AV[51] = complex(+1.1276590e-001, +3.5112440e-001);// ( 3,5/sin(pi/3)) - AV[52] = complex(+9.5156390e-004, +0.0000000e+000);// ( 4,0) - AV[53] = complex(-8.0843000e-004, -7.6590130e-004);// ( 4,1/sin(pi/3)) - AV[54] = complex(+1.6147820e-004, +1.7661760e-003);// ( 4,2/sin(pi/3)) - AV[55] = complex(+2.0138720e-003, -3.1976720e-003);// ( 4,3/sin(pi/3)) - AV[56] = complex(-9.5086780e-003, +4.5377830e-003);// ( 4,4/sin(pi/3)) - AV[57] = complex(+3.7560190e-002, +5.7361920e-004);// ( 4,5/sin(pi/3)) - AV[58] = complex(+1.0834440e-004, +0.0000000e+000);// ( 5,0) - AV[59] = complex(-1.0968610e-004, -5.9902330e-005);// ( 5,1/sin(pi/3)) - AV[60] = complex(+1.0778190e-004, +1.5771600e-004);// ( 5,2/sin(pi/3)) - AV[61] = complex(-6.8980940e-005, -3.7626460e-004);// ( 5,3/sin(pi/3)) - AV[62] = complex(-1.6166130e-004, +9.7457770e-004);// ( 5,4/sin(pi/3)) - AV[63] = complex(+9.9476940e-006, +0.0000000e+000);// ( 6,0) - AV[64] = complex(-1.0956820e-005, -2.9508800e-006);// ( 6,1/sin(pi/3)) - AV[65] = complex(+1.4709070e-005, +8.1042090e-006);// ( 6,2/sin(pi/3)) - AV[66] = complex(-2.4446020e-005, -2.0638140e-005);// ( 6,3/sin(pi/3)) - AV[67] = complex(+7.4921290e-007, +0.0000000e+000);// ( 7,0) - AV[68] = complex(-8.4619070e-007, -3.6807340e-008);// ( 7,1/sin(pi/3)) - AV[69] = complex(+1.2183960e-006, +8.3589200e-008);// ( 7,2/sin(pi/3)) + // Complex array of the value Ai(a) Airy function + // where a is the complex location of the center of expansion of the Taylor series + constexpr std::complex AV[] = { + {-3.2914520e-001, +0.0000000e+000}, // (-6,0) + {-2.6780040e+000, +1.4774590e+000}, // (-6,1/sin(pi/3)) + {+3.5076100e-001, +0.0000000e+000}, // (-5,0) + {+2.4122260e+000, +6.9865120e-001}, // (-5,1/sin(pi/3)) + {+3.3635530e+001, -3.4600960e+000}, // (-5,2/sin(pi/3)) + {+3.4449740e+002, -3.3690890e+002}, // (-5,3/sin(pi/3)) + {-7.0265530e-002, +0.0000000e+000}, // (-4,0) + {-5.4818220e-001, -1.9207370e+000}, // (-4,1/sin(pi/3)) + {-1.3383400e+001, -1.6022590e+001}, // (-4,2/sin(pi/3)) + {-2.2967800e+002, -3.2072450e+001}, // (-4,3/sin(pi/3)) + {-1.8040780e+003, +2.1917680e+003}, // (-4,4/sin(pi/3)) + {-3.7881430e-001, +0.0000000e+000}, // (-3,0) + {-1.3491840e+000, +8.4969080e-001}, // (-3,1/sin(pi/3)) + {-6.0453340e+000, +1.0623180e+001}, // (-3,2/sin(pi/3)) + {+3.1169620e+001, +9.8813520e+001}, // (-3,3/sin(pi/3)) + {+9.8925350e+002, +1.3905290e+002}, // (-3,4/sin(pi/3)) + {+2.2740740e-001, +0.0000000e+000}, // (-2,0) + {+7.1857400e-001, +9.7809090e-001}, // (-2,1/sin(pi/3)) + {+6.0621090e+000, +2.7203010e+000}, // (-2,2/sin(pi/3)) + {+3.6307080e+001, -2.0961360e+001}, // (-2,3/sin(pi/3)) + {-6.7139790e+001, -3.0904640e+002}, // (-2,4/sin(pi/3)) + {-2.8001650e+003, +4.6649370e+002}, // (-2,5/sin(pi/3)) + {+5.3556090e-001, +0.0000000e+000}, // (-1,0) + {+9.2407370e-001, -1.9106560e-001}, // (-1,1/sin(pi/3)) + {+1.8716190e+000, -2.5743310e+000}, // (-1,2/sin(pi/3)) + {-7.2188440e+000, -1.2924200e+001}, // (-1,3/sin(pi/3)) + {-8.1787380e+001, +3.2087010e+001}, // (-1,4/sin(pi/3)) + {+2.9933950e+002, +5.6922180e+002}, // (-1,5/sin(pi/3)) + {+3.5502810e-001, +0.0000000e+000}, // ( 0,0) + {+3.1203440e-001, -3.8845390e-001}, // ( 0,1/sin(pi/3)) + {-5.2840000e-001, -1.0976410e+000}, // ( 0,2/sin(pi/3)) + {-4.2009350e+000, +1.1940150e+000}, // ( 0,3/sin(pi/3)) + {+7.1858830e+000, +1.9600910e+001}, // ( 0,4/sin(pi/3)) + {+1.0129120e+002, -7.5951230e+001}, // ( 0,5/sin(pi/3)) + {+1.3529240e-001, +0.0000000e+000}, // ( 1,0) + {+3.2618480e-002, -1.7084870e-001}, // ( 1,1/sin(pi/3)) + {-3.4215380e-001, -8.9067650e-002}, // ( 1,2/sin(pi/3)) + {-1.4509640e-001, +1.0328020e+000}, // ( 1,3/sin(pi/3)) + {+4.1001970e+000, -6.8936910e-001}, // ( 1,4/sin(pi/3)) + {-1.3030120e+001, -1.6910540e+001}, // ( 1,5/sin(pi/3)) + {+3.4924130e-002, +0.0000000e+000}, // ( 2,0) + {-8.4464730e-003, -4.2045150e-002}, // ( 2,1/sin(pi/3)) + {-6.9313270e-002, +3.5364800e-002}, // ( 2,2/sin(pi/3)) + {+1.5227620e-001, +1.2848450e-001}, // ( 2,3/sin(pi/3)) + {+1.0681370e-001, -6.7766150e-001}, // ( 2,4/sin(pi/3)) + {-2.6193430e+000, +1.5699860e+000}, // ( 2,5/sin(pi/3)) + {+6.5911390e-003, +0.0000000e+000}, // ( 3,0) + {-3.9443990e-003, -6.8060110e-003}, // ( 3,1/sin(pi/3)) + {-5.9820130e-003, +1.1799010e-002}, // ( 3,2/sin(pi/3)) + {+2.9922500e-002, -5.9772930e-003}, // ( 3,3/sin(pi/3)) + {-7.7464130e-002, -5.2292400e-002}, // ( 3,4/sin(pi/3)) + {+1.1276590e-001, +3.5112440e-001}, // ( 3,5/sin(pi/3)) + {+9.5156390e-004, +0.0000000e+000}, // ( 4,0) + {-8.0843000e-004, -7.6590130e-004}, // ( 4,1/sin(pi/3)) + {+1.6147820e-004, +1.7661760e-003}, // ( 4,2/sin(pi/3)) + {+2.0138720e-003, -3.1976720e-003}, // ( 4,3/sin(pi/3)) + {-9.5086780e-003, +4.5377830e-003}, // ( 4,4/sin(pi/3)) + {+3.7560190e-002, +5.7361920e-004}, // ( 4,5/sin(pi/3)) + {+1.0834440e-004, +0.0000000e+000}, // ( 5,0) + {-1.0968610e-004, -5.9902330e-005}, // ( 5,1/sin(pi/3)) + {+1.0778190e-004, +1.5771600e-004}, // ( 5,2/sin(pi/3)) + {-6.8980940e-005, -3.7626460e-004}, // ( 5,3/sin(pi/3)) + {-1.6166130e-004, +9.7457770e-004}, // ( 5,4/sin(pi/3)) + {+9.9476940e-006, +0.0000000e+000}, // ( 6,0) + {-1.0956820e-005, -2.9508800e-006}, // ( 6,1/sin(pi/3)) + {+1.4709070e-005, +8.1042090e-006}, // ( 6,2/sin(pi/3)) + {-2.4446020e-005, -2.0638140e-005}, // ( 6,3/sin(pi/3)) + {+7.4921290e-007, +0.0000000e+000}, // ( 7,0) + {-8.4619070e-007, -3.6807340e-008}, // ( 7,1/sin(pi/3)) + {+1.2183960e-006, +8.3589200e-008} // ( 7,2/sin(pi/3)) + }; ////////////////////////////////////////////////////////////////////////// // This array APV[] is the derivative of the Airy function for Ai'(a) // ////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////// Center of expansion /////////////////////////////////////////////////// ( Real, Imaginary) - APV[0] = complex(+3.4593550e-001, +0.0000000e+000);// (-6,0) - APV[1] = complex(+4.1708880e+000, +6.2414440e+000);// (-6,1/sin(pi/3)) - APV[2] = complex(+3.2719280e-001, +0.0000000e+000);// (-5,0) - APV[3] = complex(+1.0828740e+000, -5.4928300e+000);// (-5,1/sin(pi/3)) - APV[4] = complex(-2.3363520e+001, -7.4901850e+001);// (-5,2/sin(pi/3)) - APV[5] = complex(-1.0264880e+003, -5.6707940e+002);// (-5,3/sin(pi/3)) - APV[6] = complex(-7.9062860e-001, +0.0000000e+000);// (-4,0) - APV[7] = complex(-3.8085830e+000, +1.5129610e+000);// (-4,1/sin(pi/3)) - APV[8] = complex(-2.6086380e+001, +3.5540710e+001);// (-4,2/sin(pi/3)) - APV[9] = complex(+1.0761840e+002, +5.1239940e+002);// (-4,3/sin(pi/3)) - APV[10] = complex(+6.6597800e+003, +1.8096190e+003);// (-4,4/sin(pi/3)) - APV[11] = complex(+3.1458380e-001, +0.0000000e+000);// (-3,0) - APV[12] = complex(+1.8715430e+000, +2.0544840e+000);// (-3,1/sin(pi/3)) - APV[13] = complex(+2.2591740e+001, +4.8563000e+000);// (-3,2/sin(pi/3)) - APV[14] = complex(+1.6163000e+002, -1.4335600e+002);// (-3,3/sin(pi/3)) - APV[15] = complex(-8.0047160e+002, -2.1527450e+003);// (-3,4/sin(pi/3)) - APV[16] = complex(+6.1825900e-001, +0.0000000e+000);// (-2,0) - APV[17] = complex(+1.3019600e+000, -1.2290770e+000);// (-2,1/sin(pi/3)) - APV[18] = complex(+1.5036120e-001, -1.1008090e+001);// (-2,2/sin(pi/3)) - APV[19] = complex(-7.0116800e+001, -4.0480820e+001);// (-2,3/sin(pi/3)) - APV[20] = complex(-4.8317170e+002, +4.9692760e+002);// (-2,4/sin(pi/3)) - APV[21] = complex(+4.8970660e+003, +4.8627290e+003);// (-2,5/sin(pi/3)) - APV[22] = complex(-1.0160570e-002, +0.0000000e+000);// (-1,0) - APV[23] = complex(-5.4826640e-001, -7.1365290e-001);// (-1,1/sin(pi/3)) - APV[24] = complex(-4.6749130e+000, -1.1924250e-001);// (-1,2/sin(pi/3)) - APV[25] = complex(-1.0536400e+001, +2.4943710e+001);// (-1,3/sin(pi/3)) - APV[26] = complex(+1.6333770e+002, +9.0394910e+001);// (-1,4/sin(pi/3)) - APV[27] = complex(+5.6449460e+002, -1.4248320e+003);// (-1,5/sin(pi/3)) - APV[28] = complex(-2.5881940e-001, +0.0000000e+000);// ( 0,0) - APV[29] = complex(-4.8620750e-001, +1.5689920e-001);// ( 0,1/sin(pi/3)) - APV[30] = complex(-4.7348130e-001, +1.7093440e+000);// ( 0,2/sin(pi/3)) - APV[31] = complex(+7.0373840e+000, +3.6281820e+000);// ( 0,3/sin(pi/3)) - APV[32] = complex(+1.7739590e+001, -4.0360420e+001);// ( 0,4/sin(pi/3)) - APV[33] = complex(-2.9791510e+002, -3.8408890e+001);// ( 0,5/sin(pi/3)) - APV[34] = complex(-1.5914740e-001, +0.0000000e+000);// ( 1,0) - APV[35] = complex(-1.1340420e-001, +1.9730500e-001);// ( 1,1/sin(pi/3)) - APV[36] = complex(+4.0126210e-001, +3.9223000e-001);// ( 1,2/sin(pi/3)) - APV[37] = complex(+1.3348650e+000, -1.4377270e+000);// ( 1,3/sin(pi/3)) - APV[38] = complex(-7.9022490e+000, -4.2063640e+000);// ( 1,4/sin(pi/3)) - APV[39] = complex(-1.3892750e+000, +5.1229420e+001);// ( 1,5/sin(pi/3)) - APV[40] = complex(-5.3090380e-002, +0.0000000e+000);// ( 2,0) - APV[41] = complex(-1.6832970e-003, +6.8366970e-002);// ( 2,1/sin(pi/3)) - APV[42] = complex(+1.3789400e-001, -1.1613800e-002);// ( 2,2/sin(pi/3)) - APV[43] = complex(-1.4713730e-001, -3.7151990e-001);// ( 2,3/sin(pi/3)) - APV[44] = complex(-1.0070200e+000, +1.1591350e+000);// ( 2,4/sin(pi/3)) - APV[45] = complex(+7.5045050e+000, +4.6913120e-001);// ( 2,5/sin(pi/3)) - APV[46] = complex(-1.1912980e-002, +0.0000000e+000);// ( 3,0) - APV[47] = complex(+5.1468570e-003, +1.3660890e-002);// ( 3,1/sin(pi/3)) - APV[48] = complex(+1.8309710e-002, -1.8808590e-002);// ( 3,2/sin(pi/3)) - APV[49] = complex(-6.4461590e-002, -1.3611790e-002);// ( 3,3/sin(pi/3)) - APV[50] = complex(+1.0516240e-001, +1.9313050e-001);// ( 3,4/sin(pi/3)) - APV[51] = complex(+2.0520050e-001, -9.1772620e-001);// ( 3,5/sin(pi/3)) - APV[52] = complex(-1.9586410e-003, +0.0000000e+000);// ( 4,0) - APV[53] = complex(+1.4695650e-003, +1.8086380e-003);// ( 4,1/sin(pi/3)) - APV[54] = complex(+5.9709950e-004, -3.8332700e-003);// ( 4,2/sin(pi/3)) - APV[55] = complex(-6.8910890e-003, +5.4467430e-003);// ( 4,3/sin(pi/3)) - APV[56] = complex(+2.6167930e-002, -8.4092000e-004);// ( 4,4/sin(pi/3)) - APV[57] = complex(-8.8284470e-002, -4.6475310e-002);// ( 4,5/sin(pi/3)) - APV[58] = complex(-2.4741390e-004, +0.0000000e+000);// ( 5,0) - APV[59] = complex(+2.3707840e-004, +1.6461110e-004);// ( 5,1/sin(pi/3)) - APV[60] = complex(-1.7465570e-004, -4.2026780e-004);// ( 5,2/sin(pi/3)) - APV[61] = complex(-1.0394520e-004, +9.4761840e-004);// ( 5,3/sin(pi/3)) - APV[62] = complex(+1.3004110e-003, -2.2446660e-003);// ( 5,4/sin(pi/3)) - APV[63] = complex(-2.4765200e-005, +0.0000000e+000);// ( 6,0) - APV[64] = complex(+2.6714870e-005, +9.8691570e-006);// ( 6,1/sin(pi/3)) - APV[65] = complex(-3.3539770e-005, -2.7113280e-005);// ( 6,2/sin(pi/3)) - APV[66] = complex(+4.9197840e-005, +6.9349090e-005);// ( 6,3/sin(pi/3)) - APV[67] = complex(-2.0081510e-006, +0.0000000e+000);// ( 7,0) - APV[68] = complex(+2.2671240e-006, +2.7848510e-007);// ( 7,1/sin(pi/3)) - APV[69] = complex(-3.2692130e-006, -7.3943490e-007);// ( 7,2/sin(pi/3)) + // Complex array of the value Ai'(a) derivative of the Airy function + // where a is the complex location of the center of expansion of the Taylor series + // presumably for Ai'[z] + constexpr std::complex APV[] = { + {+3.4593550e-001, +0.0000000e+000}, // (-6,0) + {+4.1708880e+000, +6.2414440e+000}, // (-6,1/sin(pi/3)) + {+3.2719280e-001, +0.0000000e+000}, // (-5,0) + {+1.0828740e+000, -5.4928300e+000}, // (-5,1/sin(pi/3)) + {-2.3363520e+001, -7.4901850e+001}, // (-5,2/sin(pi/3)) + {-1.0264880e+003, -5.6707940e+002}, // (-5,3/sin(pi/3)) + {-7.9062860e-001, +0.0000000e+000}, // (-4,0) + {-3.8085830e+000, +1.5129610e+000}, // (-4,1/sin(pi/3)) + {-2.6086380e+001, +3.5540710e+001}, // (-4,2/sin(pi/3)) + {+1.0761840e+002, +5.1239940e+002}, // (-4,3/sin(pi/3)) + {+6.6597800e+003, +1.8096190e+003}, // (-4,4/sin(pi/3)) + {+3.1458380e-001, +0.0000000e+000}, // (-3,0) + {+1.8715430e+000, +2.0544840e+000}, // (-3,1/sin(pi/3)) + {+2.2591740e+001, +4.8563000e+000}, // (-3,2/sin(pi/3)) + {+1.6163000e+002, -1.4335600e+002}, // (-3,3/sin(pi/3)) + {-8.0047160e+002, -2.1527450e+003}, // (-3,4/sin(pi/3)) + {+6.1825900e-001, +0.0000000e+000}, // (-2,0) + {+1.3019600e+000, -1.2290770e+000}, // (-2,1/sin(pi/3)) + {+1.5036120e-001, -1.1008090e+001}, // (-2,2/sin(pi/3)) + {-7.0116800e+001, -4.0480820e+001}, // (-2,3/sin(pi/3)) + {-4.8317170e+002, +4.9692760e+002}, // (-2,4/sin(pi/3)) + {+4.8970660e+003, +4.8627290e+003}, // (-2,5/sin(pi/3)) + {-1.0160570e-002, +0.0000000e+000}, // (-1,0) + {-5.4826640e-001, -7.1365290e-001}, // (-1,1/sin(pi/3)) + {-4.6749130e+000, -1.1924250e-001}, // (-1,2/sin(pi/3)) + {-1.0536400e+001, +2.4943710e+001}, // (-1,3/sin(pi/3)) + {+1.6333770e+002, +9.0394910e+001}, // (-1,4/sin(pi/3)) + {+5.6449460e+002, -1.4248320e+003}, // (-1,5/sin(pi/3)) + {-2.5881940e-001, +0.0000000e+000}, // ( 0,0) + {-4.8620750e-001, +1.5689920e-001}, // ( 0,1/sin(pi/3)) + {-4.7348130e-001, +1.7093440e+000}, // ( 0,2/sin(pi/3)) + {+7.0373840e+000, +3.6281820e+000}, // ( 0,3/sin(pi/3)) + {+1.7739590e+001, -4.0360420e+001}, // ( 0,4/sin(pi/3)) + {-2.9791510e+002, -3.8408890e+001}, // ( 0,5/sin(pi/3)) + {-1.5914740e-001, +0.0000000e+000}, // ( 1,0) + {-1.1340420e-001, +1.9730500e-001}, // ( 1,1/sin(pi/3)) + {+4.0126210e-001, +3.9223000e-001}, // ( 1,2/sin(pi/3)) + {+1.3348650e+000, -1.4377270e+000}, // ( 1,3/sin(pi/3)) + {-7.9022490e+000, -4.2063640e+000}, // ( 1,4/sin(pi/3)) + {-1.3892750e+000, +5.1229420e+001}, // ( 1,5/sin(pi/3)) + {-5.3090380e-002, +0.0000000e+000}, // ( 2,0) + {-1.6832970e-003, +6.8366970e-002}, // ( 2,1/sin(pi/3)) + {+1.3789400e-001, -1.1613800e-002}, // ( 2,2/sin(pi/3)) + {-1.4713730e-001, -3.7151990e-001}, // ( 2,3/sin(pi/3)) + {-1.0070200e+000, +1.1591350e+000}, // ( 2,4/sin(pi/3)) + {+7.5045050e+000, +4.6913120e-001}, // ( 2,5/sin(pi/3)) + {-1.1912980e-002, +0.0000000e+000}, // ( 3,0) + {+5.1468570e-003, +1.3660890e-002}, // ( 3,1/sin(pi/3)) + {+1.8309710e-002, -1.8808590e-002}, // ( 3,2/sin(pi/3)) + {-6.4461590e-002, -1.3611790e-002}, // ( 3,3/sin(pi/3)) + {+1.0516240e-001, +1.9313050e-001}, // ( 3,4/sin(pi/3)) + {+2.0520050e-001, -9.1772620e-001}, // ( 3,5/sin(pi/3)) + {-1.9586410e-003, +0.0000000e+000}, // ( 4,0) + {+1.4695650e-003, +1.8086380e-003}, // ( 4,1/sin(pi/3)) + {+5.9709950e-004, -3.8332700e-003}, // ( 4,2/sin(pi/3)) + {-6.8910890e-003, +5.4467430e-003}, // ( 4,3/sin(pi/3)) + {+2.6167930e-002, -8.4092000e-004}, // ( 4,4/sin(pi/3)) + {-8.8284470e-002, -4.6475310e-002}, // ( 4,5/sin(pi/3)) + {-2.4741390e-004, +0.0000000e+000}, // ( 5,0) + {+2.3707840e-004, +1.6461110e-004}, // ( 5,1/sin(pi/3)) + {-1.7465570e-004, -4.2026780e-004}, // ( 5,2/sin(pi/3)) + {-1.0394520e-004, +9.4761840e-004}, // ( 5,3/sin(pi/3)) + {+1.3004110e-003, -2.2446660e-003}, // ( 5,4/sin(pi/3)) + {-2.4765200e-005, +0.0000000e+000}, // ( 6,0) + {+2.6714870e-005, +9.8691570e-006}, // ( 6,1/sin(pi/3)) + {-3.3539770e-005, -2.7113280e-005}, // ( 6,2/sin(pi/3)) + {+4.9197840e-005, +6.9349090e-005}, // ( 6,3/sin(pi/3)) + {-2.0081510e-006, +0.0000000e+000}, // ( 7,0) + {+2.2671240e-006, +2.7848510e-007}, // ( 7,1/sin(pi/3)) + {-3.2692130e-006, -7.3943490e-007}, // ( 7,2/sin(pi/3)) + }; ///////////////////////////////////////////////////////////////////////// // Complex array of the value Bi(a) Airy function // where a is the complex location of the center of expansion of the Taylor series - complex BV[70]; - - // Complex array of the value Bi'(a) derivative of the Airy function - // where a is the complex location of the center of expansion of the Taylor series - // presumably for Ai'[z] - complex BPV[70]; - - BV[0] = complex(-1.466984e-001, -9.813078e-017);// (-6,0) - BV[1] = complex(-1.489391e+000, -2.660635e+000);// (-6,1/sin(pi/3)) - BV[2] = complex(-1.383691e-001, +0.000000e+000);// (-5,0) - BV[3] = complex(-7.034482e-001, +2.384547e+000);// (-5,1/sin(pi/3)) - BV[4] = complex(+3.460723e+000, +3.363363e+001);// (-5,2/sin(pi/3)) - BV[5] = complex(+3.369090e+002, +3.444973e+002);// (-5,3/sin(pi/3)) - BV[6] = complex(+3.922347e-001, -1.041605e-016);// (-4,0) - BV[7] = complex(+1.956219e+000, -5.327226e-001);// (-4,1/sin(pi/3)) - BV[8] = complex(+1.602464e+001, -1.338050e+001);// (-4,2/sin(pi/3)) - BV[9] = complex(+3.207239e+001, -2.296777e+002);// (-4,3/sin(pi/3)) - BV[10] = complex(-2.191768e+003, -1.804078e+003);// (-4,4/sin(pi/3)) - BV[11] = complex(-1.982896e-001, +4.440892e-016);// (-3,0) - BV[12] = complex(-8.880754e-001, -1.308713e+000);// (-3,1/sin(pi/3)) - BV[13] = complex(-1.062975e+001, -6.044056e+000);// (-3,2/sin(pi/3)) - BV[14] = complex(-9.881405e+001, +3.116914e+001);// (-3,3/sin(pi/3)) - BV[15] = complex(-1.390528e+002, +9.892534e+002);// (-3,4/sin(pi/3)) - BV[16] = complex(-4.123026e-001, +1.451806e-016);// (-2,0) - BV[17] = complex(-1.034766e+000, +6.541962e-001);// (-2,1/sin(pi/3)) - BV[18] = complex(-2.720266e+000, +6.048328e+000);// (-2,2/sin(pi/3)) - BV[19] = complex(+2.096300e+001, +3.630613e+001);// (-2,3/sin(pi/3)) - BV[20] = complex(+3.090465e+002, -6.713963e+001);// (-2,4/sin(pi/3)) - BV[21] = complex(-4.664937e+002, -2.800165e+003);// (-2,5/sin(pi/3)) - BV[22] = complex(+1.039974e-001, +0.000000e+000);// (-1,0) - BV[23] = complex(+2.797458e-001, +8.086491e-001);// (-1,1/sin(pi/3)) - BV[24] = complex(+2.606133e+000, +1.870297e+000);// (-1,2/sin(pi/3)) - BV[25] = complex(+1.292648e+001, -7.213647e+000);// (-1,3/sin(pi/3)) - BV[26] = complex(-3.208774e+001, -8.178697e+001);// (-1,4/sin(pi/3)) - BV[27] = complex(-5.692218e+002, +2.993394e+002);// (-1,5/sin(pi/3)) - BV[28] = complex(+6.149266e-001, +0.000000e+000);// ( 0,0) - BV[29] = complex(+6.732023e-001, +3.575876e-001);// ( 0,1/sin(pi/3)) - BV[30] = complex(+1.125057e+000, -4.471292e-001);// ( 0,2/sin(pi/3)) - BV[31] = complex(-1.211148e+000, -4.191469e+000);// ( 0,3/sin(pi/3)) - BV[32] = complex(-1.960240e+001, +7.182663e+000);// ( 0,4/sin(pi/3)) - BV[33] = complex(+7.595175e+001, +1.012911e+002);// ( 0,5/sin(pi/3)) - BV[34] = complex(+1.207424e+000, +0.000000e+000);// ( 1,0) - BV[35] = complex(+5.951440e-001, +6.156664e-001);// ( 1,1/sin(pi/3)) - BV[36] = complex(-1.002325e-001, -1.338228e-001);// ( 1,2/sin(pi/3)) - BV[37] = complex(-1.089323e+000, -2.019524e-001);// ( 1,3/sin(pi/3)) - BV[38] = complex(+7.047139e-001, +4.091592e+000);// ( 1,4/sin(pi/3)) - BV[39] = complex(+1.691067e+001, -1.302705e+001);// ( 1,5/sin(pi/3)) - BV[40] = complex(+3.298095e+000, +0.000000e+000);// ( 2,0) - BV[41] = complex(+2.244706e-001, +2.421124e+000);// ( 2,1/sin(pi/3)) - BV[42] = complex(-1.199515e+000, -1.167656e-001);// ( 2,2/sin(pi/3)) - BV[43] = complex(+6.781072e-003, -2.225418e-001);// ( 2,3/sin(pi/3)) - BV[44] = complex(+7.470822e-001, +1.832986e-001);// ( 2,4/sin(pi/3)) - BV[45] = complex(-1.590993e+000, -2.617694e+000);// ( 2,5/sin(pi/3)) - BV[46] = complex(+1.403733e+001, +0.000000e+000);// ( 3,0) - BV[47] = complex(-3.731398e+000, +1.066394e+001);// ( 3,1/sin(pi/3)) - BV[48] = complex(-4.440986e+000, -4.309647e+000);// ( 3,2/sin(pi/3)) - BV[49] = complex(+2.373933e+000, -5.300179e-001);// ( 3,3/sin(pi/3)) - BV[50] = complex(-2.821481e-001, +5.657373e-001);// ( 3,4/sin(pi/3)) - BV[51] = complex(-3.904913e-001, -5.168316e-002);// ( 3,5/sin(pi/3)) - BV[52] = complex(+8.384707e+001, +0.000000e+000);// ( 4,0) - BV[53] = complex(-4.356467e+001, +5.497027e+001);// ( 4,1/sin(pi/3)) - BV[54] = complex(-7.156364e+000, -4.113550e+001);// ( 4,2/sin(pi/3)) - BV[55] = complex(+1.455852e+001, +1.109071e+001);// ( 4,3/sin(pi/3)) - BV[56] = complex(-6.111359e+000, -1.094609e-001);// ( 4,4/sin(pi/3)) - BV[57] = complex(+1.403434e+000, -7.255043e-001);// ( 4,5/sin(pi/3)) - BV[58] = complex(+6.577920e+002, +0.000000e+000);// ( 5,0) - BV[59] = complex(-4.598656e+002, +3.242259e+002);// ( 5,1/sin(pi/3)) - BV[60] = complex(+1.324505e+002, -3.294705e+002);// ( 5,2/sin(pi/3)) - BV[61] = complex(+2.057579e+001, +1.674034e+002);// ( 5,3/sin(pi/3)) - BV[62] = complex(-3.161505e+001, -5.302141e+001);// ( 5,4/sin(pi/3)) - BV[63] = complex(+6.536446e+003, +0.000000e+000);// ( 6,0) - BV[64] = complex(-5.316522e+003, +1.992175e+003);// ( 6,1/sin(pi/3)) - BV[65] = complex(+2.888529e+003, -2.373473e+003);// ( 6,2/sin(pi/3)) - BV[66] = complex(-1.078657e+003, +1.551931e+003);// ( 6,3/sin(pi/3)) - BV[67] = complex(+8.032779e+004, +0.000000e+000);// ( 7,0) - BV[68] = complex(-7.001987e+004, +8.828416e+003);// ( 7,1/sin(pi/3)) - BV[69] = complex(+4.676699e+004, -1.085924e+004);// ( 7,2/sin(pi/3)) + constexpr std::complex BV[] = { + {-1.466984e-001, -9.813078e-017}, // (-6,0) + {-1.489391e+000, -2.660635e+000}, // (-6,1/sin(pi/3)) + {-1.383691e-001, +0.000000e+000}, // (-5,0) + {-7.034482e-001, +2.384547e+000}, // (-5,1/sin(pi/3)) + {+3.460723e+000, +3.363363e+001}, // (-5,2/sin(pi/3)) + {+3.369090e+002, +3.444973e+002}, // (-5,3/sin(pi/3)) + {+3.922347e-001, -1.041605e-016}, // (-4,0) + {+1.956219e+000, -5.327226e-001}, // (-4,1/sin(pi/3)) + {+1.602464e+001, -1.338050e+001}, // (-4,2/sin(pi/3)) + {+3.207239e+001, -2.296777e+002}, // (-4,3/sin(pi/3)) + {-2.191768e+003, -1.804078e+003}, // (-4,4/sin(pi/3)) + {-1.982896e-001, +4.440892e-016}, // (-3,0) + {-8.880754e-001, -1.308713e+000}, // (-3,1/sin(pi/3)) + {-1.062975e+001, -6.044056e+000}, // (-3,2/sin(pi/3)) + {-9.881405e+001, +3.116914e+001}, // (-3,3/sin(pi/3)) + {-1.390528e+002, +9.892534e+002}, // (-3,4/sin(pi/3)) + {-4.123026e-001, +1.451806e-016}, // (-2,0) + {-1.034766e+000, +6.541962e-001}, // (-2,1/sin(pi/3)) + {-2.720266e+000, +6.048328e+000}, // (-2,2/sin(pi/3)) + {+2.096300e+001, +3.630613e+001}, // (-2,3/sin(pi/3)) + {+3.090465e+002, -6.713963e+001}, // (-2,4/sin(pi/3)) + {-4.664937e+002, -2.800165e+003}, // (-2,5/sin(pi/3)) + {+1.039974e-001, +0.000000e+000}, // (-1,0) + {+2.797458e-001, +8.086491e-001}, // (-1,1/sin(pi/3)) + {+2.606133e+000, +1.870297e+000}, // (-1,2/sin(pi/3)) + {+1.292648e+001, -7.213647e+000}, // (-1,3/sin(pi/3)) + {-3.208774e+001, -8.178697e+001}, // (-1,4/sin(pi/3)) + {-5.692218e+002, +2.993394e+002}, // (-1,5/sin(pi/3)) + {+6.149266e-001, +0.000000e+000}, // ( 0,0) + {+6.732023e-001, +3.575876e-001}, // ( 0,1/sin(pi/3)) + {+1.125057e+000, -4.471292e-001}, // ( 0,2/sin(pi/3)) + {-1.211148e+000, -4.191469e+000}, // ( 0,3/sin(pi/3)) + {-1.960240e+001, +7.182663e+000}, // ( 0,4/sin(pi/3)) + {+7.595175e+001, +1.012911e+002}, // ( 0,5/sin(pi/3)) + {+1.207424e+000, +0.000000e+000}, // ( 1,0) + {+5.951440e-001, +6.156664e-001}, // ( 1,1/sin(pi/3)) + {-1.002325e-001, -1.338228e-001}, // ( 1,2/sin(pi/3)) + {-1.089323e+000, -2.019524e-001}, // ( 1,3/sin(pi/3)) + {+7.047139e-001, +4.091592e+000}, // ( 1,4/sin(pi/3)) + {+1.691067e+001, -1.302705e+001}, // ( 1,5/sin(pi/3)) + {+3.298095e+000, +0.000000e+000}, // ( 2,0) + {+2.244706e-001, +2.421124e+000}, // ( 2,1/sin(pi/3)) + {-1.199515e+000, -1.167656e-001}, // ( 2,2/sin(pi/3)) + {+6.781072e-003, -2.225418e-001}, // ( 2,3/sin(pi/3)) + {+7.470822e-001, +1.832986e-001}, // ( 2,4/sin(pi/3)) + {-1.590993e+000, -2.617694e+000}, // ( 2,5/sin(pi/3)) + {+1.403733e+001, +0.000000e+000}, // ( 3,0) + {-3.731398e+000, +1.066394e+001}, // ( 3,1/sin(pi/3)) + {-4.440986e+000, -4.309647e+000}, // ( 3,2/sin(pi/3)) + {+2.373933e+000, -5.300179e-001}, // ( 3,3/sin(pi/3)) + {-2.821481e-001, +5.657373e-001}, // ( 3,4/sin(pi/3)) + {-3.904913e-001, -5.168316e-002}, // ( 3,5/sin(pi/3)) + {+8.384707e+001, +0.000000e+000}, // ( 4,0) + {-4.356467e+001, +5.497027e+001}, // ( 4,1/sin(pi/3)) + {-7.156364e+000, -4.113550e+001}, // ( 4,2/sin(pi/3)) + {+1.455852e+001, +1.109071e+001}, // ( 4,3/sin(pi/3)) + {-6.111359e+000, -1.094609e-001}, // ( 4,4/sin(pi/3)) + {+1.403434e+000, -7.255043e-001}, // ( 4,5/sin(pi/3)) + {+6.577920e+002, +0.000000e+000}, // ( 5,0) + {-4.598656e+002, +3.242259e+002}, // ( 5,1/sin(pi/3)) + {+1.324505e+002, -3.294705e+002}, // ( 5,2/sin(pi/3)) + {+2.057579e+001, +1.674034e+002}, // ( 5,3/sin(pi/3)) + {-3.161505e+001, -5.302141e+001}, // ( 5,4/sin(pi/3)) + {+6.536446e+003, +0.000000e+000}, // ( 6,0) + {-5.316522e+003, +1.992175e+003}, // ( 6,1/sin(pi/3)) + {+2.888529e+003, -2.373473e+003}, // ( 6,2/sin(pi/3)) + {-1.078657e+003, +1.551931e+003}, // ( 6,3/sin(pi/3)) + {+8.032779e+004, +0.000000e+000}, // ( 7,0) + {-7.001987e+004, +8.828416e+003}, // ( 7,1/sin(pi/3)) + {+4.676699e+004, -1.085924e+004} // ( 7,2/sin(pi/3)) + }; ////////////////////////////////////////////////////////////// - BPV[0] = complex(-8.128988e-001, +3.365185e-016);// (-6,0) - BPV[1] = complex(-6.287609e+000, +4.146176e+000);// (-6,1/sin(pi/3)) - BPV[2] = complex(+7.784118e-001, -3.004629e-016);// (-5,0) - BPV[3] = complex(+5.554036e+000, +1.063645e+000);// (-5,1/sin(pi/3)) - BPV[4] = complex(+7.490659e+001, -2.336310e+001);// (-5,2/sin(pi/3)) - BPV[5] = complex(+5.670796e+002, -1.026488e+003);// (-5,3/sin(pi/3)) - BPV[6] = complex(-1.166706e-001, +2.371654e-015);// (-4,0) - BPV[7] = complex(-1.532413e+000, -3.730947e+000);// (-4,1/sin(pi/3)) - BPV[8] = complex(-3.554558e+001, -2.608034e+001);// (-4,2/sin(pi/3)) - BPV[9] = complex(-5.124001e+002, +1.076185e+002);// (-4,3/sin(pi/3)) - BPV[10] = complex(-1.809619e+003, +6.659780e+003);// (-4,4/sin(pi/3)) - BPV[11] = complex(-6.756112e-001, -2.403703e-017);// (-3,0) - BPV[12] = complex(-2.142202e+000, +1.818610e+000);// (-3,1/sin(pi/3)) - BPV[13] = complex(-4.863137e+000, +2.258023e+001);// (-3,2/sin(pi/3)) - BPV[14] = complex(+1.433564e+002, +1.616285e+002);// (-3,3/sin(pi/3)) - BPV[15] = complex(+2.152746e+003, -8.004716e+002);// (-3,4/sin(pi/3)) - BPV[16] = complex(+2.787952e-001, +0.000000e+000);// (-2,0) - BPV[17] = complex(+1.300360e+000, +1.185229e+000);// (-2,1/sin(pi/3)) - BPV[18] = complex(+1.103082e+001, +1.397575e-001);// (-2,2/sin(pi/3)) - BPV[19] = complex(+4.048422e+001, -7.011484e+001);// (-2,3/sin(pi/3)) - BPV[20] = complex(-4.969277e+002, -4.831712e+002);// (-2,4/sin(pi/3)) - BPV[21] = complex(-4.862729e+003, +4.897066e+003);// (-2,5/sin(pi/3)) - BPV[22] = complex(+5.923756e-001, +0.000000e+000);// (-1,0) - BPV[23] = complex(+9.080502e-001, -5.080757e-001);// (-1,1/sin(pi/3)) - BPV[24] = complex(+1.499485e-001, -4.631403e+000);// (-1,2/sin(pi/3)) - BPV[25] = complex(-2.494926e+001, -1.052676e+001);// (-1,3/sin(pi/3)) - BPV[26] = complex(-9.039663e+001, +1.633370e+002);// (-1,4/sin(pi/3)) - BPV[27] = complex(+1.424833e+003, +5.644943e+002);// (-1,5/sin(pi/3)) - BPV[28] = complex(+4.482884e-001, +0.000000e+000);// ( 0,0) - BPV[29] = complex(+2.493288e-002, -1.876446e-001);// ( 0,1/sin(pi/3)) - BPV[30] = complex(-1.774795e+000, -3.533830e-001);// ( 0,2/sin(pi/3)) - BPV[31] = complex(-3.663891e+000, +7.026174e+000);// ( 0,3/sin(pi/3)) - BPV[32] = complex(+4.036322e+001, +1.773235e+001);// ( 0,4/sin(pi/3)) - BPV[33] = complex(+3.840990e+001, -2.979143e+002);// ( 0,5/sin(pi/3)) - BPV[34] = complex(+9.324359e-001, +0.000000e+000);// ( 1,0) - BPV[35] = complex(-1.293870e-001, +7.817697e-001);// ( 1,1/sin(pi/3)) - BPV[36] = complex(-8.385825e-001, +4.901385e-001);// ( 1,2/sin(pi/3)) - BPV[37] = complex(+1.421331e+000, +1.181168e+000);// ( 1,3/sin(pi/3)) - BPV[38] = complex(+4.244380e+000, -7.895016e+000);// ( 1,4/sin(pi/3)) - BPV[39] = complex(-5.123410e+001, -1.383387e+000);// ( 1,5/sin(pi/3)) - BPV[40] = complex(+4.100682e+000, +0.000000e+000);// ( 2,0) - BPV[41] = complex(-9.576171e-001, +3.432468e+000);// ( 2,1/sin(pi/3)) - BPV[42] = complex(-1.747487e+000, -8.602854e-001);// ( 2,2/sin(pi/3)) - BPV[43] = complex(+9.978890e-001, -6.434913e-001);// ( 2,3/sin(pi/3)) - BPV[44] = complex(-1.127841e+000, -7.762214e-001);// ( 2,4/sin(pi/3)) - BPV[45] = complex(-5.136144e-001, +7.476880e+000);// ( 2,5/sin(pi/3)) - BPV[46] = complex(+2.292221e+001, +0.000000e+000);// ( 3,0) - BPV[47] = complex(-1.021000e+001, +1.662556e+001);// ( 3,1/sin(pi/3)) - BPV[48] = complex(-5.018884e+000, -1.067168e+001);// ( 3,2/sin(pi/3)) - BPV[49] = complex(+5.067979e+000, +1.074279e+000);// ( 3,3/sin(pi/3)) - BPV[50] = complex(-1.620678e+000, +1.029461e+000);// ( 3,4/sin(pi/3)) - BPV[51] = complex(+1.055970e+000, -2.041230e-001);// ( 3,5/sin(pi/3)) - BPV[52] = complex(+1.619267e+002, +0.000000e+000);// ( 4,0) - BPV[53] = complex(-1.021827e+002, +9.434616e+001);// ( 4,1/sin(pi/3)) - BPV[54] = complex(+9.638391e+000, -8.764529e+001);// ( 4,2/sin(pi/3)) - BPV[55] = complex(+2.157904e+001, +3.568852e+001);// ( 4,3/sin(pi/3)) - BPV[56] = complex(-1.346647e+001, -6.665778e+000);// ( 4,4/sin(pi/3)) - BPV[57] = complex(+4.276735e+000, -9.657825e-002);// ( 4,5/sin(pi/3)) - BPV[58] = complex(+1.435819e+003, +0.000000e+000);// ( 5,0) - BPV[59] = complex(-1.099383e+003, +5.897525e+002);// ( 5,1/sin(pi/3)) - BPV[60] = complex(+4.709887e+002, -6.717565e+002);// ( 5,2/sin(pi/3)) - BPV[61] = complex(-7.965464e+001, +4.040825e+002);// ( 5,3/sin(pi/3)) - BPV[62] = complex(-2.418965e+001, -1.582957e+002);// ( 5,4/sin(pi/3)) - BPV[63] = complex(+1.572560e+004, +0.000000e+000);// ( 6,0) - BPV[64] = complex(-1.334470e+004, +3.525428e+003);// ( 6,1/sin(pi/3)) - BPV[65] = complex(+8.229089e+003, -4.446372e+003);// ( 6,2/sin(pi/3)) - BPV[66] = complex(-3.795705e+003, +3.141147e+003);// ( 6,3/sin(pi/3)) - BPV[67] = complex(+2.095527e+005, +0.000000e+000);// ( 7,0) - BPV[68] = complex(-1.853403e+005, +7.452520e+003);// ( 7,1/sin(pi/3)) - BPV[69] = complex(+1.286235e+005, -8.069218e+003);// ( 7,2/sin(pi/3)) + // Complex array of the value Bi'(a) derivative of the Airy function + // where a is the complex location of the center of expansion of the Taylor series + // presumably for Ai'[z] + constexpr std::complex BPV[] = { + {-8.128988e-001, +3.365185e-016}, // (-6,0) + {-6.287609e+000, +4.146176e+000}, // (-6,1/sin(pi/3)) + {+7.784118e-001, -3.004629e-016}, // (-5,0) + {+5.554036e+000, +1.063645e+000}, // (-5,1/sin(pi/3)) + {+7.490659e+001, -2.336310e+001}, // (-5,2/sin(pi/3)) + {+5.670796e+002, -1.026488e+003}, // (-5,3/sin(pi/3)) + {-1.166706e-001, +2.371654e-015}, // (-4,0) + {-1.532413e+000, -3.730947e+000}, // (-4,1/sin(pi/3)) + {-3.554558e+001, -2.608034e+001}, // (-4,2/sin(pi/3)) + {-5.124001e+002, +1.076185e+002}, // (-4,3/sin(pi/3)) + {-1.809619e+003, +6.659780e+003}, // (-4,4/sin(pi/3)) + {-6.756112e-001, -2.403703e-017}, // (-3,0) + {-2.142202e+000, +1.818610e+000}, // (-3,1/sin(pi/3)) + {-4.863137e+000, +2.258023e+001}, // (-3,2/sin(pi/3)) + {+1.433564e+002, +1.616285e+002}, // (-3,3/sin(pi/3)) + {+2.152746e+003, -8.004716e+002}, // (-3,4/sin(pi/3)) + {+2.787952e-001, +0.000000e+000}, // (-2,0) + {+1.300360e+000, +1.185229e+000}, // (-2,1/sin(pi/3)) + {+1.103082e+001, +1.397575e-001}, // (-2,2/sin(pi/3)) + {+4.048422e+001, -7.011484e+001}, // (-2,3/sin(pi/3)) + {-4.969277e+002, -4.831712e+002}, // (-2,4/sin(pi/3)) + {-4.862729e+003, +4.897066e+003}, // (-2,5/sin(pi/3)) + {+5.923756e-001, +0.000000e+000}, // (-1,0) + {+9.080502e-001, -5.080757e-001}, // (-1,1/sin(pi/3)) + {+1.499485e-001, -4.631403e+000}, // (-1,2/sin(pi/3)) + {-2.494926e+001, -1.052676e+001}, // (-1,3/sin(pi/3)) + {-9.039663e+001, +1.633370e+002}, // (-1,4/sin(pi/3)) + {+1.424833e+003, +5.644943e+002}, // (-1,5/sin(pi/3)) + {+4.482884e-001, +0.000000e+000}, // ( 0,0) + {+2.493288e-002, -1.876446e-001}, // ( 0,1/sin(pi/3)) + {-1.774795e+000, -3.533830e-001}, // ( 0,2/sin(pi/3)) + {-3.663891e+000, +7.026174e+000}, // ( 0,3/sin(pi/3)) + {+4.036322e+001, +1.773235e+001}, // ( 0,4/sin(pi/3)) + {+3.840990e+001, -2.979143e+002}, // ( 0,5/sin(pi/3)) + {+9.324359e-001, +0.000000e+000}, // ( 1,0) + {-1.293870e-001, +7.817697e-001}, // ( 1,1/sin(pi/3)) + {-8.385825e-001, +4.901385e-001}, // ( 1,2/sin(pi/3)) + {+1.421331e+000, +1.181168e+000}, // ( 1,3/sin(pi/3)) + {+4.244380e+000, -7.895016e+000}, // ( 1,4/sin(pi/3)) + {-5.123410e+001, -1.383387e+000}, // ( 1,5/sin(pi/3)) + {+4.100682e+000, +0.000000e+000}, // ( 2,0) + {-9.576171e-001, +3.432468e+000}, // ( 2,1/sin(pi/3)) + {-1.747487e+000, -8.602854e-001}, // ( 2,2/sin(pi/3)) + {+9.978890e-001, -6.434913e-001}, // ( 2,3/sin(pi/3)) + {-1.127841e+000, -7.762214e-001}, // ( 2,4/sin(pi/3)) + {-5.136144e-001, +7.476880e+000}, // ( 2,5/sin(pi/3)) + {+2.292221e+001, +0.000000e+000}, // ( 3,0) + {-1.021000e+001, +1.662556e+001}, // ( 3,1/sin(pi/3)) + {-5.018884e+000, -1.067168e+001}, // ( 3,2/sin(pi/3)) + {+5.067979e+000, +1.074279e+000}, // ( 3,3/sin(pi/3)) + {-1.620678e+000, +1.029461e+000}, // ( 3,4/sin(pi/3)) + {+1.055970e+000, -2.041230e-001}, // ( 3,5/sin(pi/3)) + {+1.619267e+002, +0.000000e+000}, // ( 4,0) + {-1.021827e+002, +9.434616e+001}, // ( 4,1/sin(pi/3)) + {+9.638391e+000, -8.764529e+001}, // ( 4,2/sin(pi/3)) + {+2.157904e+001, +3.568852e+001}, // ( 4,3/sin(pi/3)) + {-1.346647e+001, -6.665778e+000}, // ( 4,4/sin(pi/3)) + {+4.276735e+000, -9.657825e-002}, // ( 4,5/sin(pi/3)) + {+1.435819e+003, +0.000000e+000}, // ( 5,0) + {-1.099383e+003, +5.897525e+002}, // ( 5,1/sin(pi/3)) + {+4.709887e+002, -6.717565e+002}, // ( 5,2/sin(pi/3)) + {-7.965464e+001, +4.040825e+002}, // ( 5,3/sin(pi/3)) + {-2.418965e+001, -1.582957e+002}, // ( 5,4/sin(pi/3)) + {+1.572560e+004, +0.000000e+000}, // ( 6,0) + {-1.334470e+004, +3.525428e+003}, // ( 6,1/sin(pi/3)) + {+8.229089e+003, -4.446372e+003}, // ( 6,2/sin(pi/3)) + {-3.795705e+003, +3.141147e+003}, // ( 6,3/sin(pi/3)) + {+2.095527e+005, +0.000000e+000}, // ( 7,0) + {-1.853403e+005, +7.452520e+003}, // ( 7,1/sin(pi/3)) + {+1.286235e+005, -8.069218e+003} // ( 7,2/sin(pi/3)) + }; //////////////////////////////////////// // Validate input - kind & derivative // //////////////////////////////////////// - if ((kind != AIRY) && - (kind != BAIRY) && - (kind != WONE) && - (kind != DWONE) && - (kind != WTWO) && - (kind != DWTWO)) { - return complex(0, 0); // Airy Error: Invalid kind value + if ((kind != AIRY) && (kind != BAIRY) && (kind != WONE) && (kind != DWONE) + && (kind != WTWO) && (kind != DWTWO)) { + return std::complex(0, 0); // Airy Error: Invalid kind value }; if ((scaling != NONE) && (scaling != HUFFORD) && (scaling != WAIT)) { - return complex(0, 0); // Airy Error: Invalid scaling value + return std::complex(0, 0); // Airy Error: Invalid scaling value }; - // Set the derivative flag + // Set a flag and index value to control function flow based on whether or not + // we are finding a derivative (e.g., Ai', Bi') or not (e.g., Ai, Bi). The + // `derivative_idx` ensures that arrays are accessed correctly in each case, while + // `derivative_flag` is generally used as a condition. + bool derivative_flag; // True if finding a derivative (e.g., Ai', Bi') + int derivative_idx; // Index used to get correct values from arrays if (kind == DWTWO || kind == DWONE || kind == AIRYD || kind == BAIRYD) { - derivative = YES; - } - else { - derivative = NO; + derivative_flag = true; + derivative_idx = 1; + } else { + derivative_flag = false; + derivative_idx = 0; }; // Now do something productive with numbers... - // In the original version of this by George Hufford it calculated Ai(Z), Ai'(Z) and the + // In the original version of this by George Hufford it calculated Ai(Z), Ai'(Z) and the // Airy functions of the "3rd kind" Wi(1)(Z) and Wi(2)(Z) (see Eqn 38 in NTIA Report 87-219) - // Note: Theta in 87-219 is Z in this program. - // The input switch had three values, here we are going to have four so that the + // Note: Theta in 87-219 is Z in this program. + // The input switch had three values, here we are going to have four so that the // Bairy function doesn't feel left out. // The following scales the input parameter Z depending on what the user is trying to do. // If the user is trying to find just the Ai(Z), Ai'(Z), Bi(Z) or Bi'(Z) there is no scaling. if (kind == AIRY || kind == BAIRY || kind == AIRYD || kind == BAIRYD) { // For Ai(Z) and Bi(Z) No translation in the complex plane - U = complex(1.0, 0.0); + U = std::complex(1.0, 0.0); } // Note that W1 Wait = Wi(2) Hufford and W2 Wait = Wi(1) Hufford // So the following inequalities keep this all straight else if (((kind == DWONE || kind == WONE) && scaling == HUFFORD) - || - ((kind == DWTWO || kind == WTWO) && scaling == WAIT)) { + || ((kind == DWTWO || kind == WTWO) && scaling == WAIT)) { // This corresponds to Wi(1)(Z) in Eqn 38 Hufford NTIA Report 87-219 // or Wait W2 - U = complex(cos(2.0*PI / 3.0), sin(2.0*PI / 3.0)); - } - else if (((kind == DWTWO || kind == WTWO) && scaling == HUFFORD) - || - ((kind == DWONE || kind == WONE) && scaling == WAIT)) { + U = std::complex( + std::cos(2.0 * PI / 3.0), std::sin(2.0 * PI / 3.0) + ); + } else if (((kind == DWTWO || kind == WTWO) && scaling == HUFFORD) + || ((kind == DWONE || kind == WONE) && scaling == WAIT)) { // This corresponds to Wi(2)(Z) in Eqn 38 Hufford NTIA Report 87-219 // or Wait W1 - U = complex(cos(-2.0*PI / 3.0), sin(-2.0*PI / 3.0)); + U = std::complex( + std::cos(-2.0 * PI / 3.0), std::sin(-2.0 * PI / 3.0) + ); }; - // Translate the input parameter + // Translate the input parameter ZU = Z * U; // We will be only calculating for quadrant 1 and 2. If the desired value is in 3 or 4 we // will have to flip it over after the calculation reflection = false; if (ZU.imag() <= 0) { - reflection = true; // reflection = true means Z.imag() <= 0, use reflection formula to get result - ZU = complex(ZU.real(), -ZU.imag()); + reflection + = true; // reflection = true means Z.imag() <= 0, use reflection formula to get result + ZU = std::complex(ZU.real(), -ZU.imag()); }; - // Begin the calculation to determine if - // a) the shifted Taylor series will be used or + // Begin the calculation to determine if + // a) the shifted Taylor series will be used or // b) the Asymptotic approximation is used. // A shifted Taylor series is necessary because the Taylor series is defined with a center of expansion // at the origin is a poor approximation to the true value of the (B)Airy function. - // NOTE: The condition for which method is used is dependant on that value of Z and not the - // transformed version of ZU, which is the shifted Z by exp(+-j*2*pi/3). - // For the calculation of Ai(Z) and Bi(Z) Z is not shifted. - // For the calculation of Wi(1)(Z) and Wi(2)(Z) the value of Ai(ZU) is found. + // NOTE: The condition for which method is used is dependant on that value of Z and not the + // transformed version of ZU, which is the shifted Z by exp(+-j*2*pi/3). + // For the calculation of Ai(Z) and Bi(Z) Z is not shifted. + // For the calculation of Wi(1)(Z) and Wi(2)(Z) the value of Ai(ZU) is found. // Initialize the indexes for the center of expansion // Note these are used in the if statements below as flags @@ -557,65 +579,76 @@ complex Airy(complex Z, int kind, int scaling) // The following inequality is formed from the implicit arguments for the AV[], BV[], BPV[] and APV[] // The inequality makes sure that the center of expansion for the Taylor series solution is not - // exceeded. - // (ZU.real() >= -6.5) -6.5 is 0.5 is the real value of the center of expansion in the array - // (ZU.real() <= 7.5) 7.5 is 0.5 is the real value of the center of expansion in the array - // (ZU.imag() <= 6.35) 6.35 is 5.5/sin(PI/3) which is 0.5 past 5/sin(PI/3) + // exceeded. + // (ZU.real() >= -6.5) -6.5 is 0.5 is the real value of the center of expansion in the array + // (ZU.real() <= 7.5) 7.5 is 0.5 is the real value of the center of expansion in the array + // (ZU.imag() <= 6.35) 6.35 is 5.5/sin(PI/3) which is 0.5 past 5/sin(PI/3) if ((ZU.real() >= -6.5) && (ZU.real() <= 7.5) && (ZU.imag() <= 6.35)) { - // choose center of expansion of the Taylor series - CoERealidx = (int)(ZU.real() + _copysign(0.5, ZU.real())); - CoEImagidx = (int)(sin(PI / 3.0) * (ZU.imag() + 0.5)); // sin(60)*(Z.imag()+0.5) + CoERealidx + = static_cast(ZU.real() + std::copysign(0.5, ZU.real())); + CoEImagidx = static_cast( + std::sin(PI / 3.0) * (ZU.imag() + 0.5) + ); // sin(60)*(Z.imag()+0.5) - N = NQTT[CoERealidx + 6] + CoEImagidx; // N is index of center of expansion + N = NQTT[CoERealidx + 6] + + CoEImagidx; // N is index of center of expansion // Check to see if N is out of bounds - if (N >= 70) { // Stop if the index N reaches the limit of array AV[] which is 70 + if (N + >= 70) { // Stop if the index N reaches the limit of array AV[] which is 70 //printf("Airy() Error: Z is too large\n"); - return complex(0, 0); + return std::complex(0, 0); }; - NQ8 = NQTT[CoERealidx + 7]; // The next real center of expansion or what is know here ... - // as the area of the Taylor's series + NQ8 = NQTT + [CoERealidx + + 7]; // The next real center of expansion or what is know here ... + // as the area of the Taylor's series // if Z is inside Taylor's series area, continue. Otherwise, go to asymptotic series if (N < NQ8) { - /////////////////////////////////////////// // Compute the function by Taylor Series // /////////////////////////////////////////// // sum Taylor's series around nearest expansion point // The arrays AV[] and APV[] are incremented in the complex domain by 1/sin(PI/3) - CoE = complex((double)CoERealidx, (double)CoEImagidx / sin(PI / 3.0)); + CoE = std::complex( + (double)CoERealidx, (double)CoEImagidx / std::sin(PI / 3.0) + ); // Translate the input parameter to the new location ZU = ZU - CoE; // Calculate the first term of the Taylor Series - // To do this we need to find the Airy or Bairy function at the center of + // To do this we need to find the Airy or Bairy function at the center of // expansion, CoE, that has been precalculated in the arrays above. if (kind == BAIRY || kind == BAIRYD) { - Ai = BV[N - 1]; // Bi(CoE) - Aip = BPV[N - 1]; // Bi'(CoE) - } - else { // All other cases use the Coe for Ai(z) - Ai = AV[N - 1]; // Ai(CoE) - Aip = APV[N - 1]; // Ai'(CoE) + Ai = BV[N - 1]; // Bi(CoE) + Aip = BPV[N - 1]; // Bi'(CoE) + } else { // All other cases use the Coe for Ai(z) + Ai = AV[N - 1]; // Ai(CoE) + Aip = APV[N - 1]; // Ai'(CoE) }; // Find the first elements of the Taylor series // Translation - B1 = Ai; // B1 is first term for function Ai(a) - B3 = B1 * CoE*ZU; // B3 is second term for derivative Ai(a)*a*(z-a) - A[1] = Aip; // A is first term for derivation Ai'(a) - B2 = A[1] * ZU; // B2 is second term for function Ai'(a)(z-a) - A[0] = B2 + B1; // A[0] is the sum of Ai() or Bi() Ai'(a)(z-a) + Ai(a) - A[1] = A[1] + B3; // A[1] is the sum of Ai'() or Bi'() Ai'(a) + Ai(a)*a*(z-a) + B1 = Ai; // B1 is first term for function Ai(a) + B3 = B1 * CoE + * ZU; // B3 is second term for derivative Ai(a)*a*(z-a) + A[1] = Aip; // A is first term for derivation Ai'(a) + B2 = A[1] * ZU; // B2 is second term for function Ai'(a)(z-a) + A[0] + = B2 + + B1; // A[0] is the sum of Ai() or Bi() Ai'(a)(z-a) + Ai(a) + A[1] + = A[1] + + B3; // A[1] is the sum of Ai'() or Bi'() Ai'(a) + Ai(a)*a*(z-a) AN = 1.0; - // Initialize the counter + // Initialize the counter cnt = 0; // compute terms of series and sum until convergence @@ -627,27 +660,25 @@ complex Airy(complex Z, int kind, int scaling) B0 = B1; B1 = B2; B2 = B3; - B3 = (CoE*B1 + ZU * B0)*ZU / AN; + B3 = (CoE * B1 + ZU * B0) * ZU / AN; A[1] = B3 + A[1]; - } while ((abs(B2) > (0.5E-7 * abs(A[0]))) - || - (abs(B3) > (0.5E-7 * abs(A[1])))); // Has the convergence criteria been met? + } while ((std::abs(B2) > (0.5E-7 * std::abs(A[0]))) + || (std::abs(B3) > (0.5E-7 * std::abs(A[1]))) + ); // Has the convergence criteria been met? cnt++; - } while (cnt < 3); // require that the loop be executed 3 times + } while (cnt < 3); // require that the loop be executed 3 times } - }; // if ((ZU.real() >= -6.5) && (ZU.real() <= 7.5) && (ZU.imag() <= 6.35)) + }; // if ((ZU.real() >= -6.5) && (ZU.real() <= 7.5) && (ZU.imag() <= 6.35)) -// Determine if the data for the center of expansion is exceeded + // Determine if the data for the center of expansion is exceeded if (((ZU.real() < -6.5) || (ZU.real() > 7.5) || (ZU.imag() > 6.35)) - || - (N >= NQ8) - || - (Z.real() == 0 && Z.imag() == 0)) { - - + || (N >= NQ8) + //|| (Z.real() == 0.0 && Z.imag() == 0.0)) { //Replaced with AlmostEqualRelative + || (AlmostEqualRelative(Z.real(), 0.0) + && AlmostEqualRelative(Z.imag(), 0.0))) { /////////////////////////////////////////////// // Compute the function by Asymptotic Series // /////////////////////////////////////////////// @@ -663,25 +694,25 @@ complex Airy(complex Z, int kind, int scaling) /////////////////////////////////////////////////////// // Find intermediate values - ZA = sqrt(ZU); // zeta^(1/2) - ZT = (2.0 / 3.0)*ZU*ZA; // NIST DLMF 9.7.1 => -(2/3)zeta^(3/2) + ZA = std::sqrt(ZU); // zeta^(1/2) + ZT = (2.0 / 3.0) * ZU * ZA; // NIST DLMF 9.7.1 => -(2/3)zeta^(3/2) if (kind == BAIRY || kind == BAIRYD) { one = 1.0; // Terms for the Bairy sum do not alternate sign - } - else { - one = -1.0; // All other functions use the Airy whose sum alternates sign + } else { + one = -1.0; // All other functions use the Airy whose sum alternates sign } // Compute the asymptotic series either sum over k for (u_k*zeta^-k) or sum over k for (v_k*zeta^-k) // Which is used depends on M => M = 0 use u_k M = 1 use v_k // By doing this backward you don't have to do multiple powers zeta^-1 - // Note the coefficients are backward so the for loop will be forward - sum1 = complex(0.0, 0.0); // Initialize the sum + // Note the coefficients are backward so the for loop will be forward + sum1 = std::complex(0.0, 0.0); // Initialize the sum for (int i = 0; i < 14; i++) { - sum1 = (pow(one, i)*ASV[i][derivative] + sum1) / ZT; + sum1 = (std::pow(one, i) * ASV[i][derivative_idx] + sum1) / ZT; }; - sum1 = ASV[SIZE_OF_ASV - 1][derivative] + sum1; // Add the first element that is a function of zeta^0 + sum1 = ASV[SIZE_OF_ASV - 1][derivative_idx] + + sum1; // Add the first element that is a function of zeta^0 // Now determine if a second series is necessary // If it is not set the second sum to zero @@ -697,7 +728,7 @@ complex Airy(complex Z, int kind, int scaling) // if((ZT.imag() <= 0.0) && (ZT.real() >= -8.4056)) // Since ZT = (2/3)ZU^(3/2) and for ZT.imag() = 0.0 and ZT.real() = -8.4056 // ZU = -2.70859033 + j*4.69141606 which has an angle of 2*PI/3 - // Similarly for Hufford's original code + // Similarly for Hufford's original code // ZU = -3.40728475 + j*5.90159031 which has an angle of 2*PI/3 // Thus we could replace the following with the inequality // if(ZU.arg() > 2.0*PI/3.0) @@ -705,49 +736,50 @@ complex Airy(complex Z, int kind, int scaling) // From Copson the F(z) solution is only valid for phase(z) <= PI/3.0 // While the F(z) + i*G(z) solution is necessary for phase(z) > PI/3.0 - if (abs(arg(ZU)) > PI / 3.0) { - sum2 = complex(0.0, 0.0); // Initialize the second sum + if (std::abs(std::arg(ZU)) > PI / 3.0) { + sum2 = std::complex(0.0, 0.0); // Initialize the second sum for (int i = 0; i < 14; i++) { - sum2 = (ASV[i][derivative] + sum2) / ZT; + sum2 = (ASV[i][derivative_idx] + sum2) / ZT; }; - sum2 = ASV[SIZE_OF_ASV - 1][derivative] + sum2; // Add the first element that is a function of zeta^0 - } - else { // Only one series is necessary for accuracy - sum2 = complex(0.0, 0.0); + sum2 = ASV[SIZE_OF_ASV - 1][derivative_idx] + + sum2; // Add the first element that is a function of zeta^0 + } else { // Only one series is necessary for accuracy + sum2 = std::complex(0.0, 0.0); }; - // Now do the final function that leads the sum depending on what the user wants. - // The leading function has to be taken apart so that it can be assembled as necessary for + // Now do the final function that leads the sum depending on what the user wants. + // The leading function has to be taken apart so that it can be assembled as necessary for // the possible two parts of the sum if (kind == BAIRY || kind == BAIRYD) { - if (derivative == NO) { - ZB = 1.0 / (sqrt(ZA)); // NIST DLMF 9.7.8 - } - else if (derivative == YES) { - ZB = sqrt(ZA); // NIST DLMF 9.7.7 + if (derivative_flag) { + ZB = std::sqrt(ZA); // NIST DLMF 9.7.7 + } else { + ZB = 1.0 / (std::sqrt(ZA)); // NIST DLMF 9.7.8 }; - ZB1 = ZB * exp(ZT) / sqrt(PI); // For Bairy multiply by e^(zeta)/sqrt(PI) - ZB2 = ZB * 1.0 / (exp(ZT)*sqrt(PI)); - - } - else { // All other kind use Airy - if (derivative == NO) { - ZB = 1.0 / sqrt(ZA); // NIST DLMF 9.7.6 - } - else if (derivative == YES) { - ZB = -1.0*sqrt(ZA); // NIST DLMF 9.7.5 + ZB1 = ZB * std::exp(ZT) + / std::sqrt(PI); // For Bairy multiply by e^(zeta)/sqrt(PI) + ZB2 = ZB * 1.0 / (std::exp(ZT) * std::sqrt(PI)); + + } else { // All other kind use Airy + if (derivative_flag) { + ZB = -1.0 * std::sqrt(ZA); // NIST DLMF 9.7.5 + } else { + ZB = 1.0 / std::sqrt(ZA); // NIST DLMF 9.7.6 }; - ZB1 = ZB * 1.0 / (2.0*exp(ZT)*sqrt(PI)); // For Airy multiply be e^(-zeta)/(2.0*sqrt(PI)) - ZB2 = ZB * exp(ZT) / (2.0*sqrt(PI)); + ZB1 = ZB * 1.0 + / (2.0 * std::exp(ZT) * std::sqrt(PI) + ); // For Airy multiply be e^(-zeta)/(2.0*sqrt(PI)) + ZB2 = ZB * std::exp(ZT) / (2.0 * std::sqrt(PI)); }; // Multiply by the leading coefficient to get the results for NIST DLMF 9.7.5 - 9.7.8 - if (derivative == YES) { - A[derivative] = ZB1 * sum1 - complex(0.0, 1.0)*ZB2*sum2; - } - else if (derivative == NO) { - A[derivative] = ZB1 * sum1 + complex(0.0, 1.0)*ZB2*sum2; + if (derivative_flag) { + A[derivative_idx] + = ZB1 * sum1 - std::complex(0.0, 1.0) * ZB2 * sum2; + } else { + A[derivative_idx] + = ZB1 * sum1 + std::complex(0.0, 1.0) * ZB2 * sum2; }; }; // if (( Z.real() < 6.5 || Z.real() > 7.5 || Z.imag() > 6.35 || N > NQ8 || ((Z.real() == 0 && Z.imag() == 0)))) @@ -758,55 +790,57 @@ complex Airy(complex Z, int kind, int scaling) ////////////////////////////////////////////// // Store the desired quantity - Ai = A[derivative]; + Ai = A[derivative_idx]; - // Final Transform to get the desired function - // Was the input parameter in quadrant 3 or 4? + // Final Transform to get the desired function + // Was the input parameter in quadrant 3 or 4? // If it was we have to take the conjugate of the calculation result if (reflection != false) { - Ai = complex(Ai.real(), -Ai.imag()); + Ai = std::complex(Ai.real(), -Ai.imag()); }; // The final scaling factor is a function of the kind, derivative and scaling flags if (scaling == NONE) { - // The number from the Taylor series or asymptotic calculation + // The number from the Taylor series or asymptotic calculation // does not need to multiplied by anything - U = complex(1.0, 0.0); + U = std::complex(1.0, 0.0); } // Hufford Wi(1) and Wi'(1) else if ((kind == WONE || kind == DWONE) && (scaling == HUFFORD)) { - if (derivative == NO) { - U = 2.0*complex(cos(-PI / 3.0), sin(-PI / 3.0)); - } - else if (derivative == YES) { - U = 2.0*complex(cos(PI / 3.0), sin(PI / 3.0)); + if (derivative_flag) { + U = 2.0 + * std::complex(std::cos(PI / 3.0), std::sin(PI / 3.0)); + } else { + U = 2.0 + * std::complex(std::cos(-PI / 3.0), std::sin(-PI / 3.0)); }; } // Hufford Wi(2) and Wi'(2) else if ((kind == WTWO || kind == DWTWO) && (scaling == HUFFORD)) { - if (derivative == NO) { - U = 2.0*complex(cos(PI / 3.0), sin(PI / 3.0)); - } - else if (derivative == YES) { - U = 2.0*complex(cos(-PI / 3.0), sin(-PI / 3.0)); + if (derivative_flag) { + U = 2.0 + * std::complex(std::cos(-PI / 3.0), std::sin(-PI / 3.0)); + } else { + U = 2.0 + * std::complex(std::cos(PI / 3.0), std::sin(PI / 3.0)); }; } // Wait W1 and W1' else if ((kind == WONE || kind == DWONE) && (scaling == WAIT)) { - if (derivative == NO) { - U = complex(sqrt(3.0*PI), -1.0*sqrt(PI)); - } - else if (derivative == YES) { - U = complex(-1.0*sqrt(3.0*PI), -1.0*sqrt(PI)); + if (derivative_flag) { + U = std::complex( + -1.0 * std::sqrt(3.0 * PI), -1.0 * std::sqrt(PI) + ); + } else { + U = std::complex(std::sqrt(3.0 * PI), -1.0 * std::sqrt(PI)); }; } // Wait W2 and W2' else if ((kind == WTWO || kind == DWTWO) && (scaling == WAIT)) { - if (derivative == NO) { - U = complex(sqrt(3.0*PI), sqrt(PI)); - } - else if (derivative == YES) { - U = complex(-1.0*sqrt(3.0*PI), sqrt(PI)); + if (derivative_flag) { + U = std::complex(-1.0 * std::sqrt(3.0 * PI), std::sqrt(PI)); + } else { + U = std::complex(std::sqrt(3.0 * PI), std::sqrt(PI)); }; }; @@ -814,5 +848,8 @@ complex Airy(complex Z, int kind, int scaling) Ai = Ai * U; return Ai; +} -}; \ No newline at end of file +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..63f7ab5 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,62 @@ +########################################### +## BUILD THE LIBRARY +########################################### +proplib_message("Configuring library ${LIB_NAME}") + +message(STATUS "STATUS: Start building the libaray: " ${LIB_NAME}) + +# Include source AND header files here so IDEs can find them +set(LIB_HEADERS "${PROJECT_SOURCE_DIR}/include") + +set(LIB_FILES + Airy.cpp + FlatEarthCurveCorrection.cpp + LFMF.cpp + ResidueSeries.cpp + ReturnCodes.cpp + ValidateInputs.cpp + WiRoot.cpp + wofz.cpp + "${LIB_HEADERS}/LFMF.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}") + +# Configure compiler options +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/FlatEarthCurveCorrection.cpp b/src/FlatEarthCurveCorrection.cpp index 51309f8..f578eba 100644 --- a/src/FlatEarthCurveCorrection.cpp +++ b/src/FlatEarthCurveCorrection.cpp @@ -1,91 +1,120 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: Calculates the groundwave field strength using the flat Earth - | approximation with curvature correction. - | - | References: 99-368 "Medium Frequency Propagation - | Prediction Techniques and Antenna Modeling for - | Intelligent Transportation Systems (ITS) Broadcast - | Applications", Nicholas DeMinco. Eq (31) - | J. Wait, "Radiation From a Vertical Antenna Over a Curved - | Stratified Ground", Journal of Research of the National - | Bureau of Standards Vol 56, No. 4, April 1956 - | Research Paper 2671 - | - | Input: Delta - Surface impedance - | q - Intermediate value -j*nu*delta - | h_1__km - Height of the higher antenna, in km - | h_2__km - Height of the lower antenna, in km - | d__km - Path distance, in km - | k - Wavenumber, in rad/km - | a_e__km - Effective earth radius, in km - | - | Returns: E_gw - Normalized field strength in mV/m - | - *===========================================================================*/ -double FlatEarthCurveCorrection(complex Delta, complex q, double h_1__km, double h_2__km, double d__km, double k, double a_e__km) -{ - complex j = complex(0.0, 1.0); +/** @file FlatEarthCurveCorrection.cpp + * Calculates the groundwave using the flat Earth approximation with curvature correction. + */ + +#include "LFMF.h" + +#include // for abs, exp, pow, sqrt +#include // for std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Calculates the groundwave field strength using the flat Earth approximation + * with curvature correction. + * + * References: + * - NTIA Report 99-368 "Medium Frequency Propagation Prediction Techniques + * and Antenna Modeling for Intelligent Transportation Systems (ITS) + * Broadcast Applications", Nicholas DeMinco. Eq (31) + * - J. Wait, "Radiation From a Vertical Antenna Over a Curved Stratified + * Ground", Journal of Research of the National Bureau of Standards Vol 56, + * No. 4, April 1956 Research Paper 2671 + * + * @param[in] delta Surface impedance + * @param[in] q Intermediate value -j*nu*delta + * @param[in] h_1__km Height of the higher antenna, in km + * @param[in] h_2__km Height of the lower antenna, in km + * @param[in] d__km Path distance, in km + * @param[in] k Wavenumber, in rad/km + * @param[in] a_e__km Effective earth radius, in km + * @return Normalized field strength in mV/m + ******************************************************************************/ +double FlatEarthCurveCorrection( + const std::complex delta, + const std::complex q, + const double h_1__km, + const double h_2__km, + const double d__km, + const double k, + const double a_e__km +) { + const std::complex j = std::complex(0.0, 1.0); // In order for the wofz() function to be used both here and in gwfe() // the argument, qi, has to be defined correctly. The following is how - // it is done in the original GWFEC.FOR - complex qi = (-0.5 + j * 0.5)*sqrt(k*d__km)*Delta; - complex p = qi * qi; - - complex p2 = pow(p, 2); - complex q3 = pow(q, 3); - complex q6 = pow(q, 6); - complex q9 = pow(q, 9); - - complex fofx; - - if(abs(q) > 0.1){ + // it is done in the original GWFEC.FOR + const std::complex qi + = (-0.5 + j * 0.5) * std::sqrt(k * d__km) * delta; + const std::complex p = qi * qi; + + const std::complex p2 = std::pow(p, 2); + const std::complex q3 = std::pow(q, 3); + const std::complex q6 = std::pow(q, 6); + const std::complex q9 = std::pow(q, 9); + + std::complex fofx; + + if (std::abs(q) > 0.1) { // Find F(p) Eqn (32) NTIA Report 99-368 - complex Fofp = 1.0 + sqrt(PI)*j*qi*wofz(qi); + std::complex Fofp = 1.0 + std::sqrt(PI) * j * qi * wofz(qi); // Calculate f(x) which is the normalized electric field, E_ratio; Eqn (31) NTIA Report 99-368 - fofx = Fofp + (1.0 - j * sqrt(PI * p) - (1.0 + 2.0 * p) * Fofp) / (4.0 * q3); - fofx = fofx + (1.0 - j * sqrt(PI * p) * (1.0 - p) - 2.0 * p + 5.0 * p2 / 6.0 + (p2 / 2.0 - 1.0) * Fofp) / (4.0 * q6); + fofx = Fofp + + (1.0 - j * std::sqrt(PI * p) - (1.0 + 2.0 * p) * Fofp) + / (4.0 * q3); + fofx = fofx + + (1.0 - j * std::sqrt(PI * p) * (1.0 - p) - 2.0 * p + + 5.0 * p2 / 6.0 + (p2 / 2.0 - 1.0) * Fofp) + / (4.0 * q6); } else { - - complex A[10]; - + std::complex A[10]; + // [Deminco, Eq 30] A[0] = 1.0; - A[1] = -j * sqrt(PI); + A[1] = -j * std::sqrt(PI); A[2] = -2.0; - A[3] = j * sqrt(PI) * (1.0 + 1.0/(4.0*q3)); - A[4] = 4.0/3.0 * ( 1.0 + 1.0/(2.0*q3) ); - A[5] = -j * sqrt(PI) / 4.0 * (1.0 + 3.0 / (4.0 * q3)); - A[6] = -8.0/15.0 * ( 1.0 + 1.0/q3 + 7.0 / (32.0 * q6) ); - A[7] = j * sqrt(PI) / 6.0 * ( 1.0 + 5.0/(4.0*q3) + 27.0 /( 32.0*q6 ) ); - A[8] = 16.0/105.0 * ( 1.0 + 3.0/(2.0*q3) + 27.0/(32.0*q6) ); - A[9] = -j * sqrt(PI)/24.0 * (1.0 + 7.0/ (4.0*q3) + 5.0/(4.0*q6) + 21.0/(64.0*q9)); - - double x = d__km / a_e__km * pow(k * a_e__km/2.0, (1.0/3.0)); - - fofx = 0.0 + j*0.0; - + A[3] = j * std::sqrt(PI) * (1.0 + 1.0 / (4.0 * q3)); + A[4] = 4.0 / 3.0 * (1.0 + 1.0 / (2.0 * q3)); + A[5] = -j * std::sqrt(PI) / 4.0 * (1.0 + 3.0 / (4.0 * q3)); + A[6] = -8.0 / 15.0 * (1.0 + 1.0 / q3 + 7.0 / (32.0 * q6)); + A[7] = j * std::sqrt(PI) / 6.0 + * (1.0 + 5.0 / (4.0 * q3) + 27.0 / (32.0 * q6)); + A[8] = 16.0 / 105.0 * (1.0 + 3.0 / (2.0 * q3) + 27.0 / (32.0 * q6)); + A[9] = -j * std::sqrt(PI) / 24.0 + * (1.0 + 7.0 / (4.0 * q3) + 5.0 / (4.0 * q6) + 21.0 / (64.0 * q9)); + + const double x + = d__km / a_e__km * std::pow(k * a_e__km / 2.0, (1.0 / 3.0)); + + fofx = 0.0 + j * 0.0; + // [Deminco, Eq 28] - for (int ii = 0; ii<10; ii++) { - fofx = fofx + A[ii] * pow( exp(j * PI/4.0) * q * pow(x,1.0/2.0), ii); + for (int ii = 0; ii < 10; ii++) { + fofx = fofx + + A[ii] + * std::pow( + std::exp(j * PI / 4.0) * q * std::pow(x, 1.0 / 2.0), + ii + ); } - - } - // Now find the final normalized field strength from f(x) and the height-gain function for each antenna - // A height-gain function for an antenna is expressed as two terms of a Taylor series + // A height-gain function for an antenna is expressed as two terms of a Taylor series // (See DeMinco NTIA Report 99-368 Aug 1999 - // "Medium Frequency Propagation Prediction Techniques and + // "Medium Frequency Propagation Prediction Techniques and // Antenna Modeling for Intelligent Transportation Systems (ITS) Broadcast Applications" - // Equation 36) - - double E_gw__mVm = abs(fofx*(1.0 + j * k*h_2__km*Delta)*(1.0 + j * k*h_1__km*Delta)); + // Equation 36) + const double E_gw__mVm = std::abs( + fofx * (1.0 + j * k * h_2__km * delta) * (1.0 + j * k * h_1__km * delta) + ); return E_gw__mVm; -}; +} + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/LFMF.cpp b/src/LFMF.cpp index b5d9e9f..28341ef 100644 --- a/src/LFMF.cpp +++ b/src/LFMF.cpp @@ -1,105 +1,198 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: Compute the LFMF propagation prediction - | - | Input: h_tx__meter - Height of the transmitter, in meter - | h_rx__meter - Height of the receiver, in meter - | f__mhz - Frequency, in MHz - | P_tx__watt - Transmitter power, in Watts - | N_s - Surface refractivity, in N-Units - | d__km - Path distance, in km - | epsilon - Relative permittivity - | sigma - Conductivity - | pol - Polarization - | + 0 : POLARIZATION__HORIZONTAL - | + 1 : POLARIZATION__VERTICAL - | - | Outputs: result - Result structure - | - | Returns: error - Error code - | - *===========================================================================*/ -int LFMF(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol, Result *result) -{ - int rtn = ValidateInput(h_tx__meter, h_rx__meter, f__mhz, P_tx__watt, N_s, - d__km, epsilon, sigma, pol); +/** @file LFMF.cpp + * Implements the model from ITS.Propagation.LFMF. + */ + +#include "LFMF.h" + +#include // for cbrt, exp, fabs, log10, pow, sqrt +#include // for std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Compute the LFMF propagation prediction + * + * @param[in] h_tx__meter Height of the transmitter, in meter + * @param[in] h_rx__meter Height of the receiver, in meter + * @param[in] f__mhz Frequency, in MHz + * @param[in] P_tx__watt Transmitter power, in watts + * @param[in] N_s Surface refractivity, in N-Units + * @param[in] d__km Path distance, in km + * @param[in] epsilon Relative permittivity + * @param[in] sigma Conductivity + * @param[in] pol Polarization: 0 = Horizontal, 1 = Vertical + * @param[out] result Result structure + * @return Error code + ******************************************************************************/ +ReturnCode LFMF( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma, + const int pol, + Result &result +) { + ReturnCode rtn = ValidatePolarization(pol); if (rtn != SUCCESS) return rtn; - // Create the complex value j since this was written by electrical engineers - complex j = complex(0.0, 1.0); + return LFMF_CPP( + h_tx__meter, + h_rx__meter, + f__mhz, + P_tx__watt, + N_s, + d__km, + epsilon, + sigma, + static_cast(pol), + result + ); +} + +/******************************************************************************* + * Compute the LFMF propagation prediction + * + * @param[in] h_tx__meter Height of the transmitter, in meter + * @param[in] h_rx__meter Height of the receiver, in meter + * @param[in] f__mhz Frequency, in MHz + * @param[in] P_tx__watt Transmitter power, in watts + * @param[in] N_s Surface refractivity, in N-Units + * @param[in] d__km Path distance, in km + * @param[in] epsilon Relative permittivity + * @param[in] sigma Conductivity + * @param[in] pol Polarization: 0 = Horizontal, 1 = Vertical + * @param[out] result Result structure + * @return Error code + * + * @see ITS::Propagation::LFMF::Polarization + * @see ITS::Propagation::LFMF::Result + ******************************************************************************/ +ReturnCode LFMF_CPP( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma, + const Polarization pol, + Result &result +) { + ReturnCode rtn = ValidateInput( + h_tx__meter, h_rx__meter, f__mhz, P_tx__watt, N_s, d__km, epsilon, sigma + ); + if (rtn != SUCCESS) + return rtn; - double f__hz = f__mhz * 1e6; - double lambda__meter = C / f__hz; // wavelength, in meters + // Create the complex value j since this was written by electrical engineers + constexpr std::complex j = std::complex(0.0, 1.0); - double h_1__km = MIN(h_tx__meter, h_rx__meter) / 1000; // lower antenna, in km - double h_2__km = MAX(h_tx__meter, h_rx__meter) / 1000; // higher antenna, in km + const double f__hz = f__mhz * 1e6; + const double lambda__meter = C / f__hz; // wavelength, in meters - double a_e__km = a_0__km * 1 / (1 - 0.04665 * exp(0.005577 * N_s)); // effective earth radius, in km + const double h_1__km + = std::min(h_tx__meter, h_rx__meter) / 1000; // lower antenna, in km + const double h_2__km + = std::max(h_tx__meter, h_rx__meter) / 1000; // higher antenna, in km - double theta__rad = d__km / a_e__km; + const double a_e__km = a_0__km * 1 + / (1 - 0.04665 * std::exp(0.005577 * N_s) + ); // effective earth radius, in km - double k = 2.0 * PI / (lambda__meter / 1000); // wave number, in rad/km + const double theta__rad = d__km / a_e__km; - double nu = pow(a_e__km * k / 2.0, THIRD); // Intermediate value nu + const double k = 2.0 * PI * 1000 / lambda__meter; // wavenumber, in rad/km + const double nu = std::cbrt(a_e__km * k / 2.0); // Intermediate value nu // dielectric ground constant. See Eq (17) DeMinco 99-368 - complex eta = complex(epsilon, -sigma / (epsilon_0 * 2 * PI * f__hz)); + const std::complex eta + = std::complex(epsilon, -sigma / (epsilon_0 * 2 * PI * f__hz)); // Find the surface impedance, DeMinco 99-368 Eqn (15) - complex delta = sqrt(eta - 1.0); - if (pol == POLARIZATION__VERTICAL) + std::complex delta = std::sqrt(eta - 1.0); + if (pol == Polarization::VERTICAL) delta /= eta; - complex q = -nu * j * delta; // intermediate value q + const std::complex q = -nu * j * delta; // intermediate value q // Determine which smooth earth method is used; SG3 Groundwave Handbook, Eq 15 - double d_test__km = 80 * pow(f__mhz, -THIRD); + const double d_test__km = 80 / std::cbrt(f__mhz); double E_gw; - if (d__km < d_test__km) - { - E_gw = FlatEarthCurveCorrection(delta, q, h_1__km, h_2__km, d__km, k, a_e__km); - result->method = METHOD__FLAT_EARTH_CURVE; - } - else - { - E_gw = ResidueSeries(d__km, k, h_1__km, h_2__km, nu, theta__rad, q); - result->method = METHOD__RESIDUE_SERIES; + if (d__km < d_test__km) { + E_gw = FlatEarthCurveCorrection( + delta, q, h_1__km, h_2__km, d__km, k, a_e__km + ); + result.method = SolutionMethod::FLAT_EARTH_CURVE; + } else { + E_gw = ResidueSeries(k, h_1__km, h_2__km, nu, theta__rad, q); + result.method = SolutionMethod::RESIDUE_SERIES; } // Antenna gains - double G_tx__dbi = 4.77; - double G_rx__dbi = 4.77; + constexpr double G_tx__dbi = 4.77; + constexpr double G_rx__dbi = 4.77; - double G_tx = pow(10, G_tx__dbi / 10); + const double G_tx = std::pow(10, G_tx__dbi / 10); // Un-normalize the electric field strength - double E_0 = sqrt(ETA * (P_tx__watt * G_tx) / (4.0 * PI)) / d__km; // V/km or mV/m + const double E_0 = std::sqrt(ETA * (P_tx__watt * G_tx) / (4.0 * PI)) + / d__km; // V/km or mV/m E_gw = E_gw * E_0; // Calculate the basic transmission loss using (derived using Friis Transmission Equation with Electric Field Strength) // Pt Gt * Pt * ETA * 4*PI * f^2 // L = ---- = --------------------------- and convert to dB // Pr E^2 * c^2 - // with all values entered using base units: Watts, Hz, and V/m + // with all values entered using base units: watts, Hz, and V/m // basic transmission loss is not a function of power/gain, but since electric field strength E_gw is a function of (Gt * Pt), // and Lbtl is a function of 1/E_gw, we add in (Gt * Pt) to remove its effects - result->A_btl__db = 10 * log10(P_tx__watt * G_tx) - + 10 * log10(ETA * 4 * PI) - + 20 * log10(f__hz) - - 20 * log10(E_gw / 1000) - - 20 * log10(C); + result.A_btl__db = 10 * std::log10(P_tx__watt * G_tx) + + 10 * std::log10(ETA * 4 * PI) + 20 * std::log10(f__hz) + - 20 * std::log10(E_gw / 1000) - 20 * std::log10(C); // the 60 constant comes from converting field strength from mV/m to dB(uV/m) thus 20*log10(1e3) - result->E_dBuVm = 60 + 20 * log10(E_gw); + result.E_dBuVm = 60 + 20 * std::log10(E_gw); // Note power is a function of frequency. 42.8 comes from MHz to hz, power in dBm, and the remainder from // the collection of constants in the derivation of the below equation. - result->P_rx__dbm = result->E_dBuVm + G_rx__dbi - 20.0*log10(f__hz) + 42.8; + result.P_rx__dbm + = result.E_dBuVm + G_rx__dbi - 20.0 * std::log10(f__hz) + 42.8; return SUCCESS; -} \ No newline at end of file +} + +/******************************************************************************* + * Relative epsilon comparisons method + * + * @param[in] A First double to compare + * @param[in] B Second double to compare + * @param[in] maxRelDiff Maximum relative difference, defaults to DBL_EPSILON + * @return If it is equal of the two doubles + ******************************************************************************/ +bool AlmostEqualRelative( + const double A, const double B, const double maxRelDiff +) { + // Calculate the difference. + const double diff = std::fabs(A - B); + const double absA = std::fabs(A); + const double absB = std::fabs(B); + // Find the largest + const double largest = (absB > absA) ? absB : absA; + + if (diff <= largest * maxRelDiff) + return true; + return false; +} + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/ResidueSeries.cpp b/src/ResidueSeries.cpp index 9c1fbbc..a34fc47 100644 --- a/src/ResidueSeries.cpp +++ b/src/ResidueSeries.cpp @@ -1,33 +1,45 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: Calculates the groundwave field strength using the - | Residue Series method - | - | Input: d__km - Path distance, in km - | k - Wavenumber, in rad/km - | h_1__km - Height of the lower antenna, in km - | h_2__km - Height of the higher antenna, in km - | nu - Intermediate value, - | pow(a_e__km * k / 2.0, THIRD); - | theta__rad - Angular distance of path, in radians - | q - Intermediate value -j*nu*delta - | - | Returns: E_gw - Normalized field strength in mV/m - | - *===========================================================================*/ -double ResidueSeries(double d__km, double k, double h_1__km, double h_2__km, double nu, double theta__rad, complex q) -{ - complex DW2[200], W2[200]; // dummy variables - complex G; - - complex j = complex(0.0, 1.0); - - complex T[200], W1[200], W[200]; +/** @file ResidueSeries.cpp + * Implements a function to calculate the ground wave field strength. + */ + +#include "LFMF.h" + +#include // for abs, exp, sqrt +#include // for std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Calculates the groundwave field strength using the Residue Series method + * + * @param[in] k Wavenumber, in rad/km + * @param[in] h_1__km Height of the lower antenna, in km + * @param[in] h_2__km Height of the higher antenna, in km + * @param[in] nu Intermediate value, pow(a_e__km * k / 2.0, THIRD); + * @param[in] theta__rad Angular distance of path, in radians + * @param[in] q Intermediate value -j*nu*delta + * @return Normalized field strength in mV/m + ******************************************************************************/ +double ResidueSeries( + const double k, + const double h_1__km, + const double h_2__km, + const double nu, + const double theta__rad, + const std::complex q +) { + std::complex DW2[200], W2[200]; // dummy variables + std::complex G; + + constexpr std::complex j = std::complex(0.0, 1.0); + + std::complex T[200], W1[200], W[200]; double yHigh, yLow; - complex GW = complex(0, 0); // initial ground wave + // Initialize the ground wave + std::complex GW = std::complex(0.0, 0.0); // Associated argument for the height-gain function H_1[h_1] yHigh = k * h_2__km / nu; @@ -35,36 +47,45 @@ double ResidueSeries(double d__km, double k, double h_1__km, double h_2__km, dou // Associated argument for the height-gain function H_2[h_2] yLow = k * h_1__km / nu; - double x = nu * theta__rad; + const double x = nu * theta__rad; + + for (int i = 0; i < 200; i++) { + // find the (i+1)th root of Airy function for given q + T[i] = WiRoot(i + 1, DW2[i], q, W2[i], WONE, WAIT); + W1[i] = Airy(T[i], WONE, WAIT); // Airy function of (i)th root - for (int i = 0; i < 200; i++) - { - T[i] = WiRoot(i + 1, &DW2[i], q, &W2[i], WONE, WAIT); // find the (i+1)th root of Airy function for given q - W1[i] = Airy(T[i], WONE, WAIT); // Airy function of (i)th root + if (h_1__km > 0) { + W[i] + = Airy(T[i] - yLow, WONE, WAIT) + / W1[i]; //height gain function H_1(h_1) eqn.(22) from NTIA report 99-368 - if (h_1__km > 0) - { - W[i] = Airy(T[i] - yLow, WONE, WAIT) / W1[i]; //height gain function H_1(h_1) eqn.(22) from NTIA report 99-368 - if (h_2__km > 0) - W[i] *= Airy(T[i] - yHigh, WONE, WAIT) / W1[i]; //H_1(h_1)*H_1(h_2) - } - else if (h_2__km > 0) + W[i] *= Airy(T[i] - yHigh, WONE, WAIT) + / W1[i]; //H_1(h_1)*H_1(h_2) + } else if (h_2__km > 0) { W[i] = Airy(T[i] - yHigh, WONE, WAIT) / W1[i]; - else - W[i] = complex(1, 0); + } else { + W[i] = std::complex(1, 0); + } // W[i] is the coefficient of the distance factor for the i-th - W[i] /= (T[i] - (q*q)); // H_1(h_1)*H_1(h_2)/(t_i-q^2) eqn.26 from NTIA report 99-368 - G = W[i] * exp(-1.0*j*x*T[i]); // sum of exp(-j*x*t_i)*W[i] eqn.26 from NTIA report 99-368 - GW += G; // sum the series - - if (i != 0) - { - if (((abs((GW*GW).real())) + (abs((GW*GW).imag()))) == 0) // when the ground wave is too small, close to 0 - return 0; // end the loop and output E = 0 - else if (((abs((G / GW).real())) + (abs((G / GW).imag()))) < 0.0005) // when the new G is too small compared to its series sum - { + // H_1(h_1)*H_1(h_2)/(t_i-q^2) eqn.26 from NTIA report 99-368: + W[i] /= (T[i] - (q * q)); + // sum of exp(-j*x*t_i)*W[i] eqn.26 from NTIA report 99-368: + G = W[i] * std::exp(-1.0 * j * x * T[i]); + GW += G; // sum the series + + if (i != 0) { + if (AlmostEqualRelative( + (std::abs((GW * GW).real()) + (std::abs((GW * GW).imag()))), + 0.0, + 0.9 + )) { + return 0; // end the loop and output E = 0 + } else if (((std::abs((G / GW).real())) + + (std::abs((G / GW).imag()))) + < 0.0005) // when the new G is too small compared to its series sum + { // when the new G is too small compared to its series sum, it's ok to stop the loop // because adding small number to a significant big one doesn't affect their sum. //J1 = i; @@ -74,10 +95,15 @@ double ResidueSeries(double d__km, double k, double h_1__km, double h_2__km, dou } // field strength. complex(sqrt(PI/2)) = sqrt(pi)*e(-j*PI/4) - complex Ew = sqrt(x)*complex(sqrt(PI / 2), -sqrt(PI / 2))*GW; + const std::complex Ew + = std::sqrt(x) + * std::complex(std::sqrt(PI / 2), -std::sqrt(PI / 2)) * GW; - double E_gw = abs(Ew); // take the magnitude of the result + const double E_gw = std::abs(Ew); // take the magnitude of the result return E_gw; +} -}; \ No newline at end of file +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/ReturnCodes.cpp b/src/ReturnCodes.cpp new file mode 100644 index 0000000..05dfcf1 --- /dev/null +++ b/src/ReturnCodes.cpp @@ -0,0 +1,91 @@ +/** @file ReturnCodes.cpp + * Maps status messages to library return codes + */ + +#include "LFMF.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_s +#include // for std::string +#include // for std::unordered_map + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * 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(int code) { + static const std::unordered_map messages = { + {SUCCESS, "Successful execution"}, + {ERROR__TX_TERMINAL_HEIGHT, "TX terminal height is out of range"}, + {ERROR__RX_TERMINAL_HEIGHT, "RX terminal height is out of range"}, + {ERROR__FREQUENCY, "Frequency is out of range"}, + {ERROR__TX_POWER, "Transmit power is out of range"}, + {ERROR__SURFACE_REFRACTIVITY, "Surface refractivity is out of range"}, + {ERROR__PATH_DISTANCE, "Path distance is out of range"}, + {ERROR__EPSILON, "Epsilon is out of range"}, + {ERROR__SIGMA, "Sigma is out of range"}, + {ERROR__POLARIZATION, "Invalid value for polarization"}, + }; + // 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 LFMF +} // namespace Propagation +} // namespace ITS diff --git a/src/ValidateInputs.cpp b/src/ValidateInputs.cpp index 949751e..fa9356d 100644 --- a/src/ValidateInputs.cpp +++ b/src/ValidateInputs.cpp @@ -1,29 +1,36 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: Perform input parameter validation - | - | Input: h_tx__meter - Height of the transmitter, in meter - | h_rx__meter - Height of the receiver, in meter - | f__mhz - Frequency, in MHz - | P_tx__watt - Transmitter power, in Watts - | N_s - Surface refractivity, in N-Units - | d__km - Path distance, in km - | epsilon - Relative permittivity - | sigma - Conductivity - | pol - Polarization - | + 0 : POLARIZATION__HORIZONTAL - | + 1 : POLARIZATION__VERTICAL - | - | Outputs: [None] - | - | Returns: error - Error code - | - *===========================================================================*/ -int ValidateInput(double h_tx__meter, double h_rx__meter, double f__mhz, double P_tx__watt, - double N_s, double d__km, double epsilon, double sigma, int pol) -{ +/** @file ValidateInputs.cpp + * Implements functions to validate LFMF model inputs are in range. + */ + +#include "LFMF.h" + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Validate that model input values are within valid ranges. + * + * @param[in] h_tx__meter Height of the transmitter, in meters + * @param[in] h_rx__meter Height of the receiver, in meters + * @param[in] f__mhz Frequency, in MHz + * @param[in] P_tx__watt Transmitter power, in watts + * @param[in] N_s Surface refractivity, in N-Units + * @param[in] d__km Path distance, in km + * @param[in] epsilon Relative permittivity + * @param[in] sigma Conductivity, in siemens per meter + * @return Return code + ******************************************************************************/ +ReturnCode ValidateInput( + const double h_tx__meter, + const double h_rx__meter, + const double f__mhz, + const double P_tx__watt, + const double N_s, + const double d__km, + const double epsilon, + const double sigma +) { if (h_tx__meter < 0 || h_tx__meter > 50) return ERROR__TX_TERMINAL_HEIGHT; @@ -48,9 +55,22 @@ int ValidateInput(double h_tx__meter, double h_rx__meter, double f__mhz, double if (sigma <= 0) return ERROR__SIGMA; - if (pol != POLARIZATION__HORIZONTAL && - pol != POLARIZATION__VERTICAL) - return ERROR__POLARIZATION; + return SUCCESS; +} + +/****************************************************************************** + * Perform input Polarization validation + * + * @param[in] pol Polarization + * @return Return code + ******************************************************************************/ +ReturnCode ValidatePolarization(const int pol) { + if (pol != Polarization::HORIZONTAL && pol != Polarization::VERTICAL) + return ERROR__POLARIZATION; return SUCCESS; -} \ No newline at end of file +} + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/WiRoot.cpp b/src/WiRoot.cpp index 21591ca..3e1a1dd 100644 --- a/src/WiRoot.cpp +++ b/src/WiRoot.cpp @@ -1,206 +1,206 @@ -#include "..\include\LFMF.h" - -/*============================================================================= - | - | Description: This routine finds the roots to the equation - | - | Wi'(ti) - q*Wi(ti) = 0 - | - | The parameter i selects the ith root of the equation. The - | function Wi(ti) is the "Airy function of the third kind" - | as defined by Hufford[1] and Wait. The root is found by - | iteration starting from a real root. - | - | Note: Although roots that are found for W1 (Wait) and - | Wi(2) (Hufford) will be equal, and the roots found for - | W2 (Wait) and Wi(1) (Hufford) will be equal, the return - | values for *Wi and *DWi will not be the same. The input - | parameters for kind and scale are used here as they - | are in Airy() for consistency. - | - | References: "Airy Functions of the third kind" are found in equation 38 - | of NTIA Report 87-219 "A General Theory of Radio - | Propagation through a Stratified Atmosphere", George - | Hufford, July 1987 - | - | Input: i - The ith complex root of - | Wi'(2)(ti) - q*Wi(2)(ti) starting with 1 - | DWi - Derivative of "Airy function of the - | third kind" Wi'(2)(ti) - | q - Intermediate value -j*nu*delta - | Wi - "Airy function of the third kind" Wi(2)(ti) - | kind - Kind of Airy function to use - | scaling - Type of scaling to use - | - | Outputs: DWi - Derivative of "Airy function of the - | third kind" Wi'(2)(ti) - | Wi - "Airy function of the third kind" Wi(2)(ti) - | - | Returns: tw - ith complex root of the "Airy function of - | the third kind" - | - *===========================================================================*/ -complex WiRoot(int i, complex *DWi, complex q, complex *Wi, int kind, int scaling) -{ - complex ph; // Airy root phase - complex ct; // Temp - complex ti; // the ith complex root of Wi'(2)(ti) - q*Wi(2)(ti) = 0 - - complex tw; // Return variable - complex T; // Temp - complex A; // Temp - - double t, tt; // Temp - double eps; // Temp - - int cnt; // Temp - int dkind; // Temp - - // From the NIST DLMF (Digital Library of Mathematical Functions) +/** @file WiRoot.cpp + * Implements a function to find roots of equations involving Airy functions. + */ + +#include "LFMF.h" + +#include // for abs, cos, pow, sin +#include // for std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * Finds the roots to the equation @f$ Wi'(ti) - q*Wi(ti) = 0 @f$ + * + * The parameter `i` selects the @f$i@f$-th root of the equation. The function + * @f$ Wi(ti) @f$ is the "Airy function of the third kind" as defined by Hufford + * [1] and Wait. The root is found by iteration starting from a real root. + * + * @note Although roots that are found for @f$ w_1 @f$ (Wait) and + * @f$ \mathrm{Wi}^{(2)} @f$ (Hufford) will be equal, and the roots found for + * @f$ w_2 @f$ (Wait) and @f$ \mathrm{Wi}^{(1)} @f$ (Hufford) will be equal, the + * return values for `Wi` and `DWi` will not be the same. The input parameters for + * kind and scale are used here as they are in `Airy()` for consistency. + * + * @param[in] i The @f$ i @f$-th complex root of + * @f$ Wi'^{(2)}(ti) - q*Wi^{(2)}(ti) @f$, starting with 1 + * @param[in] q Intermediate value: @f$ -j \nu \delta @f$ + * @param[in] kind Kind of Airy function to use + * @param[in] scaling Type of scaling to use + * @param[out] DWi Derivative of "Airy function of the third kind" + * @f$ Wi'^{(2)}(ti) @f$ + * @param[out] Wi "Airy function of the third kind" @f$ Wi^{(2)}(ti) @f$ + * @return The @f$ i @f$-th complex root of the "Airy function of + * the third kind" + * + * **References** + * - "Airy Functions of the third kind" are found in equation 38 of [NTIA + * Report 87-219](https://its.ntia.gov/publications/details?pub=2242) + * "A General Theory of Radio Propagation through a Stratified Atmosphere", + * George Hufford, July 1987. + * + * @see ITS::Propagation::LFMF::AiryFunctionKind + * @see ITS::Propagation::LFMF::AiryFunctionScaling + * @see ITS::Propagation::LFMF::Airy + ******************************************************************************/ +std::complex WiRoot( + const int i, + std::complex &DWi, + const std::complex q, + std::complex &Wi, + const AiryFunctionKind kind, + const AiryFunctionScaling scaling +) { + std::complex ph; // Airy root phase + std::complex ti; // ith cplx root of Wi'(2)(ti) - q*Wi(2)(ti) = 0 + std::complex tw; // Return variable + + std::complex A; // Temp + double t, tt; // Temp + double eps; // Temp + int cnt; // Temp + AiryFunctionKind dkind; // Temp + + // From the NIST DLMF (Digital Library of Mathematical Functions) // http://dlmf.nist.gov/ - // The first 10 roots of Ai(Z) and Ai'(Z) can be found in: Table 9.9.1: Zeros of Ai and Ai. + // The first 10 roots of Ai(Z) and Ai'(Z) can be found in: Table 9.9.1: Zeros of Ai and Ai. // Note: That ak is the root of Ai(ak) while akp (ak') is the root of Ai'(akp) // Root of the Airy function, Ai(ak) // TZERO(I) in GWINT.FOR - double akp[] = { -1.0187929716, - -3.2481975822, - -4.8200992112, - -6.1633073556, - -7.3721772550, - -8.4884867340, - -9.5354490524, - -10.5276603970, - -11.4750666335, - -12.3847883718, - -13.2636395229 }; + constexpr double akp[11] + = {-1.0187929716, + -3.2481975822, + -4.8200992112, + -6.1633073556, + -7.3721772550, + -8.4884867340, + -9.5354490524, + -10.5276603970, + -11.4750666335, + -12.3847883718, + -13.2636395229}; // Root of the derivative of Airy function, Ai'(akp) // TINFIN(I) in GWINT.FOR - double ak[] = { -2.3381074105, - -4.0879494441, - -5.5205698281, - -6.7867080901, - -7.9441335871, - -9.0226508533, - -10.0401743416, - -11.0085243037, - -11.9360255632, - -12.8287867529, - -13.6914890352 }; + constexpr double ak[11] + = {-2.3381074105, + -4.0879494441, + -5.5205698281, + -6.7867080901, + -7.9441335871, + -9.0226508533, + -10.0401743416, + -11.0085243037, + -11.9360255632, + -12.8287867529, + -13.6914890352}; // Verify that the input data is correct // Make sure that the desired root is greater than or equal to one - if (i <= 0) - { + if (i <= 0) { // There is an input parameter error; printf("WiRoot(): The root must be >= 0 (%d)\n", i); - tw = complex(-998.8, -998.8); + tw = std::complex(-998.8, -998.8); return tw; }; - if ((scaling != HUFFORD) && (scaling != WAIT)) - { + if ((scaling != HUFFORD) && (scaling != WAIT)) { // There is an input parameter error; printf("WiRoot(): The scaling must be HUFFORD (%d) or WAIT (%d)\n", HUFFORD, WAIT); - tw = complex(-997.7, -997.7); + tw = std::complex(-997.7, -997.7); return tw; }; - if ((kind != WTWO) && (kind != WONE)) - { + if ((kind != WTWO) && (kind != WONE)) { // There is an input parameter error; printf("WiRoot(): The kind must be W1 (%d) or W2 (%d)\n", WONE, WTWO); - tw = complex(-996.6, -996.6); + tw = std::complex(-996.6, -996.6); return tw; }; // Input parameters verified - // Initialize the Wi and Wi'(z)functions - *DWi = complex(0.0, 0.0); // Wi'(z) - *Wi = complex(0.0, 0.0); // Wi(z) + // Initialize the Wi and Wi'(z)functions + DWi = std::complex(0.0, 0.0); // Wi'(z) + Wi = std::complex(0.0, 0.0); // Wi(z) // This routine starts with a real root of the Airy function to find the complex root - // The real root has to be turned into a complex number. + // The real root has to be turned into a complex number. // ph is a factor that is used to find the root of the Wi function // Determine what scaling the user wants and which Wi function is used to set ph and - // the dkind flag. This will allow that the real root that starts this process can be + // the dkind flag. This will allow that the real root that starts this process can be // scaled appropriately. - // This is the simmilar to the initial scaling that is done in Airy() + // This is the similar to the initial scaling that is done in Airy() // Note that W1 Wait = Wi(2) Hufford and W2 Wait = Wi(1) Hufford // So the following inequalities keep this all straight - if ((kind == WONE && scaling == HUFFORD) || (kind == WTWO && scaling == WAIT)) - { + if ((kind == WONE && scaling == HUFFORD) + || (kind == WTWO && scaling == WAIT)) { // Wi(1)(Z) in Eqn 38 Hufford NTIA Report 87-219 or Wait W2 - ph = complex(cos(-2.0*PI / 3.0), sin(-2.0*PI / 3.0)); + ph = std::complex( + std::cos(-2.0 * PI / 3.0), std::sin(-2.0 * PI / 3.0) + ); // Set the dkind flag - if (scaling == WAIT) - { + if (scaling == WAIT) { dkind = DWTWO; - } - else - { + } else { dkind = DWONE; }; - } - else if ((kind == WTWO && scaling == HUFFORD) || (kind == WONE && scaling == WAIT)) - { + } else if ((kind == WTWO && scaling == HUFFORD) + || (kind == WONE && scaling == WAIT)) { // Wi(2)(Z) in Eqn 38 Hufford NTIA Report 87-219 or Wait W1 - ph = complex(cos(2.0*PI / 3.0), sin(2.0*PI / 3.0)); - if (scaling == WAIT) - { + ph = std::complex( + std::cos(2.0 * PI / 3.0), std::sin(2.0 * PI / 3.0) + ); + if (scaling == WAIT) { dkind = DWONE; - } - else - { + } else { dkind = DWTWO; }; - }; - + } else { + dkind = DWONE; // Not used, initialization for compile warning + } - // Note: The zeros of the Airy functions i[ak'] and Ak'[ak], ak' and ak, are on the negative real axis. + // Note: The zeros of the Airy functions i[ak'] and Ak'[ak], ak' and ak, are on the negative real axis. // This is why 4*i+3 and 4*i+1 are used here instead of 4*k-3 and 4*k-1 which are - // used in 9.9.8 and 9.9.6 in NIST DLMF. We are finding the ith negative root here. - if (pow(abs(q), 3.0) <= 4 * (i - 1) + 3) { + // used in 9.9.8 and 9.9.6 in NIST DLMF. We are finding the ith negative root here. + if (std::pow(std::abs(q), 3.0) <= 4 * (i - 1) + 3) { // Small Z, use ak' as the first guess (Ak(ak') = 0) - if (i <= 10) - { + if (i <= 10) { // The desired root is less than 10 so it is in the array above tt = akp[i - 1]; - } - else - { + } else { // The desired root is a higher order than those given in the ak array above // so we will approximate it from the first three terms of NIST DLMF 9.9.1.9 // First find the argument (9.9.8) used in 9.9.1.9 for the ith negative root of Ai'(ak). - t = (3.0 / 8.0)*PI*(4.0*(i - 1) + 1); - tt = -1.0*pow(t, 2.0 / 3.0)*(1.0 - ((7.0 / 48.0)*pow(t, -2.0)) + ((35.0 / 288.0)*pow(t, -4.0))); + t = (3.0 / 8.0) * PI * (4.0 * (i - 1) + 1); + tt = -1.0 * std::pow(t, 2.0 / 3.0) + * (1.0 - ((7.0 / 48.0) * std::pow(t, -2.0)) + + ((35.0 / 288.0) * std::pow(t, -4.0))); }; // Make the real Airy root complex ti = tt * ph; // t is now the solution for q = 0, The next step is the first Newton iteration ti = ti + q / ti; - } - else - { + } else { // Large q, use ak as the first guess (Ai'(ak) = 0) - if (i <= 10) - { + if (i <= 10) { // The desired root is less than 10 so it is in the array above tt = ak[i - 1]; - } - else - { + } else { // The desired root must be approximated from the first three terms of NIST DLMF 9.9.1.8 // First find the argument (9.9.6) used in 9.9.1.8 for the ith negative root of Ai(ak). - t = (3.0 / 8.0)*PI*(4.0*(i - 1) + 3.0); - tt = -1.0*pow(t, 2.0 / 3.0)*(1.0 + ((5.0 / 48.0)*pow(t, -2.0)) - ((5.0 / 36.0)*pow(t, -4.0))); + t = (3.0 / 8.0) * PI * (4.0 * (i - 1) + 3.0); + tt = -1.0 * std::pow(t, 2.0 / 3.0) + * (1.0 + ((5.0 / 48.0) * std::pow(t, -2.0)) + - ((5.0 / 36.0) * std::pow(t, -4.0))); }; ti = tt * ph; // t is now the solution for Z = infinity. Next step the first newton iteration ti = ti + 1.0 / q; }; - cnt = 0; // Set the iteration counter - eps = 0.5e-6; // Set the error desired for the iteration + cnt = 0; // Set the iteration counter + eps = 0.5e-6; // Set the error desired for the iteration // Now iterate by Newton's method @@ -211,31 +211,33 @@ complex WiRoot(int i, complex *DWi, complex q, complex eps))); + } while ((cnt <= 25) + && ((std::abs((A / ti).real()) + (std::abs((A / ti).imag())) > eps) + )); // Check to see if there if the loop converged on an answer - if (cnt == 26) // The cnt that fails is an arbitrary number most converge in ~5 tries - { + // The cnt that fails is an arbitrary number; most converge in ~5 tries + if (cnt == 26) { // No Convergence return 0 + j*0 as the root as TW() did - tw = complex(0.0, 0.0); - } - else - { + tw = std::complex(0.0, 0.0); + } else { // Converged! tw = ti; }; return tw; -}; \ No newline at end of file +} + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/src/wofz.cpp b/src/wofz.cpp index 2bbb58c..bf317a4 100644 --- a/src/wofz.cpp +++ b/src/wofz.cpp @@ -1,229 +1,204 @@ -#include "..\include\LFMF.h" - -complex wofz(complex z) -{ - -// ALGORITHM 680, COLLECTED ALGORITHMS FROM ACM. -// THIS WORK PUBLISHED IN TRANSACTIONS ON MATHEMATICAL SOFTWARE, -// VOL. 16, NO. 1, PP. 47. -// -// GIVEN A COMPLEX NUMBER Z = (XI,YI), THIS SUBROUTINE COMPUTES -// THE VALUE OF THE FADDEEVA-FUNCTION W(Z) = EXP(-Z**2)*ERFC(-I*Z), -// WHERE ERF%IS THE COMPLEX COMPLEMENTARY ERROR-FUNCTION AND I -// MEANS SQRT(-1). -// THE ACCURACY OF THE ALGORITHM FOR Z IN THE 1ST AND 2ND QUADRANT -// IS 14 SIGNIFICANT DIGITS; IN THE 3RD AND 4TH IT IS 13 SIGNIFICANT -// DIGITS OUTSIDE A CIRCULAR REGION WITH RADIUS 0.126 AROUND A ZERO -// OF THE FUNCTION. -// ALL REAL VARIABLES IN THE PROGRAM ARE DOUBLE PRECISION. -// -// -// THE CODE CONTAINS A FEW COMPILER-DEPENDENT PARAMETERS : -// RMAXREAL = THE MAXIMUM VALUE OF RMAXREAL EQUALS THE ROOT OF -// RMAX = THE LARGEST NUMBER WHICH CAN STILL BE -// IMPLEMENTED ON THE COMPUTER IN DOUBLE PRECISION -// FLOATING-POINT ARITHMETIC -// RMAXEXP = LN(RMAX) - LN(2) -// RMAXGONI = THE LARGEST POSSIBLE ARGUMENT OF A DOUBLE PRECISION -// GONIOMETRI%FUNCTION (DCOS, DSIN, ...) -// THE REASON WHY THESE PARAMETERS ARE NEEDED AS THEY ARE DEFINED WILL -// BE EXPLAINED IN THE CODE BY MEANS OF COMMENTS -// -// PARAMETER LIST -// flag = AN ERROR FLAG INDICATING WHETHER OVERFLOW WILL -// OCCUR OR NOT; TYPE LOGICAL; -// THE VALUES OF THIS VARIABLE HAVE THE FOLLOWING -// MEANING : -// FLAG=.FALSE. : NO ERROR CONDITION -// FLAG=.TRUE. : OVERFLOW WILL OCCUR, THE ROUTINE -// BECOMES INACTIVE -// -// FURTHERMORE THE PARAMETER FACTOR EQUALS 2/SQRT(PI) -// -// THE ROUTINE IS NOT UNDERFLOW-PROTECTED BUT ANY VARIABLE CAN BE -// PUT TO 0 UPON UNDERFLOW; -// -// REFERENCE - GPM POPPE, CMJ WIJERS; MORE EFFICIENT COMPUTATION OF -// THE COMPLEX ERROR-FUNCTION, ACM TRANS. MATH. SOFTWARE. -// -// Translated from FORTRAN to C/C++ by I. Stevanovic OFCOM CH - - -double FACTOR = 1.12837916709551257388; -double RMAXREAL = 0.5E+154; -double RMAXEXP = 708.503061461606E0; -double RMAXGONI = 3.53711887601422E+15; - -double XI = z.real(); -double YI = z.imag(); - -double XABS = abs(XI); -double YABS = abs(YI); -double X = XABS/6.3; -double Y = YABS/4.4; - -bool flag = false; - -complex w; - -double XSUM, YSUM, U, V, XAUX, U1, V1, DAUX, U2, V2, H, H2, KAPN; -double QLAMBDA, RX, RY, TX, TY, SX, SY, W1, CC; - -int NU, NP1; - -// THE FOLLOWING IF-STATEMENT PROTECTS -// QRHO = (X**2 + Y**2) AGAINST OVERFLOW - -if ((XABS > RMAXREAL) || (YABS > RMAXREAL)) { - flag = true; - w = complex(0.0,0.0); - return w; -} - -double QRHO = X*X + Y*Y; - -double XABSQ = pow(XABS,2.0); -double XQUAD = XABSQ - pow(YABS, 2.0); -double YQUAD = 2*XABS*YABS; - -bool A = (QRHO < 0.085264E0); - -if (A) { - - // IF (QRHO < 0.085264) THEN THE FADDEEVA-FUNCTION IS EVALUATED - // USING A POWER-SERIES (ABRAMOWITZ/STEGUN, EQUATION (7.1.5), P.297) - // N IS THE MINIMUM NUMBER OF TERMS NEEDED TO OBTAIN THE REQUIRED - // ACCURACY - - QRHO = (1-0.85*Y)*sqrt(QRHO); - int N = (int)(6 + 72*QRHO); - int J = 2*N+1; - XSUM = 1.0/J; - YSUM = 0.0; - for (int I = N; I > 0; I--) { - - J = J - 2; - XAUX = (XSUM*XQUAD - YSUM*YQUAD)/I; - YSUM = (XSUM*YQUAD + YSUM*XQUAD)/I; - XSUM = XAUX + 1.0/J; +/** @file wofz.cpp + * Implements a function to calculate the Faddeeva function @f$ W(z) @f$. + */ + +#include "LFMF.h" + +#include // for abs, cos, exp, pow, sin, sqrt +#include // for std::complex + +namespace ITS { +namespace Propagation { +namespace LFMF { + +/******************************************************************************* + * This function computes the Faddeeva function + * @f$ W(z) = e^{-z^2} \mathrm{erfc}(-iz) @f$. + * + * Given a complex input argument @f$ z @f$, this function computes the value of + * the above equation, in which @f$\mathrm{erfc}@f$ is the complex complementary + * error function and @f$ i @f$ is @f$ \sqrt{-1} @f$. + * + * The basis for this function is Algorithm 680, Collected Algorithms from ACM, + * (reference provided below). This version of the function accepts a single + * complex input argument `z` and returns a single complex output. The following + * note is restated from the original implementation's description: + * + * @note The accuracy of the algorithm for @f$ z @f$ in the 1st and 2nd + * quadrant is 14 significant digits; in the 3rd and 4th it is 13 significant + * digits outside a circular region with radius 0.126 around a zero of the function. + * + * @param[in] z Input argument + * @return The desired @f$ W(z)_ @f$ function calculated at `z` + * + * **Lineage** + * - The original FORTRAN implementation was translated to C/C++ by + * I. Stevanovic (OFCOM CH). + * - Direct comparisons of `double` values with `==` were removed by C. Heroy, + * who implemented `AlmostEqualRelative()`. + * + * **References** + * - Algorithm 680, Collected Algorithms from ACM, Transactions of Mathematical + * Software, Vol. 16, No. 1, pp. 47: https://doi.org/10.1145/77626.77630 + ******************************************************************************/ +std::complex wofz(const std::complex z) { + constexpr double FACTOR = 1.12837916709551257388; + constexpr double RMAXREAL = 0.5E+154; + constexpr double RMAXEXP = 708.503061461606E0; + constexpr double RMAXGONI = 3.53711887601422E+15; + + const double XI = z.real(); + const double YI = z.imag(); + + const double XABS = std::abs(XI); + const double YABS = std::abs(YI); + const double X = XABS / 6.3; + const double Y = YABS / 4.4; + + std::complex w; + + double XSUM, YSUM, U, V, XAUX, U1, V1, DAUX, U2, V2, H, H2, KAPN; + double QLAMBDA, RX, RY, TX, TY, SX, SY, W1, CC; + + int NU, NP1; + + // This condition protects `QRHO = (X**2 + Y**2)` against overflow + if ((XABS > RMAXREAL) || (YABS > RMAXREAL)) { + w = std::complex(0.0, 0.0); + return w; } - U1 = -FACTOR*(XSUM*YABS + YSUM*XABS) + 1.0; - V1 = FACTOR*(XSUM*XABS - YSUM*YABS); - DAUX = exp(-XQUAD); - U2 = DAUX*cos(YQUAD); - V2 = -DAUX*sin(YQUAD); - - U = U1*U2 - V1*V2; - V = U1*V2 + V1*U2; - -} else { - - // IF (QRHO > 1.O) THEN W(Z) IS EVALUATED USING THE LAPLACE - // CONTINUED FRACTION - // NU IS THE MINIMUM NUMBER OF TERMS NEEDED TO OBTAIN THE REQUIRED - // ACCURACY - - // IF ((QRHO > 0.085264).AND.(QRHO < 1.0)) THEN W(Z) IS EVALUATED - // BY A TRUNCATED TAYLOR EXPANSION, WHERE THE LAPLACE CONTINUED FRACTION - // IS USED TO CALCULATE THE DERIVATIVES OF W(Z) - // KAPN IS THE MINIMUM NUMBER OF TERMS IN THE TAYLOR EXPANSION NEEDED - // TO OBTAIN THE REQUIRED ACCURACY - // NU IS THE MINIMUM NUMBER OF TERMS OF THE CONTINUED FRACTION NEEDED - // TO CALCULATE THE DERIVATIVES WITH THE REQUIRED ACCURACY - - - if (QRHO > 1.0) { - H = 0.0; - KAPN = 0; - QRHO = sqrt(QRHO); - NU = (int)(3 + (1442/(26*QRHO+77))); + + double QRHO = X * X + Y * Y; + + const double XABSQ = std::pow(XABS, 2.0); + double XQUAD = XABSQ - std::pow(YABS, 2.0); + const double YQUAD = 2 * XABS * YABS; + + const bool A = (QRHO < 0.085264E0); + + if (A) { + // If (QRHO < 0.085264) then the Faddeeva-function is evaluated using a + // power-series (Abramowitz/Stegun, Eqn 7.1.5, p.297). + // N is the minimum number of terms needed to obtain the required accuracy. + QRHO = (1 - 0.85 * Y) * std::sqrt(QRHO); + const int N = (int)(6 + 72 * QRHO); + int J = 2 * N + 1; + XSUM = 1.0 / J; + YSUM = 0.0; + for (int I = N; I > 0; I--) { + J = J - 2; + XAUX = (XSUM * XQUAD - YSUM * YQUAD) / I; + YSUM = (XSUM * YQUAD + YSUM * XQUAD) / I; + XSUM = XAUX + 1.0 / J; + } + U1 = -FACTOR * (XSUM * YABS + YSUM * XABS) + 1.0; + V1 = FACTOR * (XSUM * XABS - YSUM * YABS); + DAUX = std::exp(-XQUAD); + U2 = DAUX * std::cos(YQUAD); + V2 = -DAUX * std::sin(YQUAD); + + U = U1 * U2 - V1 * V2; + V = U1 * V2 + V1 * U2; } else { - QRHO = (1-Y)*sqrt(1-QRHO); - H = 1.88*QRHO; - H2 = 2*H; - KAPN = (int)(7 + 34*QRHO); - NU = (int)(16 + 26*QRHO); - } - - bool B = (H > 0.0); - - if (B) { - QLAMBDA = pow(H2, KAPN); - } - - RX = 0.0; - RY = 0.0; - SX = 0.0; - SY = 0.0; - - for (int N = NU; N>= 0; N--){ - NP1 = N + 1; - TX = YABS + H + NP1*RX; - TY = XABS - NP1*RY; - CC = 0.5/(TX*TX + TY*TY); - RX = CC*TX; - RY = CC*TY; - if ((B) && (N <= KAPN)) { - TX = QLAMBDA + SX; - SX = RX*TX - RY*SY; - SY = RY*TX + RX*SY; - QLAMBDA = QLAMBDA/H2; + if (QRHO > 1.0) { + // If (QRHO > 1.O) then W(Z) is evaluated using the Laplace continued fraction + // NU is the minimum number of terms needed to obtain the required accuracy. + H = 0.0; + KAPN = 0; + QRHO = std::sqrt(QRHO); + NU = (int)(3 + (1442 / (26 * QRHO + 77))); + } else { + // If ((QRHO > 0.085264) AND (QRHO < 1.0)) then W(Z) is evaluated by a + // truncated Taylor expansion, where the Laplace continued fraction is + // used to calculate the derivatives of W(Z) + // KAPN is the minimum number of terms in the Taylor expansion needed to + // obtain the required accuracy. + // NU is the number of terms of the continued fraction needed to calculate + // the derivatives with the required accuracy. + QRHO = (1 - Y) * std::sqrt(1 - QRHO); + H = 1.88 * QRHO; + H2 = 2 * H; + KAPN = (int)(7 + 34 * QRHO); + NU = (int)(16 + 26 * QRHO); + } + + const bool B = (H > 0.0); + + if (B) { + QLAMBDA = std::pow(H2, KAPN); + } else { + QLAMBDA = 0.0; // Not used, initialization avoids compile warning + } + + RX = 0.0; + RY = 0.0; + SX = 0.0; + SY = 0.0; + + for (int N = NU; N >= 0; N--) { + NP1 = N + 1; + TX = YABS + H + NP1 * RX; + TY = XABS - NP1 * RY; + CC = 0.5 / (TX * TX + TY * TY); + RX = CC * TX; + RY = CC * TY; + if ((B) && (N <= KAPN)) { + TX = QLAMBDA + SX; + SX = RX * TX - RY * SY; + SY = RY * TX + RX * SY; + QLAMBDA = QLAMBDA / H2; + } + } + + if (AlmostEqualRelative(H, 0.0)) { + U = FACTOR * RX; + V = FACTOR * RY; + } else { + U = FACTOR * SX; + V = FACTOR * SY; + } + + if (AlmostEqualRelative(YABS, 0.0)) { + U = std::exp(-XABS * XABS); } } - - if (H == 0.0) { - U = FACTOR*RX; - V = FACTOR*RY; - }else{ - U = FACTOR*SX; - V = FACTOR*SY; - } - - if (YABS == 0.0){ - U = exp(-XABS*XABS); - } -} -// EVALUATION OF W(Z) IN THE OTHER QUADRANTS - - -if (YI < 0.0) { - - if (A) { - U2 = 2*U2; - V2 = 2*V2; + // EVALUATION OF W(Z) IN THE OTHER QUADRANTS + + + if (YI < 0.0) { + if (A) { + U2 = 2 * U2; + V2 = 2 * V2; + } else { + XQUAD = -XQUAD; + + // This condition protects `2*EXP(-Z**2)` against overflow + if ((YQUAD > RMAXGONI) || (XQUAD > RMAXEXP)) { + w = std::complex(0.0, 0.0); + return w; + } + + W1 = 2 * std::exp(XQUAD); + U2 = W1 * std::cos(YQUAD); + V2 = -W1 * std::sin(YQUAD); + } + + U = U2 - U; + V = V2 - V; + if (XI > 0.0) { + V = -V; + } } else { - XQUAD = -XQUAD; - - - // THE FOLLOWING IF-STATEMENT PROTECTS 2*EXP(-Z**2) - // AGAINST OVERFLOW - - if ((YQUAD > RMAXGONI) || (XQUAD > RMAXEXP)) { - flag = true; - w = complex(0.0, 0.0); - return w; + if (XI < 0.0) { + V = -V; } - - - W1 = 2*exp(XQUAD); - U2 = W1*cos(YQUAD); - V2 = -W1*sin(YQUAD); } - - U = U2 - U; - V = V2 - V; - if (XI > 0.0) { - V = -V; - } - -} else { - if (XI < 0.0){ - V = -V; - } -} - -w = complex(U, V); -return w; - + + w = std::complex(U, V); + return w; } + +} // namespace LFMF +} // namespace Propagation +} // namespace ITS \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e7c8d1f --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +############################################ +## CONFIGURE UNIT TESTS +############################################ +set(TEST_NAME "${LIB_NAME}Test") +proplib_message("Configuring library tests ${TEST_NAME}") + +## Include all source AND header files for tests here. +## Do not include the source and header files of the library under test. +add_executable( + ${TEST_NAME} + "TestLFMFReturnCode.cpp" + "TestUtils.cpp" + "TestUtils.h" +) + +########################################### +## 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/TestLFMFReturnCode.cpp b/tests/TestLFMFReturnCode.cpp new file mode 100644 index 0000000..394b9d1 --- /dev/null +++ b/tests/TestLFMFReturnCode.cpp @@ -0,0 +1,303 @@ +/** @file TestLFMFReturnCode.cpp + * Test against a set of example cases, including intentionally triggering errors. + */ + +#include "TestUtils.h" + +/******************************************************************************* + * These tests evaluate a number of expected input/output combinations against + * the current version of the LF/MF code, including combinations which are + * expected to produce specific errors. + ******************************************************************************/ +class TestLFMFReturnCode: public ::testing::Test { + protected: + void SetUp() override { + // Load test data from CSV + testData = ReadLFMFTestData(fileName); + } + + // Vector to hold test data + std::vector testData; + + std::string fileName = "LFMF_Examples.csv"; +}; + +/** Test case to verify LFMF results are correct */ +TEST_F(TestLFMFReturnCode, ReturnSuccess) { + // Ensure test data was loaded + EXPECT_NE(static_cast(testData.size()), 0); + std::cout << "TestLFMF from '" << fileName << "': " << testData.size() + << " Test instances." << std::endl; + + for (const auto &data : testData) { + if (data.rtn == SUCCESS) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, data.rtn); + EXPECT_NEAR(result.A_btl__db, data.A_btl__db, ABSTOL__DB); + EXPECT_NEAR(result.E_dBuVm, data.E_dBuVm, ABSTOL__DB); + EXPECT_NEAR(result.P_rx__dbm, data.P_rx__dbm, ABSTOL__DB); + EXPECT_EQ(result.method, data.method); + } + } +} + +/** Test case to verify LFMF input TX terminal height is out of range */ +TEST_F(TestLFMFReturnCode, InvalidTXTerminalHeight) { + for (const auto &data : testData) { + if (data.rtn == ERROR__TX_TERMINAL_HEIGHT) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__TX_TERMINAL_HEIGHT); + } + } +} + +/** Test case to verify LFMF input RX terminal height is out of range */ +TEST_F(TestLFMFReturnCode, InvalidRXTerminalHeight) { + for (const auto &data : testData) { + if (data.rtn == ERROR__RX_TERMINAL_HEIGHT) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__RX_TERMINAL_HEIGHT); + } + } +} + +/** Test case to verify LFMF input Frequency is out of range */ +TEST_F(TestLFMFReturnCode, InvalidFrequency) { + for (const auto &data : testData) { + if (data.rtn == ERROR__FREQUENCY) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__FREQUENCY); + } + } +} + +/** Test case to verify LFMF input Transmit power is out of range */ +TEST_F(TestLFMFReturnCode, InvalidTransmitPower) { + for (const auto &data : testData) { + if (data.rtn == ERROR__TX_POWER) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__TX_POWER); + } + } +} + +/** Test case to verify LFMF input Surface refractivity is out of range */ +TEST_F(TestLFMFReturnCode, InvalidSurfaceRefractivity) { + for (const auto &data : testData) { + if (data.rtn == ERROR__SURFACE_REFRACTIVITY) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__SURFACE_REFRACTIVITY); + } + } +} + +/** Test case to verify LFMF input Path distance is out of range */ +TEST_F(TestLFMFReturnCode, InvalidPathDistance) { + for (const auto &data : testData) { + if (data.rtn == ERROR__PATH_DISTANCE) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__PATH_DISTANCE); + } + } +} + +/** Test case to verify LFMF input Epsilon is out of range */ +TEST_F(TestLFMFReturnCode, InvalidEpsilon) { + for (const auto &data : testData) { + if (data.rtn == ERROR__EPSILON) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__EPSILON); + } + } +} + +/** Test case to verify LFMF input Sigma is out of range */ +TEST_F(TestLFMFReturnCode, InvalidSigma) { + for (const auto &data : testData) { + if (data.rtn == ERROR__SIGMA) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__SIGMA); + } + } +} + +/** Test case to verify LFMF input polarization is invalid */ +TEST_F(TestLFMFReturnCode, InvalidPolarization) { + for (const auto &data : testData) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + -1, + result + ); + + EXPECT_EQ(rtn, ERROR__POLARIZATION); + + rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + 3, + result + ); + + EXPECT_EQ(rtn, ERROR__POLARIZATION); + if (data.rtn == ERROR__POLARIZATION) { + Result result; + + int rtn = LFMF( + data.h_tx__meter, + data.h_rx__meter, + data.f__mhz, + data.P_tx__watt, + data.N_s, + data.d__km, + data.epsilon, + data.sigma, + data.pol, + result + ); + + EXPECT_EQ(rtn, ERROR__POLARIZATION); + } + } +} \ No newline at end of file diff --git a/tests/TestUtils.cpp b/tests/TestUtils.cpp new file mode 100644 index 0000000..dc73b06 --- /dev/null +++ b/tests/TestUtils.cpp @@ -0,0 +1,129 @@ +/** @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; +} + +/******************************************************************************* + * Loads test data from a CSV file + * + * @param[in] filename Test data CSV file name + * @return A vector including all inputs and outputs from the CSV + * in `LFMFTestData` structures. + ******************************************************************************/ +std::vector ReadLFMFTestData(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: + LFMFTestData d; + char c; // single character representing the comma (delimiter) + // Integers to temporarily store enum values before casting + int pol_value; + int rtn_value; + int method_value; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (iss >> d.h_tx__meter >> c >> d.h_rx__meter >> c >> d.f__mhz >> c + >> d.P_tx__watt >> c >> d.N_s >> c >> d.d__km >> c >> d.epsilon >> c + >> d.sigma >> c >> pol_value >> c >> rtn_value >> c >> d.A_btl__db + >> c >> d.E_dBuVm >> c >> d.P_rx__dbm >> c >> method_value) { + // Convert integers to enum + d.pol = static_cast(pol_value); + d.rtn = static_cast(rtn_value); + d.method = static_cast(method_value); + testData.push_back(d); + } + } + return testData; +} + +/******************************************************************************* + * Loads test data from a CSV file located in `tests/data`. The column structure + * of files used for validation is different from the LFMF-test-data structure. + * + * @param[in] filename Test data CSV file name + * @return A vector including all inputs and outputs from the CSV + * in `LFMFTestData` structures. + ******************************************************************************/ +std::vector ReadLFMFValidationData(const std::string &filename) { + std::vector testData; + std::string dataDir = GetLocalDataDirectory(); + std::ifstream file(dataDir + filename); + std::string line; + // struct to store data from a single line of CSV: + LFMFTestData d; + char c; // single character representing the comma (delimiter) + // Integers to temporarily store enum values before casting + int pol_value; + int rtn_value; + int method_value; + while (std::getline(file, line)) { + std::istringstream iss(line); + if (iss >> d.h_tx__meter >> c >> d.h_rx__meter >> c >> d.f__mhz >> c + >> d.P_tx__watt >> c >> d.N_s >> c >> d.d__km >> c >> d.epsilon >> c + >> d.sigma >> c >> pol_value >> c >> d.A_btl__db >> c >> d.E_dBuVm + >> c >> d.P_rx__dbm >> c >> method_value >> c >> rtn_value) { + // Convert integers to enum + d.pol = static_cast(pol_value); + d.rtn = static_cast(rtn_value); + d.method = static_cast(method_value); + testData.push_back(d); + } + } + return testData; +} + +/****************************************************************************** + * Get the full path of the directory containing validation data files. + * + * @return The path `tests/data/` + ******************************************************************************/ +std::string GetLocalDataDirectory() { + std::string dataDir(__FILE__); + dataDir.resize(dataDir.find_last_of("/\\")); + AppendDirectorySep(dataDir); + dataDir += "data"; + AppendDirectorySep(dataDir); + std::cerr << "****************************************\n***************************************"; + std::cerr << dataDir; + std::cerr << "****************************************\n***************************************"; + return dataDir; +} \ No newline at end of file diff --git a/tests/TestUtils.h b/tests/TestUtils.h new file mode 100644 index 0000000..74e31bd --- /dev/null +++ b/tests/TestUtils.h @@ -0,0 +1,47 @@ +/** @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 "LFMF.h" + +#include // for std::string + +using namespace ITS::Propagation::LFMF; + +// Absolute tolerance for comparing double values +constexpr double ABSTOL_DBL = 1.0e-6; + +// Absolute tolerance for comparing dB values (model predictions) +constexpr double ABSTOL__DB = 1.0e-1; + +// clang-format off +/** Struct for parsing LFMF CSV test data */ +struct LFMFTestData { + double h_tx__meter; /**< Height of the transmitter, in meters */ + double h_rx__meter; /**< Height of the receiver, in meters */ + double f__mhz; /**< Frequency, in MHz */ + double P_tx__watt; /**< Transmitter power, in watts */ + double N_s; /**< Surface refractivity, in N-Units */ + double d__km; /**< Path distance, in km */ + double epsilon; /**< Relative permittivity */ + double sigma; /**< Conductivity, in siemens per meter */ + Polarization pol; /**< Polarization enum value */ + ReturnCode rtn; /**< Expected Return Code */ + double A_btl__db; /**< Expected result BTL prediction, in dB */ + double E_dBuVm; /**< Expected result electric field strength, in dB(uV/m) */ + double P_rx__dbm; /**< Expected result received power, in dBm */ + SolutionMethod method; /**< Expected result solution method enum value */ +}; +// clang-format on + +std::vector ReadLFMFTestData(const std::string &filename); +std::vector ReadLFMFValidationData(const std::string &filename); +void AppendDirectorySep(std::string &str); +std::string GetDataDirectory(); +std::string GetLocalDataDirectory(); diff --git a/win32/LFMF.def b/win32/LFMF.def deleted file mode 100644 index 5c296c9..0000000 --- a/win32/LFMF.def +++ /dev/null @@ -1,3 +0,0 @@ -LIBRARY -EXPORTS - LFMF \ No newline at end of file diff --git a/win32/LFMF.rc b/win32/LFMF.rc deleted file mode 100644 index 4bd292b..0000000 --- a/win32/LFMF.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,1,0,0 - PRODUCTVERSION 1,1,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", "Low Frequency / Medium Frequency (LF/MF) Propagation Model" - VALUE "FileVersion", "1.1.0.0" - VALUE "InternalName", "LFMF.dll" - VALUE "OriginalFilename", "LFMF.dll" - VALUE "ProductName", "Low Frequency / Medium Frequency (LF/MF) Propagation Model" - VALUE "ProductVersion", "1.1.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/LFMF.sln b/win32/LFMF.sln deleted file mode 100644 index 02f8307..0000000 --- a/win32/LFMF.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30320.27 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LFMF", "LFMF.vcxproj", "{9A9F0547-8206-4C93-A0A6-545F693F6118}" -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 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Debug|x64.ActiveCfg = Debug|x64 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Debug|x64.Build.0 = Debug|x64 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Debug|x86.ActiveCfg = Debug|Win32 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Debug|x86.Build.0 = Debug|Win32 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Release|x64.ActiveCfg = Release|x64 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Release|x64.Build.0 = Release|x64 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Release|x86.ActiveCfg = Release|Win32 - {9A9F0547-8206-4C93-A0A6-545F693F6118}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D501E6CA-935A-42E0-816A-F56CD1DE92D5} - EndGlobalSection -EndGlobal diff --git a/win32/LFMF.vcxproj b/win32/LFMF.vcxproj deleted file mode 100644 index d31e7d9..0000000 --- a/win32/LFMF.vcxproj +++ /dev/null @@ -1,195 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - - 16.0 - Win32Proj - {9a9f0547-8206-4c93-a0a6-545f693f6118} - LFMF - 10.0 - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)..\bin\x86\Debug - - - false - $(SolutionDir)..\bin\x86\Release - - - true - $(SolutionDir)..\bin\x64\Debug - - - false - $(SolutionDir)..\bin\x64\Release - - - - Level3 - true - WIN32;_DEBUG;LFMF_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreadedDebug - StdCall - - - Windows - true - false - LFMF.def - - - - - Level3 - true - true - true - WIN32;NDEBUG;LFMF_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreaded - StdCall - - - Windows - true - true - true - false - LFMF.def - - - - - Level3 - true - _DEBUG;LFMF_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreadedDebug - StdCall - - - Windows - true - false - LFMF.def - - - - - Level3 - true - true - true - NDEBUG;LFMF_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - NotUsing - - - MultiThreaded - StdCall - - - Windows - true - true - true - false - LFMF.def - - - - - - \ No newline at end of file diff --git a/win32/LFMF.vcxproj.filters b/win32/LFMF.vcxproj.filters deleted file mode 100644 index f7c43ce..0000000 --- a/win32/LFMF.vcxproj.filters +++ /dev/null @@ -1,55 +0,0 @@ - - - - - Source - - - Source - - - Source - - - Source - - - Source - - - Source - - - Source - - - - - Header - - - Resource - - - - - {2e06e633-8168-4d43-89d1-a87a931963f8} - - - {72ea67d1-a062-4cb5-b9cf-a9d3d41a2eb8} - - - {96116a8f-f376-49e2-9b7c-2d0078a32cd1} - - - - - Resource - - - - - Resource - - - \ No newline at end of file diff --git a/win32/resource.h b/win32/resource.h deleted file mode 100644 index 7b6a391..0000000 --- a/win32/resource.h +++ /dev/null @@ -1,14 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by LFMF.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