spectrum: Add hexagonal wraparound model

This commit is contained in:
Gabriel Ferreira
2025-07-15 12:31:50 +02:00
parent 07e7a70b87
commit 7405e41c53
4 changed files with 520 additions and 0 deletions

View File

@@ -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
)

View File

@@ -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 <algorithm>
constexpr double M_SQRT3 = 1.732050807568877; // sqrt(3)
namespace ns3
{
/**
* Coefficients used to wraparound a cluster with 7 sites (1 ring)
*/
std::vector<Vector2D> 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<Vector2D> 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<WraparoundModel>()
.SetGroupName("Spectrum")
.AddConstructor<HexagonalWraparoundModel>();
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<Vector3D>& 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<double> 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<double> 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<double>((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

View File

@@ -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 <map>
#include <vector>
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<Vector3D>& 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<Vector3D> m_sitePositions; //!< site positions
};
} // namespace ns3
#endif /* HEXAGONAL_WRAPAROUND_H */

View File

@@ -0,0 +1,257 @@
/*
* Copyright (c) 2025 CTTC
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Gabriel Ferreira <gabrielcarvfer@gmail.com>
*/
#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<Vector3D>& sites, size_t numSites, Vector3D virtualPos)
{
double minDist = std::numeric_limits<double>::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<Vector3D> 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<HexagonalWraparoundModel> wraparoundModel =
m_alternativeConfig ? CreateObject<HexagonalWraparoundModel>()
: CreateObject<HexagonalWraparoundModel>(1000, numSites);
if (m_alternativeConfig)
{
wraparoundModel->SetSiteDistance(1000);
wraparoundModel->SetNumSites(numSites);
auto sitePositionsSlice =
std::vector<Vector3D>(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<MobilityModel>();
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<MobilityModel>();
// Test cases (target site number, expected UE virtual site, real user position, expected
// wrapped position)
std::vector<std::tuple<size_t, size_t, Vector3D, Vector3D>> 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<MobilityModel>();
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<bool> 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