From 7405e41c53bf6ddcc03c7a00836cde2379d2156d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 15 Jul 2025 12:31:50 +0200 Subject: [PATCH] spectrum: Add hexagonal wraparound model --- src/spectrum/CMakeLists.txt | 3 + .../model/hexagonal-wraparound-model.cc | 140 ++++++++++ .../model/hexagonal-wraparound-model.h | 120 ++++++++ src/spectrum/test/wraparound-model-test.cc | 257 ++++++++++++++++++ 4 files changed, 520 insertions(+) create mode 100644 src/spectrum/model/hexagonal-wraparound-model.cc create mode 100644 src/spectrum/model/hexagonal-wraparound-model.h create mode 100644 src/spectrum/test/wraparound-model-test.cc diff --git a/src/spectrum/CMakeLists.txt b/src/spectrum/CMakeLists.txt index cc2af34d9..4dafb96c8 100644 --- a/src/spectrum/CMakeLists.txt +++ b/src/spectrum/CMakeLists.txt @@ -10,6 +10,7 @@ set(source_files model/friis-spectrum-propagation-loss.cc model/half-duplex-ideal-phy-signal-parameters.cc model/half-duplex-ideal-phy.cc + model/hexagonal-wraparound-model.cc model/ism-spectrum-value-helper.cc model/matrix-based-channel-model.cc model/microwave-oven-spectrum-value-helper.cc @@ -51,6 +52,7 @@ set(header_files model/friis-spectrum-propagation-loss.h model/half-duplex-ideal-phy-signal-parameters.h model/half-duplex-ideal-phy.h + model/hexagonal-wraparound-model.h model/ism-spectrum-value-helper.h model/matrix-based-channel-model.h model/microwave-oven-spectrum-value-helper.h @@ -98,4 +100,5 @@ build_lib( test/tv-helper-distribution-test.cc test/tv-spectrum-transmitter-test.cc test/two-ray-splm-test-suite.cc + test/wraparound-model-test.cc ) diff --git a/src/spectrum/model/hexagonal-wraparound-model.cc b/src/spectrum/model/hexagonal-wraparound-model.cc new file mode 100644 index 000000000..6b848be2c --- /dev/null +++ b/src/spectrum/model/hexagonal-wraparound-model.cc @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2025 CTTC + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "hexagonal-wraparound-model.h" + +#include "ns3/log.h" + +#include + +constexpr double M_SQRT3 = 1.732050807568877; // sqrt(3) + +namespace ns3 +{ +/** + * Coefficients used to wraparound a cluster with 7 sites (1 ring) + */ +std::vector WRAPPING_COEFF_CLUSTER7 = + {{0.0, 0.0}, {-3.0, 2.0}, {3.0, -2.0}, {-1.5, -2.5}, {1.5, 2.5}, {-4.5, -0.5}, {4.5, 0.5}}; + +/** + * Coefficients used to wraparound a cluster with 19 sites (3 rings) + */ +std::vector WRAPPING_COEFF_CLUSTER19 = + {{0.0, 0.0}, {-3.0, -4.0}, {3.0, 4.0}, {-4.5, 3.5}, {4.5, -3.5}, {-7.5, -0.5}, {7.5, 0.5}}; + +NS_LOG_COMPONENT_DEFINE("HexagonalWraparoundModel"); + +NS_OBJECT_ENSURE_REGISTERED(HexagonalWraparoundModel); + +TypeId +HexagonalWraparoundModel::GetTypeId() +{ + static TypeId tid = TypeId("ns3::HexagonalWraparoundModel") + .SetParent() + .SetGroupName("Spectrum") + .AddConstructor(); + return tid; +} + +HexagonalWraparoundModel::HexagonalWraparoundModel() + : m_numSites(1) +{ +} + +HexagonalWraparoundModel::HexagonalWraparoundModel(double isd, size_t numSites) + : m_numSites(numSites) +{ + SetSiteDistance(isd); +} + +void +HexagonalWraparoundModel::SetSiteDistance(double isd) +{ + m_isd = isd; + m_radius = isd / M_SQRT3; +} + +void +HexagonalWraparoundModel::SetNumSites(uint8_t numSites) +{ + NS_ASSERT((numSites == 1) || (numSites == 7) || (numSites == 19)); + m_numSites = numSites; +} + +void +HexagonalWraparoundModel::AddSitePosition(const Vector3D& pos) +{ + NS_LOG_FUNCTION(this << pos); + NS_ASSERT(m_sitePositions.size() < m_numSites); + m_sitePositions.push_back(pos); +} + +void +HexagonalWraparoundModel::SetSitePositions(const std::vector& positions) +{ + NS_ASSERT(positions.size() == m_numSites); + m_sitePositions = positions; +} + +double +HexagonalWraparoundModel::CalculateDistance(const Vector3D& a, const Vector3D& b) const +{ + return (a - GetVirtualPosition(b, a)).GetLength(); +} + +// Wraparound calculation is based on +// R. S. Panwar and K. M. Sivalingam, "Implementation of wrap +// around mechanism for system level simulation of LTE cellular +// networks in NS3," 2017 IEEE 18th International Symposium on +// A World of Wireless, Mobile and Multimedia Networks (WoWMoM), +// Macau, 2017, pp. 1-9, doi: 10.1109/WoWMoM.2017.7974289. +Vector3D +HexagonalWraparoundModel::GetSitePosition(const Vector3D& pos) const +{ + NS_ASSERT(m_numSites == m_sitePositions.size()); + + std::vector virtDists; + virtDists.resize(m_numSites); + std::transform(m_sitePositions.begin(), + m_sitePositions.end(), + virtDists.begin(), + [&](const Vector3D& sitePos) { + return (this->GetVirtualPosition(pos, sitePos) - sitePos).GetLength(); + }); + auto idx = std::min_element(virtDists.begin(), virtDists.end()) - virtDists.begin(); + return m_sitePositions[idx]; +} + +Vector3D +HexagonalWraparoundModel::GetVirtualPosition(const Vector3D txPos, const Vector3D rxPos) const +{ + NS_ASSERT_MSG((m_numSites == 1) || (m_numSites == 7) || (m_numSites == 19), + "invalid numSites: " << m_numSites); + + if (m_numSites == 1) + { + return txPos; + } + + auto& coeffs = (m_numSites == 7) ? WRAPPING_COEFF_CLUSTER7 : WRAPPING_COEFF_CLUSTER19; + + std::vector virtDists; + virtDists.resize(coeffs.size()); + std::transform(coeffs.begin(), coeffs.end(), virtDists.begin(), [&](const Vector2D& coeff) { + Vector3D virtPos2 = txPos; + virtPos2.x += coeff.x * m_radius; + virtPos2.y += coeff.y * m_isd; + return static_cast((virtPos2 - rxPos).GetLength()); + }); + + auto idx = std::min_element(virtDists.begin(), virtDists.end()) - virtDists.begin(); + Vector3D offset(m_radius * coeffs[idx].x, m_isd * coeffs[idx].y, 0.0); + return txPos + offset; +} + +} // namespace ns3 diff --git a/src/spectrum/model/hexagonal-wraparound-model.h b/src/spectrum/model/hexagonal-wraparound-model.h new file mode 100644 index 000000000..33a1394b2 --- /dev/null +++ b/src/spectrum/model/hexagonal-wraparound-model.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025 CTTC + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#ifndef HEXAGONAL_WRAPAROUND_H +#define HEXAGONAL_WRAPAROUND_H + +#include "wraparound-model.h" + +#include "ns3/vector.h" + +#include +#include + +namespace ns3 +{ +/** + * @ingroup propagation + * @brief Hexagonal wraparound model wraps nodes around in space based on an + * hexagonal deployment. It is used to determine a virtual position after + * wraparound and calculate the distance between a point of reference and + * the virtual position. + * + * This model is used for the hexagonal deployments typical of mobile networks. + * It supports rings 0 (1 site), 1 (7 sites) and 3 rings (19 sites). + * To use it, set the inter-site distance (isd) and the number of sites. + * Then, add the position coordinates of the sites. + * All space coordinates in this class and its subclasses are understood + * to be meters or meters/s. i.e., they are all metric international units. + * + * When GetRelativeVirtualPosition (absPos1, absPos2) is called, + * the relative position of the absPos2 is calculated respective to the + * reference coordinate absPos1, applying the wrapping based on the model + * described in: + * + * R. S. Panwar and K. M. Sivalingam, "Implementation of wrap + * around mechanism for system level simulation of LTE cellular + * networks in NS3," 2017 IEEE 18th International Symposium on + * A World of Wireless, Mobile and Multimedia Networks (WoWMoM), + * Macau, 2017, pp. 1-9, doi: 10.1109/WoWMoM.2017.7974289. + */ +class HexagonalWraparoundModel : public WraparoundModel +{ + public: + /** + * Register this type with the TypeId system. + * @return the object TypeId + */ + static TypeId GetTypeId(); + + /** + * @brief Default constructor + */ + HexagonalWraparoundModel(); + + /** + * @brief Constructor + * @param isd inter-site distance + * @param numSites number of sites + */ + HexagonalWraparoundModel(double isd, size_t numSites); + + /** + * @brief Set site distance + * @param isd site distance + */ + void SetSiteDistance(double isd); + + /** + * @brief Set number of sites + * @param numSites number of sites + */ + void SetNumSites(uint8_t numSites); + + /** + * @brief Add a site position + * @param pos position of a site + */ + void AddSitePosition(const Vector3D& pos); + + /** + * @brief Set site positions + * @param positions positions of all sites + */ + void SetSitePositions(const std::vector& positions); + + /** + * @brief Calculate the site position + * @param pos original position + * @return closest cell center based on wraparound distance + */ + Vector3D GetSitePosition(const Vector3D& pos) const; + + /** + * @brief Calculate distance after wraparound between two points + * @param a position of point a + * @param b position of point b + * @return wraparound distance between a and b + */ + double CalculateDistance(const Vector3D& a, const Vector3D& b) const; + /** + * @brief Get virtual position of txPos with respect to rxPos + * @param txPos + * @param rxPos + * @return virtual position of txPos + */ + Vector3D GetVirtualPosition(const Vector3D txPos, const Vector3D rxPos) const override; + + private: + double m_isd; //!< distance between sites + double m_radius; //!< site radius + uint8_t m_numSites; //!< number of sites + std::vector m_sitePositions; //!< site positions +}; +} // namespace ns3 + +#endif /* HEXAGONAL_WRAPAROUND_H */ diff --git a/src/spectrum/test/wraparound-model-test.cc b/src/spectrum/test/wraparound-model-test.cc new file mode 100644 index 000000000..1a0405907 --- /dev/null +++ b/src/spectrum/test/wraparound-model-test.cc @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2025 CTTC + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Gabriel Ferreira + */ + +#include "ns3/boolean.h" +#include "ns3/config.h" +#include "ns3/hexagonal-wraparound-model.h" +#include "ns3/mobility-helper.h" +#include "ns3/node-container.h" +#include "ns3/simulator.h" +#include "ns3/test.h" +#include "ns3/waypoint-mobility-model.h" + +using namespace ns3; + +/** + * @ingroup propagation-test + * + * @brief Wraparound Model Test + */ +class WraparoundModelTest : public TestCase +{ + public: + /** + * Constructor for test case + * @param name description of test case + * @param numRings number of rings used in the deployment + * @param alternativeConfig wraparound is configured manually when false, or via constructor + * when true. + */ + WraparoundModelTest(std::string name, int numRings, bool alternativeConfig) + : TestCase(name), + m_numRings(numRings), + m_alternativeConfig(alternativeConfig) + { + } + + private: + int m_numRings; ///< number of wrings to wraparound + bool m_alternativeConfig; ///< indicate whether to configure isd and numSites via constructor or + ///< manually + private: + void DoRun() override; +}; + +size_t +GetNearestSite(std::vector& sites, size_t numSites, Vector3D virtualPos) +{ + double minDist = std::numeric_limits::max(); + size_t minSite = 0; + for (size_t siteI = 0; siteI < sites.size() && siteI < numSites; siteI++) + { + auto dist = CalculateDistance(sites.at(siteI), virtualPos); + if (dist < minDist) + { + minDist = dist; + minSite = siteI; + } + } + return minSite; +} + +void +WraparoundModelTest::DoRun() +{ + int numSites = 0; + switch (m_numRings) + { + case 0: + numSites = 1; + break; + case 1: + numSites = 7; + break; + case 3: + numSites = 19; + break; + default: + NS_TEST_EXPECT_MSG_EQ(false, + true, + "Wraparound does not support " << m_numRings << " rings"); + break; + } + + NodeContainer userNodes(1); + NodeContainer siteNodes(numSites); + + MobilityHelper mobilityHelper; + mobilityHelper.SetMobilityModel("ns3::ConstantPositionMobilityModel"); + + // Site positions + // clang-format off + std::vector sitePositions = { + Vector3D( 0, 0, 30), // Site 01 + Vector3D( 1000, 0, 30), // Site 02 + Vector3D( 500, 866, 30), // Site 03 + Vector3D( -500, 866, 30), // Site 04 + Vector3D(-1000, 0, 30), // Site 05 + Vector3D( -500, -866, 30), // Site 06 + Vector3D( 500, -866, 30), // Site 07 + Vector3D( 2000, 0, 30), // Site 08 + Vector3D( 1500, 866, 30), // Site 09 + Vector3D( 500, 1732, 30), // Site 10 + Vector3D( -500, 1732, 30), // Site 11 + Vector3D(-1500, 866, 30), // Site 12 + Vector3D(-2000, 0, 30), // Site 13 + Vector3D(-1500, -866, 30), // Site 14 + Vector3D( -500, -1732, 30), // Site 15 + Vector3D( 500, -1732, 30), // Site 16 + Vector3D( 1500, -866, 30), // Site 17 + Vector3D( 1000, 1732, 30), // Site 18 + Vector3D(-1000, 1732, 30), // Site 19 + }; + // clang-format on + + Ptr wraparoundModel = + m_alternativeConfig ? CreateObject() + : CreateObject(1000, numSites); + + if (m_alternativeConfig) + { + wraparoundModel->SetSiteDistance(1000); + wraparoundModel->SetNumSites(numSites); + auto sitePositionsSlice = + std::vector(sitePositions.begin(), sitePositions.begin() + numSites); + wraparoundModel->SetSitePositions(sitePositionsSlice); + } + + // Install mobility models + mobilityHelper.Install(userNodes); + mobilityHelper.Install(siteNodes); + + // Add site positions and wraparound model + for (auto i = 0; i < numSites; i++) + { + auto siteMm = siteNodes.Get(i)->GetObject(); + siteMm->SetPosition(sitePositions.at(i)); + if (!m_alternativeConfig) + { + wraparoundModel->AddSitePosition(sitePositions.at(i)); + } + } + + // Retrieve user node mobility to move it around + auto userMm = userNodes.Get(0)->GetObject(); + + // Test cases (target site number, expected UE virtual site, real user position, expected + // wrapped position) + std::vector> testPoint; + // clang-format off + switch (m_numRings) + { + case 3: // Cases involving sites 8-19 + testPoint.emplace_back( 7, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back( 8, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back( 9, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(10, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(11, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(12, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(13, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(14, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(15, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(16, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(17, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(18, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back( 7, 0, Vector3D( 100, 0, 0), Vector3D( 100, 0, 0)); + testPoint.emplace_back( 8, 0, Vector3D( 500, 0, 0), Vector3D( 500, 0, 0)); + testPoint.emplace_back( 9, 1, Vector3D( 1000, 0, 0), Vector3D( 1000, 0, 0)); + testPoint.emplace_back(10, 2, Vector3D( 1000, 1000, 0), Vector3D( 1000, 1000, 0)); + testPoint.emplace_back(11, 0, Vector3D( -500, 0, 0), Vector3D( -500, 0, 0)); + testPoint.emplace_back(12, 0, Vector3D( 0, -500, 0), Vector3D( 0, -500, 0)); + testPoint.emplace_back(13, 5, Vector3D(-1000, -1000, 0), Vector3D(-1000, -1000, 0)); + testPoint.emplace_back(14, 13, Vector3D( 2000, -1000, 0), Vector3D(-2330, -1500, 0)); + testPoint.emplace_back(15, 16, Vector3D(-1000, 2000, 0), Vector3D( 1598, -1500, 0)); + testPoint.emplace_back(16, 15, Vector3D( 2000, 2000, 0), Vector3D( 267, -2000, 0)); + testPoint.emplace_back(17, 10, Vector3D(-2000, -2000, 0), Vector3D( -268, 2000, 0)); + testPoint.emplace_back(18, 11, Vector3D(-2000, 1500, 0), Vector3D(-2000, 1500, 0)); + break; + case 1: // Cases involving sites 2-7 + testPoint.emplace_back(1, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(2, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(3, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(4, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(5, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(6, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(1, 1, Vector3D( 1000, 0, 0), Vector3D( 1000, 0, 0)); + testPoint.emplace_back(2, 3, Vector3D( 1000, -500, 0), Vector3D( -732, 1500, 0)); + testPoint.emplace_back(3, 4, Vector3D( 1000, 500, 0), Vector3D(-1598, 0, 0)); + testPoint.emplace_back(4, 4, Vector3D(-1000, 0, 0), Vector3D(-1000, 0, 0)); + testPoint.emplace_back(5, 6, Vector3D(-1000, 500, 0), Vector3D( 732, -1500, 0)); + testPoint.emplace_back(6, 1, Vector3D(-1000, -500, 0), Vector3D( 1598, 0, 0)); + break; + case 0: // Cases involving site 1. No wraparound. + testPoint.emplace_back(0, 0, Vector3D( 0, 0, 0), Vector3D( 0, 0, 0)); + testPoint.emplace_back(0, 0, Vector3D( 1000, 1000, 0), Vector3D( 1000, 1000, 0)); + testPoint.emplace_back(0, 0, Vector3D( 1000, -1000, 0), Vector3D( 1000, -1000, 0)); + testPoint.emplace_back(0, 0, Vector3D(-1000, 1000, 0), Vector3D(-1000, 1000, 0)); + testPoint.emplace_back(0, 0, Vector3D(-1000, -1000, 0), Vector3D(-1000, -1000, 0)); + testPoint.emplace_back(0, 0, Vector3D( 2000, 2000, 0), Vector3D( 2000, 2000, 0)); + testPoint.emplace_back(0, 0, Vector3D( 2000, -2000, 0), Vector3D( 2000, -2000, 0)); + testPoint.emplace_back(0, 0, Vector3D(-2000, 2000, 0), Vector3D(-2000, 2000, 0)); + testPoint.emplace_back(0, 0, Vector3D(-2000, -2000, 0), Vector3D(-2000, -2000, 0)); + default: + break; + } + // clang-format on + + for (auto [siteNumber, expectedVirtualUserSite, realUserPos, expectedVirtualPos] : testPoint) + { + userMm->SetPosition(realUserPos); + auto siteMm = siteNodes.Get(siteNumber)->GetObject(); + auto virtualPos = + wraparoundModel->GetVirtualPosition(userMm->GetPosition(), siteMm->GetPosition()); + auto nearestSiteToVirtualUser = GetNearestSite(sitePositions, numSites, virtualPos); + NS_TEST_EXPECT_MSG_EQ(nearestSiteToVirtualUser, + expectedVirtualUserSite, + "Virtual position leads to from real site " + << GetNearestSite(sitePositions, numSites, realUserPos) + << " to incorrect site " << nearestSiteToVirtualUser); + NS_TEST_EXPECT_MSG_LT_OR_EQ(CalculateDistance(virtualPos, expectedVirtualPos), + 5, + "Expected " << expectedVirtualPos << " and obtained " + << virtualPos << " virtual positions differ"); + } + // Move user node and check virtual positions correspond to the expected sites + Simulator::Stop(MilliSeconds(1)); + Simulator::Run(); + Simulator::Destroy(); +} + +/** + * @ingroup propagation-test + * + * @brief Wraparound Model Test Suite + */ +static struct WraparoundModelTestSuite : public TestSuite +{ + WraparoundModelTestSuite() + : TestSuite("wraparound-model", Type::UNIT) + { + std::vector alternativeConfig{false, true}; + for (auto altConf : alternativeConfig) + { + // Supported ring numbers + AddTestCase(new WraparoundModelTest("Check wraparound with 0 rings", 0, altConf), + TestCase::Duration::QUICK); + AddTestCase(new WraparoundModelTest("Check wraparound with 1 rings", 1, altConf), + TestCase::Duration::QUICK); + AddTestCase(new WraparoundModelTest("Check wraparound with 3 rings", 3, altConf), + TestCase::Duration::QUICK); + } + } +} g_WraparoundModelTestSuite; ///< the test suite