diff --git a/.github/workflows/cff-validator.yml b/.github/workflows/cff-validator.yml index 83d75d0..6fce818 100644 --- a/.github/workflows/cff-validator.yml +++ b/.github/workflows/cff-validator.yml @@ -23,3 +23,5 @@ jobs: uses: actions/checkout@v4 - name: Validate CITATION.cff uses: dieghernan/cff-validator@v3 + with: + install-r: true diff --git a/.zenodo.json b/.zenodo.json index 6bda553..67993b1 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,36 +1,43 @@ { "upload_type": "software", "publication_date": "TODO-TEMPLATE", - "title": "TODO-TEMPLATE", + "title": "Low Frequency / Medium Frequency (LF/MF) Propagation Model, Python Wrapper", "creators": [ { - "name": "TODO-TEMPLATE", + "name": "Heroy, Chen", "affiliation": "U.S. Department of Commerce, National Telecommunications and Information Administration, Institute for Telecommunication Sciences", - "orcid": "TODO-TEMPLATE" + "orcid": "0009-0006-8728-4502" + }, + { + "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": "TODO-TEMPLATE. Make this the same as the abstract.", + "description": "This code repository contains a Python wrapper for the NTIA/ITS implementation of the Low Frequency / Medium Frequency (LF/MF) Propagation Model.", "access_right": "open", "keywords": [ - "TODO-TEMPLATE", - "TODO-TEMPLATE" + "propagation", + "communications", + "antennas", + "radio wave propagation" ], "related_identifiers": [ { - "identifier": "https://github.com/NTIA/TODO-TEMPLATE", + "identifier": "https://github.com/NTIA/LFMF", "relation": "isSupplementTo", "resource_type": "software" }, { - "identifier": "https://github.com/NTIA/TODO-TEMPLATE-test-data", + "identifier": "https://github.com/NTIA/LFMF-test-data", "relation": "isSupplementedBy", "resource_type": "dataset" }, { - "identifier": "https://ntia.github.io/propagation-library-wiki/models/TODO-TEMPLATE/", + "identifier": "https://ntia.github.io/propagation-library-wiki/models/LFMF/", "relation": "isDocumentedBy", "resource_type": "softwaredocumentation" } ], - "version": "TODO-TEMPLATE" + "version": "1.1.0" } diff --git a/CITATION.cff b/CITATION.cff index b1a5bb7..a4ce4c3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,15 +4,22 @@ title: >- message: Please cite this software using these metadata. type: software authors: - - family-names: Kozma - given-names: William - name-suffix: Jr. + - family-names: Heroy + given-names: Chen 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 + orcid: 'https://orcid.org/0009-0006-8728-4502' + email: cheroy.ctr@ntia.gov + - family-names: Romaniello + given-names: Anthony W. + affiliation: >- + U.S. Department of Commerce, National + Telecommunications and Information Administration, + Institute for Telecommunication Sciences + orcid: 'https://orcid.org/0000-0001-8437-6504' + email: aromaniello@ntia.gov - name: >- U.S. Department of Commerce, National Telecommunications and Information Administration, @@ -25,11 +32,11 @@ authors: alias: NTIA/ITS email: code@ntia.gov website: 'https://its.ntia.gov' -repository-code: 'https://github.com/NTIA/LFMF' +repository-code: 'https://github.com/NTIA/LFMF-python' url: 'https://ntia.github.io/propagation-library-wiki/models/LFMF' -repository: 'https://github.com/NTIA/LFMF' keywords: - its - propagation - lfmf -version: 1.0.0 + - antennas +version: 1.1.0 diff --git a/GitHubRepoPublicReleaseApproval.md b/GitHubRepoPublicReleaseApproval.md index 7c1fc41..102de20 100644 --- a/GitHubRepoPublicReleaseApproval.md +++ b/GitHubRepoPublicReleaseApproval.md @@ -28,5 +28,5 @@ your version of this Markdown document to that branch, then create a pull reques for that branch. The following must login to GitHub and approve that pull request before the pull request can be merged and this repo made public: -* Project Lead: Kozma Jr, William +* Project Lead: William Kozma, Jr. * Supervising Division Chief or Release Authority: Chris Anderson diff --git a/README.md b/README.md index 5109dff..8483437 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,34 @@ -# Low Frequency / Medium Frequency (LF/MF) Propagation Model, Python Wrapper # - - +# Low Frequency / Medium Frequency (LF/MF) Propagation Model, Python® Wrapper # + [![NTIA/ITS PropLib][proplib-badge]][proplib-link] [![GitHub Issues][gh-issues-badge]][gh-issues-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-python/tox.yml?branch=main&logo=pytest&logoColor=ffffff&label=Tests&labelColor=162E51 [gh-actions-test-link]: https://github.com/NTIA/LFMF-python/actions/workflows/tox.yml -[pypi-release-badge]: https://img.shields.io/pypi/v/LFMF-python?logo=pypi&logoColor=ffffff&label=Release&labelColor=162E51&color=D63E04 -[pypi-release-link]: https://pypi.org/project/LFMF-python +[pypi-release-badge]: https://img.shields.io/pypi/v/proplib-lfmf?logo=pypi&logoColor=ffffff&label=Release&labelColor=162E51&color=D63E04 +[pypi-release-link]: https://pypi.org/project/proplib-lfmf [gh-issues-badge]: https://img.shields.io/github/issues/NTIA/LFMF-python?logo=github&label=Issues&labelColor=162E51 [gh-issues-link]: https://github.com/NTIA/LFMF-python/issues -[doi-badge]: https://zenodo.org/badge/LFMF-python.svg -[doi-link]: https://zenodo.org/badge/latestdoi/LFMF-python +[doi-badge]: https://zenodo.org/badge/896234119.svg +[doi-link]: https://zenodo.org/badge/latestdoi/896234119 -This code repository contains the U.S. Reference Software Implementation of -Low Frequency / Medium Frequency (LF/MF) Propagation Model. This Python package wraps the -[base C++ implementation](https://github.com/NTIA/LFMF). +This code repository contains a Python wrapper for 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. This Python package wraps the [NTIA/ITS C++ implementation](https://github.com/NTIA/LFMF). ## Getting Started ## -> [!NOTE] -> The text below indicates this package is distributed on PyPi, -> however it is not yet uploaded. A link will be provided here when available. - -This software is distributed on [PyPI](#) and is easily installable +This software is distributed on [PyPI](https://pypi.org/project/proplib-lfmf) and is easily installable using the following command. ```cmd -pip install LFMF +pip install proplib-lfmf ``` General information about using this model is available on @@ -79,16 +57,15 @@ library from C++ source code; see relevant build instructions [here](https://github.com/NTIA/LFMF?tab=readme-ov-file#configure-and-build). 1. Optionally, configure and activate a virtual environment using a tool such as -[`venv`](https://docs.python.org/3/library/venv.html) or +[`venv`](https://docs.python.org/3/library/venv.html) or [`conda`](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html). - -2. Clone the parent repository, then initialize the Git submodule containing the Python wrapper. This repository structure makes test data available to the Python wrapper. +1. Clone this repository, then initialize the Git submodule containing the test data. ```cmd # Clone the repository - git clone https://github.com/NTIA/LFMF - cd LFMF + git clone https://github.com/NTIA/LFMF-python + cd LFMF-python # Initialize Git submodule containing test data git submodule init @@ -97,22 +74,17 @@ library from C++ source code; see relevant build instructions git submodule update ``` -3. Compile the C++ library for your platform, following instructions [here](https://github.com/NTIA/LFMF?tab=readme-ov-file#configure-and-build). Following these instructions should automatically copy the shared library into the location required by the Python wrapper. - - **OR** - - Download the shared library (`.dll`, `.so`, or `.dylib`) from a +1. Download the shared library (`.dll`, `.so`, or `.dylib`) from a [GitHub Release](https://github.com/NTIA/LFMF/releases). Then place the downloaded file in `src/ITS/Propagation/LFMF/` (alongside `__init__.py`). - -4. Install the local package and development dependencies into your current environment: +1. Install the local package and development dependencies into your current environment: ```cmd pip install .[dev] ``` -5. To build the wheel for your platform: +1. To build the wheel for your platform: ```cmd hatchling build @@ -130,12 +102,25 @@ pytest ## References ## -LFMF-python: Update references - - [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) +- 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 + +## License ## + +See [LICENSE](./LICENSE.md). + +"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. ## Contact ## -For technical questions, contact . \ No newline at end of file +For technical questions, contact . + +## Disclaimer ## + +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/pyproject.toml b/pyproject.toml index ee93b93..7ffe4d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,9 +3,9 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "LFMF" # LFMF-python update package name +name = "LFMF" dynamic = ["version"] -description = "Python wrapper for Low Frequency / Medium Frequency (LF/MF) Propagation Model" +description = "A Python wrapper for the NTIA/ITS implementation of the Low Frequency / Medium Frequency (LF/MF) Propagation Model" readme = "README.md" requires-python = ">=3.9" license = { file = "LICENSE.md" } diff --git a/src/ITS/Propagation/LFMF/LFMF.py b/src/ITS/Propagation/LFMF/LFMF.py index 5495b63..fa1e12e 100644 --- a/src/ITS/Propagation/LFMF/LFMF.py +++ b/src/ITS/Propagation/LFMF/LFMF.py @@ -6,45 +6,56 @@ class Result(Structure): # C Struct for library outputs - _fields_ = [('A_btl__db', c_double), - ('E_dBuVm', c_double), - ('P_rx__dbm', c_double), - ('method', c_int)] + _fields_ = [ + ("A_btl__db", c_double), + ("E__dBuVm", c_double), + ("P_rx__dbm", c_double), + ("method", c_int), + ] # Load the shared library -lib = PropLibCDLL("LFMF-1.0") +lib = PropLibCDLL("LFMF-1.1") # Define function prototypes lib.LFMF.restype = c_int lib.LFMF.argtypes = ( - c_double, - c_double, - c_double, - c_double, - c_double, - c_double, - c_double, - c_double, - c_int, - POINTER(Result), + c_double, + c_double, + c_double, + c_double, + c_double, + c_double, + c_double, + c_double, + c_int, + POINTER(Result), ) -def LFMF(h_tx__meter: float, h_rx__meter: float, f__mhz: float, P_tx__watt: float, - N_s: float, d__km: float, epsilon: float, sigma: float, pol: int) -> Result: +def LFMF( + h_tx__meter: float, + h_rx__meter: float, + f__mhz: float, + P_tx__watt: float, + N_s: float, + d__km: float, + epsilon: float, + sigma: float, + pol: int, +) -> Result: """ Compute the Low Frequency / Medium Frequency (LF/MF) propagation prediction - :param h_tx__meter: Height of the transmitter, in meter - :param h_rx__meter: Height of the receiver, in meter + :param h_tx__meter: Height of the transmitter, in meters + :param h_rx__meter: Height of the receiver, in meters :param f__mhz: Frequency, in MHz - :param P_tx__watt: Transmitter power, in Watts + :param P_tx__watt: Transmitter power, in watts :param N_s: Surface refractivity, in N-Units - :param d__km: Path distance, in km - :param epsilon: Relative permittivity - :param sigma: Conductivity - :param pol: Polarization + :param d__km: Path distance, in kilometers + :param epsilon: Relative permittivity (dimensionless) + :param sigma: Conductivity, in siemens per meter + :param pol: Polarization (enum value) :raises ValueError: If any input parameter is not in its valid range. :raises Exception: If an unknown error is encountered. @@ -63,7 +74,7 @@ def LFMF(h_tx__meter: float, h_rx__meter: float, f__mhz: float, P_tx__watt: floa c_double(epsilon), c_double(sigma), c_int(int(pol)), - byref(result) + byref(result), ) ) diff --git a/src/ITS/Propagation/LFMF/__init__.py b/src/ITS/Propagation/LFMF/__init__.py index 3ec44d4..1af35eb 100644 --- a/src/ITS/Propagation/LFMF/__init__.py +++ b/src/ITS/Propagation/LFMF/__init__.py @@ -1,6 +1,6 @@ # Version X.Y.Z: X.Y is the version of the C++ source, # and Z is the version of this Python wrapper -__version__ = "1.0.0" +__version__ = "1.1.0" from .LFMF import * diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lfmf.py b/tests/test_lfmf.py index 14ce9fd..db2d577 100644 --- a/tests/test_lfmf.py +++ b/tests/test_lfmf.py @@ -1,23 +1,8 @@ -import csv -from pathlib import Path - import pytest from ITS.Propagation import LFMF - -# Test data is expected to exist in parent repository tests/data -TEST_DATA_DIR = (Path(__file__).parent.parent.parent.parent / "tests") / "data" -ABSTOL__DB = 1.0E-1 # Absolute tolerance, in dB, to ensure outputs match expected value - - -def read_csv_test_data(filename: str): - with open(TEST_DATA_DIR / filename) as f: - reader = csv.reader(f) - next(reader) # Skip header row - for row in reader: - # yields (*inputs, rtn, *outputs) - yield tuple(map(float, row[:-5])), int(row[-5]), tuple(map(float, row[-4:])) +from .test_utils import ABSTOL__DB, read_csv_test_data @pytest.mark.parametrize( @@ -28,7 +13,7 @@ def test_lfmf(inputs, rtn, expected): if rtn == 0: result = LFMF.LFMF(*inputs) assert result.A_btl__db == pytest.approx(expected[0], abs=ABSTOL__DB) - assert result.E_dBuVm == pytest.approx(expected[1], abs=ABSTOL__DB) + assert result.E__dBuVm == pytest.approx(expected[1], abs=ABSTOL__DB) assert result.P_rx__dbm == pytest.approx(expected[2], abs=ABSTOL__DB) assert result.method == int(expected[3]) else: diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..b23066e --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,23 @@ +import csv +from pathlib import Path + +# Test data is expected to exist in tests/data +TEST_DATA_DIR = Path(__file__).parent / "data" +ABSTOL__DB = 0.1 # Absolute tolerance, in dB, to ensure outputs match expected value + +# Check if test data directory exists and is not empty +if not TEST_DATA_DIR.exists() or not any(TEST_DATA_DIR.iterdir()): + _test_data_checked = True + raise RuntimeError( + f"Test data is not available in {TEST_DATA_DIR}.\n Try running " + + "`git submodule init` and `git submodule update` to clone the test data submodule." + ) + + +def read_csv_test_data(filename: str): + with open(TEST_DATA_DIR / filename) as f: + reader = csv.reader(f) + next(reader) # Skip header row + for row in reader: + # yields (*inputs, rtn, *outputs) + yield tuple(map(float, row[:-5])), int(row[-5]), tuple(map(float, row[-4:]))