diff --git a/doc/installation/source/macos.rst b/doc/installation/source/macos.rst index cde771541..77732307e 100644 --- a/doc/installation/source/macos.rst +++ b/doc/installation/source/macos.rst @@ -123,7 +123,8 @@ For MacPorts packages we show the most recent package version available as of ea | Emulation with virtual | | | | machines | Not available for macOS | Not available for macOS | +-----------------------------+----------------------------------+--------------------------+ - | Support for openflow | ``boost`` | ``boost`` | + | Support for openflow, | ``boost`` | ``boost`` | + | CircularApertureAntennaModel| | | +-----------------------------+----------------------------------+--------------------------+ Caveats and troubleshooting diff --git a/doc/models/Makefile b/doc/models/Makefile index 07c94fe19..110b625b6 100644 --- a/doc/models/Makefile +++ b/doc/models/Makefile @@ -111,6 +111,7 @@ SOURCEFIGS = \ figures/testbed.dia \ figures/emulated-channel.dia \ $(SRC)/antenna/doc/source/figures/antenna-coordinate-system.dia \ + $(SRC)/antenna/doc/source/figures/circular-antenna-pattern.png \ $(SRC)/applications/doc/http-embedded-object-size.png \ $(SRC)/applications/doc/http-main-object-size.png \ $(SRC)/applications/doc/http-num-of-embedded-objects.png \ diff --git a/src/antenna/CMakeLists.txt b/src/antenna/CMakeLists.txt index 5c38a8cf4..86c442b4e 100644 --- a/src/antenna/CMakeLists.txt +++ b/src/antenna/CMakeLists.txt @@ -1,6 +1,61 @@ +# Check for dependencies and add sources accordingly +check_cxx_source_compiles( + "#include + int main() + { + return std::cyl_bessel_j(1, 1); + }" + HAVE_STD_BESSEL_FUNC +) + +set(circular_aperture_antenna_sources) +set(circular_aperture_antenna_headers) +set(circular_aperture_antenna_test_sources) + +if(${HAVE_STD_BESSEL_FUNC}) + set(circular_aperture_antenna_sources + model/circular-aperture-antenna-model.cc + ) + set(circular_aperture_antenna_headers + model/circular-aperture-antenna-model.h + ) + set(circular_aperture_antenna_test_sources + test/test-circular-aperture-antenna.cc + ) + message(STATUS "Standard library Bessel function has been found") +else() + check_include_files( + "boost/math/special_functions/bessel.hpp" + HAVE_BOOST_BESSEL_FUNC + LANGUAGE + CXX + ) + if(${HAVE_BOOST_BESSEL_FUNC}) + set(circular_aperture_antenna_sources + model/circular-aperture-antenna-model.cc + ) + set(circular_aperture_antenna_headers + model/circular-aperture-antenna-model.h + ) + set(circular_aperture_antenna_test_sources + test/test-circular-aperture-antenna.cc + ) + add_definitions(-DNEED_AND_HAVE_BOOST_BESSEL_FUNC) + message(STATUS "Boost Bessel function has been found.") + else() + message( + STATUS + "Boost Bessel function is required for CircularApertureAntennaModel" + " on platforms using Clang libc++ such as macOS." + " You may need to clean up the CMake cache after installing it to pass this check." + ) + endif() +endif() + build_lib( LIBNAME antenna SOURCE_FILES + ${circular_aperture_antenna_sources} model/angles.cc model/antenna-model.cc model/cosine-antenna-model.cc @@ -10,6 +65,7 @@ build_lib( model/three-gpp-antenna-model.cc model/uniform-planar-array.cc HEADER_FILES + ${circular_aperture_antenna_headers} model/angles.h model/antenna-model.h model/cosine-antenna-model.h @@ -20,10 +76,11 @@ build_lib( model/uniform-planar-array.h LIBRARIES_TO_LINK ${libcore} TEST_SOURCES + ${circular_aperture_antenna_test_sources} test/test-angles.cc + test/test-cosine-antenna.cc test/test-degrees-radians.cc test/test-isotropic-antenna.cc - test/test-cosine-antenna.cc test/test-parabolic-antenna.cc test/test-uniform-planar-array.cc ) diff --git a/src/antenna/doc/source/antenna-design.rst b/src/antenna/doc/source/antenna-design.rst index 7454d7414..1581d9296 100644 --- a/src/antenna/doc/source/antenna-design.rst +++ b/src/antenna/doc/source/antenna-design.rst @@ -113,7 +113,9 @@ pattern. ParabolicAntennaModel +++++++++++++++++++++ -This model is based on the parabolic approximation of the main lobe radiation pattern. It is often used in the context of cellular system to model the radiation pattern of a cell sector, see for instance [R4-092042a]_ and [Calcev]_. The antenna gain in dB is determined as: +This model is based on the parabolic approximation of the main lobe radiation pattern. It is often +used in the context of cellular system to model the radiation pattern of a cell sector, see for +instance [R4-092042a]_ and [Calcev]_. The antenna gain in dB is determined as: .. math:: @@ -192,6 +194,40 @@ are configured through the attribute "PolSlantAngle"; while the antenna elements the second polarization have the polarization slant angle minus 90 degrees, as described in [38901]_ (i.e., :math:`{\zeta}`). +CircularApertureAntennaModel +++++++++++++++++++++++++++++ + +The class CircularApertureAntennaModel implements the radiation pattern described in [38811]_. +Specifically, the latter represents parabolic antennas, i.e., antennas which are typically used +for achieving long range communications such as earth-to-satellite links. +The default boresight orientation is parallel to the positive z-axis, and it can be tuned by +using the AntennaInclination and AntennaAzimuth parameters. +This implementation provides an exact characterization of the antenna field pattern, by leveraging +the standard library Bessel functions implementation introduced with C++17. +Accordingly, the antenna gain :math:`G` at an angle :math:`\theta` from the boresight main beam +is evaluated as: + +.. math:: + G \cdot 4\left | \frac{J_{1}\left ( k\cdot a\cdot sin\theta \right )}{k\cdot a\cdot sin\theta} \right + |^{2}\;\;\;\;\; for\; 0<\left | \theta \right |\leq 90^{\circ} \\ + G \cdot 1\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; for\; \theta=0 + +where :math:`J_{1}()` is the Bessel function of the first kind and first order, and :math:`a` is +the radius of the antenna's circular aperture. +The parameter :math:`k` is equal to :math:`k=\frac{2\pi f}{x}`, where :math:`f` is the carrier +frequency, and :math:`c` is the speed of light in vacuum. +The parameters :math:`G` (in logarithmic scale), :math:`a` and :math:`f` can be configured by using +the attributes "AntennaMaxGainDb", "AntennaCircularApertureRadius" and "OperatingFrequency", respectively. +This type of antennas features a symmetric radiation pattern, meaning that a single angle, measured +from the boresight direction, is sufficient to characterize the radiation strength along a given direction. + +.. _fig-circular-antenna-pattern: + +.. figure:: figures/circular-antenna-pattern.png + :align: center + + Circular aperture antenna radiation pattern with :math:`G =` 38.5 dB and :math:`a =` 10 :math:`\frac{c}{f}.` + .. [Balanis] C.A. Balanis, "Antenna Theory - Analysis and Design", Wiley, 2nd Ed. @@ -208,6 +244,8 @@ as described in [38901]_ (i.e., :math:`{\zeta}`). .. [38901] 3GPP. 2018. TR 38.901, Study on channel model for frequencies from 0.5 to 100 GHz, V15.0.0. (2018-06). +.. [38811] 3GPP. 2018. TR 38.811, Study on New Radio (NR) to support non-terrestrial networks, V15.4.0. (2020-09). + .. [Mailloux] Robert J. Mailloux, "Phased Array Antenna Handbook", Artech House, 2nd Ed. .. [3GPP_TR36897] 3GPP. 2015. TR 36.897. Study on elevation beamforming / Full-Dimension (FD) diff --git a/src/antenna/doc/source/figures/circular-antenna-pattern.png b/src/antenna/doc/source/figures/circular-antenna-pattern.png new file mode 100644 index 000000000..2a41bf361 Binary files /dev/null and b/src/antenna/doc/source/figures/circular-antenna-pattern.png differ diff --git a/src/antenna/model/circular-aperture-antenna-model.cc b/src/antenna/model/circular-aperture-antenna-model.cc new file mode 100644 index 000000000..e409a613a --- /dev/null +++ b/src/antenna/model/circular-aperture-antenna-model.cc @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2022 University of Padova, Dep. of Information Engineering, SIGNET lab. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Mattia Sandri + */ + +#include "circular-aperture-antenna-model.h" + +// The host system uses Clang libc++, which does not support the Mathematical special functions +// (P0226R1) and Boost's implementation of cyl_bessel_j has been found. +#ifdef NEED_AND_HAVE_BOOST_BESSEL_FUNC +#include +#endif + +#include "antenna-model.h" + +#include +#include + +#include + +/** + * \file + * \ingroup antenna + * Class CircularApertureAntennaModel implementation. + */ + +namespace +{ +constexpr double C = 299792458.0; ///< speed of light in vacuum, in m/s +} // namespace + +namespace ns3 +{ +NS_LOG_COMPONENT_DEFINE("CircularApertureAntennaModel"); + +NS_OBJECT_ENSURE_REGISTERED(CircularApertureAntennaModel); + +TypeId +CircularApertureAntennaModel::GetTypeId() +{ + static TypeId tid = + TypeId("ns3::CircularApertureAntennaModel") + .SetParent() + .SetGroupName("Antenna") + .AddConstructor() + .AddAttribute("AntennaCircularApertureRadius", + "The radius of the aperture of the antenna, in meters", + DoubleValue(0.5), + MakeDoubleAccessor(&CircularApertureAntennaModel::SetApertureRadius), + MakeDoubleChecker(0.0)) + .AddAttribute("OperatingFrequency", + "The operating frequency in Hz of the antenna", + DoubleValue(2e9), + MakeDoubleAccessor(&CircularApertureAntennaModel::SetOperatingFrequency), + MakeDoubleChecker(0.0)) + .AddAttribute("AntennaMinGainDb", + "The minimum gain value in dB of the antenna", + DoubleValue(-100.0), + MakeDoubleAccessor(&CircularApertureAntennaModel::SetMinGain), + MakeDoubleChecker()) + .AddAttribute("AntennaMaxGainDb", + "The maximum gain value in dB of the antenna", + DoubleValue(1), + MakeDoubleAccessor(&CircularApertureAntennaModel::SetMaxGain), + MakeDoubleChecker(0.0)); + return tid; +} + +void +CircularApertureAntennaModel::SetApertureRadius(double aMeter) +{ + NS_LOG_FUNCTION(this << aMeter); + NS_ASSERT_MSG(aMeter > 0, "Setting invalid aperture radius: " << aMeter); + m_apertureRadiusMeter = aMeter; +} + +double +CircularApertureAntennaModel::GetApertureRadius() const +{ + return m_apertureRadiusMeter; +} + +void +CircularApertureAntennaModel::SetOperatingFrequency(double freqHz) +{ + NS_LOG_FUNCTION(this << freqHz); + NS_ASSERT_MSG(freqHz > 0, "Setting invalid operating frequency: " << freqHz); + m_operatingFrequencyHz = freqHz; +} + +double +CircularApertureAntennaModel::GetOperatingFrequency() const +{ + return m_operatingFrequencyHz; +} + +void +CircularApertureAntennaModel::SetMaxGain(double gainDb) +{ + NS_LOG_FUNCTION(this << gainDb); + m_maxGain = gainDb; +} + +double +CircularApertureAntennaModel::GetMaxGain() const +{ + return m_maxGain; +} + +void +CircularApertureAntennaModel::SetMinGain(double gainDb) +{ + NS_LOG_FUNCTION(this << gainDb); + m_minGain = gainDb; +} + +double +CircularApertureAntennaModel::GetMinGain() const +{ + return m_minGain; +} + +double +CircularApertureAntennaModel::GetGainDb(Angles a) +{ + NS_LOG_FUNCTION(this << a); + + // In 3GPP TR 38.811 v15.4.0, Section 6.4.1, the gain depends on a single angle only. + // We assume that this angle represents the angle between the vectors corresponding + // to the cartesian coordinates of the provided spherical coordinates, and the spherical + // coordinates (r = 1, azimuth = 0, elevation = PI/2) + double theta1 = a.GetInclination(); + double theta2 = M_PI_2; // reference direction + + // Convert to ISO range: the input azimuth angle phi is in [-pi,pi], + // while the ISO convention for spherical to cartesian coordinates + // assumes phi in [0,2*pi]. + double phi1 = M_PI + a.GetAzimuth(); + double phi2 = M_PI; // reference direction + + // Convert the spherical coordinates of the boresight and the incoming ray + // to Cartesian coordinates + Vector p1(sin(theta1) * cos(phi1), sin(theta1) * sin(phi1), cos(theta1)); + Vector p2(sin(theta2) * cos(phi2), sin(theta2) * sin(phi2), cos(theta2)); + + // Calculate the angle between the antenna boresight and the incoming ray + double theta = acos(p1 * p2); + + double gain = 0; + if (theta == 0) + { + gain = m_maxGain; + } + // return value of std::arccos is in [0, PI] deg + else if (theta >= M_PI_2) + { + // This is an approximation. 3GPP TR 38.811 does not provide indications + // on the antenna field pattern outside its PI degrees FOV. + gain = m_minGain; + } + else // 0 < theta < |PI/2| + { + // 3GPP TR 38.811 v15.4.0, Section 6.4.1 + double k = (2 * M_PI * m_operatingFrequencyHz) / C; + double kasintheta = k * m_apertureRadiusMeter * sin(theta); +// If needed, fall back to Boost cyl_bessel_j +#ifdef NEED_AND_HAVE_BOOST_BESSEL_FUNC + gain = boost::math::cyl_bessel_j(1, kasintheta) / kasintheta; +// Otherwise, use the std implementation +#else + gain = std::cyl_bessel_j(1, kasintheta) / kasintheta; +#endif + gain = 10 * log10(4 * gain * gain) + m_maxGain; + } + + return gain; +} + +} // namespace ns3 diff --git a/src/antenna/model/circular-aperture-antenna-model.h b/src/antenna/model/circular-aperture-antenna-model.h new file mode 100644 index 000000000..84b8feb1a --- /dev/null +++ b/src/antenna/model/circular-aperture-antenna-model.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2022 University of Padova, Dep. of Information Engineering, SIGNET lab. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Mattia Sandri + */ + +#ifndef CIRCULAR_APERTURE_ANTENNA_MODEL_H +#define CIRCULAR_APERTURE_ANTENNA_MODEL_H + +#include "antenna-model.h" + +#include + +/** + * \file + * \ingroup antenna + * Class CircularApertureAntennaModel declaration + */ + +namespace ns3 +{ +/** + * \brief Circular Aperture Antenna Model + * + * This class implements the circular aperture antenna as described in 3GPP 38.811 6.4.1 + * https://www.3gpp.org/ftp/Specs/archive/38_series/38.811 without the cosine approximation, thanks + * to the Bessel functions introduced in C++17. Spherical coordinates are used, in particular of the + * azimuth and inclination angles. All working parameters can be set, namely: operating frequency, + * aperture radius, maximum and minimum gain. + * Since Clang libc++ does not support the Mathematical special functions (P0226R1) yet, this class + * falls back to Boost's implementation of cyl_bessel_j whenever the above standard library is in + * use. If neither is available in the host system, this class is not compiled. + */ +class CircularApertureAntennaModel : public AntennaModel +{ + public: + CircularApertureAntennaModel() = default; + ~CircularApertureAntennaModel() override = default; + + /** + * Register this type. + * \return The object TypeId. + */ + static TypeId GetTypeId(); + + /** + * \brief Set the antenna aperture radius + * + * Sets the antenna operating frequency, asserting that + * the provided value is within the acceptable range [0, +inf[. + * + * \param aMeter the strictly positive antenna radius in meters + */ + void SetApertureRadius(double aMeter); + + /** + * \brief Return the antenna aperture radius + * + * \return the antenna radius in meters + */ + double GetApertureRadius() const; + + /** + * \brief Set the antenna operating frequency. + * + * Sets the antenna operating frequency, asserting that + * the provided value is within the acceptable range [0, +inf[. + * + * \param freqHz the strictly positive antenna operating frequency, in Hz + */ + void SetOperatingFrequency(double freqHz); + + /** + * \brief Return the antenna operating frequency + * + * \return the antenna operating frequency, in Hz + */ + double GetOperatingFrequency() const; + + /** + * \brief Set the antenna max gain + * + * \param gainDb the antenna max gain in dB + */ + void SetMaxGain(double gainDb); + + /** + * \brief Return the antenna max gain + * + * \return the antenna max gain in dB + */ + double GetMaxGain() const; + + /** + * \brief Set the antenna min gain + * + * \param gainDb the antenna min gain in dB + */ + void SetMinGain(double gainDb); + + /** + * \brief Return the antenna min gain + * + * \return the antenna min gain in dB + */ + double GetMinGain() const; + + /** + * \brief Get the gain in dB, using Bessel equation of first kind and first order. + * + * \param a the angle at which the gain need to be calculated with respect to the antenna + * bore sight + * + * \return the antenna gain at the specified Angles a + */ + double GetGainDb(Angles a) override; + + private: + double m_apertureRadiusMeter; //!< antenna aperture radius in meters + double m_operatingFrequencyHz; //!< antenna operating frequency in Hz + double m_maxGain; //!< antenna gain in dB towards the main orientation + double m_minGain; //!< antenna min gain in dB +}; + +} // namespace ns3 + +#endif // CIRCULAR_APERTURE_ANTENNA_MODEL_H diff --git a/src/antenna/test/gen-test-circular-aperture-antenna-reference-points.m b/src/antenna/test/gen-test-circular-aperture-antenna-reference-points.m new file mode 100644 index 000000000..51db047be --- /dev/null +++ b/src/antenna/test/gen-test-circular-aperture-antenna-reference-points.m @@ -0,0 +1,105 @@ +%{ + Compute the antenna gain pattern of the reflector antenna with circular aperture + specified in the Sec. 6.4.1, 3GPP 38.811 v.15.4.0 and generate reference gain values + for the CircularApertureAntennaModelTest. +%} +clc; clearvars; + +%{ + Consider two testing vectors, one with elevation fixed to 90 degrees and + varying azimuth, the other with azimuth fixed to 180 degrees and varying elevation. + The boresight direction, is (az, el) = (180 degrees, 90 degrees) +%} +az_test_fixed_el = 90:10:180; +eL_test_fixed_az = 0:9:90; +% Uncomment line below and comment line below for the fixed elevation test +[theta_vec, az_vec, el_vec] = theta_vec_from_sph_coord_vecs(az_test_fixed_el, 90); +% Uncomment line above and comment line below for the fixed azimuth test +%[theta_vec, az_vec, el_vec] = theta_vec_from_sph_coord_vecs(180, eL_test_fixed_az); + +%{ + The theta angle which represents the input of the pattern formula is + computed as theta = arccos(), where p_i = sin(theta_i) * + cos(phi_i), sin(theta_i) * sin(phi_i), cos(theta_i). The default + boresight is (phi1 (az), theta1(el)) = (180deg, 90deg) +%} + +%{ + 3GPP specifies the antenna gain only for |theta| < 90 degrees. + The gain outside of this region is approximated as min_gain_dB +%} +min_gain_dB = -50; + +max_gain_dB = 0; +c=physconst('LightSpeed'); +f=28e9; % operating frequency +k=2*pi*f/c; % wave number +a=10*c/f; % radius antenna aperture +arg_vec=k*a*sind(theta_vec); +pattern=besselj(1, arg_vec)./arg_vec; +pattern= 4*(abs(pattern).^2); + +% Manually set gain for theta = 0 +where_arg_zero=find(~theta_vec); +pattern(where_arg_zero)=1; +gain_dB=10*log10(pattern) + max_gain_dB; + +% Manually set gain to minimum gain for |theta| >= 90 degrees +where_arg_outside_pattern_domain = find(abs(theta_vec) >= 90); +gain_dB(where_arg_outside_pattern_domain)=min_gain_dB; + +%{ + Plot and output the C++ code which defines the reference vector of data points. + Test points are assumed to be encoded as C++ structs +%} +scatter(theta_vec, gain_dB); +out_str = ns3_output_from_vecs(az_vec, el_vec, gain_dB, max_gain_dB, min_gain_dB, a, f); + + +function theta = theta_from_sph_coord(phi1, theta1) +% theta_from_sph_coord Computes theta (the input of the radiation pattern formula) +% from the azimuth and elevation angles with resepct to boresight. +% phi1 the azimuth with respect to boresight +% theta1 the elevation angle with respect to boresight + + p1 = [sind(theta1)*cosd(phi1), sind(theta1)*sind(phi1), cosd(theta1)]; + % p2, i.e., the boresight direction, is phi1 (az), theta1(el)) = + % (180deg, 90deg) + theta2 = 90; phi2 = 180; + p2 = [sind(theta2)*cosd(phi2), sind(theta2)*sind(phi2), cosd(theta2)]; + theta = acosd(dot(p1,p2)); +end + +function [theta_vec, az_vec, el_vec] = theta_vec_from_sph_coord_vecs(az_vec, el_vec) +% theta_vec_from_sph_coord_vecs Computes the vectors of thetas +% (the inputs of the radiation pattern formula) +% from vectors of azimuth and elevation angles with resepct to boresight. +% phi1 the vector of azimuths with respect to boresight +% theta1 the vector of elevation angles with respect to boresight + if size(az_vec, 2) > 1 + el_vec = repelem(el_vec, size(az_vec, 2)); + elseif size(el_vec, 2) > 1 + az_vec = repelem(az_vec, size(el_vec, 2)); + end + theta_vec = arrayfun(@theta_from_sph_coord, az_vec, el_vec); +end + +function out_str = ns3_output_from_vecs(az_vec, el_vec, gain_dB_vec, max_gain, min_gain, a, f) +% ns3_output_from_vecs Outputs the reference gain values +% as a vector of vectors. +% The entries of the inner vectors, representing a single test point, +% represent: (gain at boresight (dB), gain outside the 3GPP pattern region (dB), +% aperture (meters), carrier frequency (Hz), test azimuth (degrees), +% test elevation angle (degrees), reference antenna gain (dB)) + out_str = ''; + for i=1:size(el_vec, 2) + out_str = [out_str, '{', ... + num2str(max_gain), ',', ... + num2str(min_gain), ',', ... + num2str(a), ',', ... + num2str(f), ',', ... + num2str(az_vec(i) - 180), ',', ... + num2str(el_vec(i)), ',', ... + num2str(gain_dB_vec(i)), '},']; + end +end diff --git a/src/antenna/test/test-circular-aperture-antenna.cc b/src/antenna/test/test-circular-aperture-antenna.cc new file mode 100644 index 000000000..e6cfa9377 --- /dev/null +++ b/src/antenna/test/test-circular-aperture-antenna.cc @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023 University of Padova, Dep. of Information Engineering, SIGNET lab. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ns3/circular-aperture-antenna-model.h" +#include "ns3/double.h" +#include "ns3/log.h" +#include "ns3/pointer.h" +#include "ns3/simulator.h" +#include "ns3/test.h" +#include "ns3/uinteger.h" +#include "ns3/uniform-planar-array.h" + +#include +#include +#include +#include + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE("TestCircularApertureAntennaModel"); + +/** + * \ingroup antenna-tests + * + * \brief CircularApertureAntennaModel Test Case + * + * Note: Since Clang libc++ does not support the Mathematical special functions (P0226R1) yet, this + * class falls back to Boost's implementation of cyl_bessel_j whenever the above standard library is + * in use. If neither is available in the host system, this class is not compiled. + */ +class CircularApertureAntennaModelTestCase : public TestCase +{ + public: + CircularApertureAntennaModelTestCase(); + + /** + * \brief Description of a single test point + * + * Description of a test point, which is characterized + * by the CircularApertureAntennaModel parameters, + * the directions towards which the antenna gain + * is to be tested, and the expected gain value. + */ + struct TestPoint + { + /** + * @brief Constructor + * + * @param antennaMaxGainDb the antenna maximum possible gain [dB] + * @param antennaMinGainDb the antenna minimum possible gain [dB] + * @param antennaCircularApertureRadius the radius of the parabolic aperture [m] + * @param operatingFrequency operating frequency [Hz] + * @param testAzimuth test azimuth [rad] + * @param testInclination test inclination [rad] + * @param expectedGain the expected gain value [dB] + */ + TestPoint(double antennaMaxGainDb, + double antennaMinGainDb, + double antennaCircularApertureRadius, + double operatingFrequency, + double testAzimuth, + double testInclination, + double expectedGain) + : m_antennaMaxGainDb(antennaMaxGainDb), + m_antennaMinGainDb(antennaMinGainDb), + m_antennaCircularApertureRadius(antennaCircularApertureRadius), + m_operatingFrequency(operatingFrequency), + m_testAzimuth(DegreesToRadians(testAzimuth)), + m_testInclination(DegreesToRadians(testInclination)), + m_expectedGain(expectedGain) + { + } + + double m_antennaMaxGainDb; ///< the antenna maximum possible gain [dB] + double m_antennaMinGainDb; ///< the antenna minimum possible gain [dB] + double m_antennaCircularApertureRadius; ///< the radius of the parabolic aperture [m] + double m_operatingFrequency; ///< operating frequency [Hz] + double m_testAzimuth; ///< test azimuth [rad] + double m_testInclination; ///< test inclination [rad] + double m_expectedGain; ///< the expected gain value [dB] + }; + + /** + * Generate a string containing all relevant parameters + * \param testPoint the parameter configuration to be tested + * \return the string containing all relevant parameters + */ + static std::string BuildNameString(TestPoint testPoint); + + /** + * Test the antenna gain for a specific parameter configuration, + * by comparing the antenna gain obtained using CircularApertureAntennaModel::GetGainDb + * and the one obtained using MATLAB. + * + * \param testPoint the parameter configuration to be tested + */ + void TestAntennaGain(TestPoint testPoint); + + private: + /** + * Run the test + */ + void DoRun() override; +}; + +CircularApertureAntennaModelTestCase::CircularApertureAntennaModelTestCase() + : TestCase("Creating CircularApertureAntennaModelTestCase") +{ +} + +std::string +CircularApertureAntennaModelTestCase::BuildNameString(TestPoint testPoint) +{ + std::ostringstream oss; + oss << " Maximum gain=" << testPoint.m_antennaMaxGainDb << "dB" + << " minimum gain=" << testPoint.m_antennaMinGainDb << "dB" + << ", antenna aperture radius=" << testPoint.m_antennaCircularApertureRadius << "m" + << ", frequency" << testPoint.m_operatingFrequency << "Hz" + << ", test inclination=" << RadiansToDegrees(testPoint.m_testInclination) << " deg" + << ", test azimuth=" << RadiansToDegrees(testPoint.m_testAzimuth) << " deg"; + return oss.str(); +} + +void +CircularApertureAntennaModelTestCase::TestAntennaGain(TestPoint testPoint) +{ + Ptr antenna = + CreateObjectWithAttributes( + "AntennaMaxGainDb", + DoubleValue(testPoint.m_antennaMaxGainDb), + "AntennaMinGainDb", + DoubleValue(testPoint.m_antennaMinGainDb), + "AntennaCircularApertureRadius", + DoubleValue(testPoint.m_antennaCircularApertureRadius), + "OperatingFrequency", + DoubleValue(testPoint.m_operatingFrequency)); + + Ptr upa = + CreateObjectWithAttributes("AntennaElement", + PointerValue(antenna), + "NumColumns", + UintegerValue(1), + "NumRows", + UintegerValue(1)); + + auto [fieldPhi, fieldTheta] = + upa->GetElementFieldPattern(Angles(testPoint.m_testAzimuth, testPoint.m_testInclination), + 0); + // Compute the antenna gain as the squared sum of the field pattern components + double gainDb = 10 * log10(fieldPhi * fieldPhi + fieldTheta * fieldTheta); + auto log = BuildNameString(testPoint); + NS_LOG_INFO(log); + NS_TEST_EXPECT_MSG_EQ_TOL(gainDb, testPoint.m_expectedGain, 0.1, log); +} + +void +CircularApertureAntennaModelTestCase::DoRun() +{ + // Vector of test points + std::vector testPoints = { + // MaxGainDb MinGainDb Radius (m) Freq (Hz) Azimuth (deg) Incl (deg) ExpGain (dB) + // Test invariant: gain always equal to max gain at boresight (inclination 90, azimuth = 0) + // for different frequency + {30, -30, 0.5, 2e9, 0, 90, 30}, + {30, -30, 2, 20e9, 0, 90, 30}, + // Test invariant: gain always equal to max gain at boresight (inclination 90, azimuth = 0) + // for different max gain + {20, -30, 0.5, 2e9, 0, 90, 20}, + {10, -30, 2, 20e9, 0, 90, 10}, + // Test invariant: gain always equal to min gain outside of |theta| < 90 deg + // for different frequency + {30, -100, 0.5, 2e9, 0, 0, -100}, + {30, -100, 2, 20e9, 0, 0, -100}, + // Test invariant: gain always equal to min gain outside of |theta| < 90 deg + // for different orientations + {30, -100, 0.5, 2e9, 180, 90, -100}, + {30, -100, 2, 20e9, -180, 90, -100}, + // Fixed elevation to boresight (90deg) and azimuth varying in [-90, 0] deg with steps of 10 + // degrees + {0, -50, 0.10707, 28000000000, -90, 90, -50}, + {0, -50, 0.10707, 28000000000, -80, 90, -49.8022}, + {0, -50, 0.10707, 28000000000, -70, 90, -49.1656}, + {0, -50, 0.10707, 28000000000, -60, 90, -60.9132}, + {0, -50, 0.10707, 28000000000, -50, 90, -59.2368}, + {0, -50, 0.10707, 28000000000, -40, 90, -44.6437}, + {0, -50, 0.10707, 28000000000, -30, 90, -43.9686}, + {0, -50, 0.10707, 28000000000, -20, 90, -36.3048}, + {0, -50, 0.10707, 28000000000, -10, 90, -30.5363}, + {0, -50, 0.10707, 28000000000, 0, 90, 0}, + // Fixed azimuth to boresight (0 deg) and azimuth varying in [0, 90] deg with steps of 9 + // degrees + {0, -50, 0.10707, 28e9, 0, 0, -50}, + {0, -50, 0.10707, 28e9, 0, 9, -49.7256}, + {0, -50, 0.10707, 28e9, 0, 18, -52.9214}, + {0, -50, 0.10707, 28e9, 0, 27, -48.6077}, + {0, -50, 0.10707, 28e9, 0, 36, -60.684}, + {0, -50, 0.10707, 28e9, 0, 45, -55.1468}, + {0, -50, 0.10707, 28e9, 0, 54, -42.9648}, + {0, -50, 0.10707, 28e9, 0, 63, -45.6472}, + {0, -50, 0.10707, 28e9, 0, 72, -48.6378}, + {0, -50, 0.10707, 28e9, 0, 81, -35.1613}, + {0, -50, 0.10707, 28e9, 0, 90, 0}}; + + // Call TestAntennaGain on each test point + for (auto& point : testPoints) + { + TestAntennaGain(point); + } +} + +/** + * \ingroup antenna-tests + * + * \brief UniformPlanarArray Test Suite + */ +class CircularApertureAntennaModelTestSuite : public TestSuite +{ + public: + CircularApertureAntennaModelTestSuite(); +}; + +CircularApertureAntennaModelTestSuite::CircularApertureAntennaModelTestSuite() + : TestSuite("circular-aperture-antenna-test", Type::UNIT) +{ + AddTestCase(new CircularApertureAntennaModelTestCase()); +} + +static CircularApertureAntennaModelTestSuite staticCircularApertureAntennaModelTestSuiteInstance;