Skip to content

Commit e8526a5

Browse files
committed
FIX(simulation): imt bs beamsteering angles
1 parent f4a26bd commit e8526a5

File tree

8 files changed

+129
-15
lines changed

8 files changed

+129
-15
lines changed

sharc/parameters/imt/parameters_antenna_imt.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class ParametersAntennaImt(ParametersBase):
5757
# PS: it isn't implemented for UEs
5858
# and current implementation doesn't make sense for UEs
5959
horizontal_beamsteering_range: tuple[float |
60-
int, float | int] = (-180., 180.)
61-
vertical_beamsteering_range: tuple[float | int, float | int] = (0., 180.)
60+
int, float | int] = (-180., 179.9999)
61+
vertical_beamsteering_range: tuple[float | int, float | int] = (0., 179.9999)
6262

6363
# Mechanical downtilt [degrees].
6464
# PS: downtilt doesn't make sense on UE's
@@ -174,11 +174,11 @@ def validate(self, ctx: str):
174174
f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
175175
"The second value must be bigger than the first"
176176
)
177-
if not all(map(lambda x: x >= -180. and x <= 180.,
177+
if not all(map(lambda x: x >= -180. and x < 180.,
178178
self.horizontal_beamsteering_range)):
179179
raise ValueError(
180180
f"Invalid {ctx}.horizontal_beamsteering_range={self.horizontal_beamsteering_range}\n"
181-
"Horizontal beamsteering limit angles must be in the range [-180, 180]"
181+
"Horizontal beamsteering limit angles must be in the range [-180, 180)"
182182
)
183183

184184
if isinstance(self.vertical_beamsteering_range, list):
@@ -199,11 +199,11 @@ def validate(self, ctx: str):
199199
f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
200200
"The second value must be bigger than the first"
201201
)
202-
if not all(map(lambda x: x >= 0. and x <= 180.,
202+
if not all(map(lambda x: x >= 0. and x < 180.,
203203
self.vertical_beamsteering_range)):
204204
raise ValueError(
205205
f"Invalid {ctx}.vertical_beamsteering_range={self.vertical_beamsteering_range}\n"
206-
"vertical beamsteering limit angles must be in the range [0, 180]"
206+
"vertical beamsteering limit angles must be in the range [0, 180)"
207207
)
208208

209209
def get_normalization_data_if_needed(self):

sharc/parameters/imt/parameters_imt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ class ParametersUE(ParametersBase):
110110

111111
def validate(self, ctx: str):
112112
"""Validate the UE antenna beamsteering range parameters."""
113-
if self.antenna.array.horizontal_beamsteering_range != (-180., 180.)\
114-
or self.antenna.array.vertical_beamsteering_range != (0., 180.):
113+
if self.antenna.array.horizontal_beamsteering_range != (-180., 179.9999)\
114+
or self.antenna.array.vertical_beamsteering_range != (0., 179.9999):
115115
raise NotImplementedError(
116116
"UE antenna beamsteering limit has not been implemented. Default values of\n"
117-
"horizontal = (-180., 180.), vertical = (0., 180.) should not be changed")
117+
"horizontal = (-180., 179.9999), vertical = (0., 179.9999) should not be changed")
118118

119119
ue: ParametersUE = field(default_factory=ParametersUE)
120120

sharc/simulation.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from sharc.station_manager import StationManager
2121
from sharc.results import Results
2222
from sharc.propagation.propagation_factory import PropagationFactory
23+
from sharc.support.sharc_utils import wrap2_180, clip_angle
2324

2425

2526
class Simulation(ABC, Observable):
@@ -482,6 +483,8 @@ def select_ue(self, random_number_gen: np.random.RandomState):
482483
self.ue, )
483484

484485
bs_active = np.where(self.bs.active)[0]
486+
487+
assert np.all((-180 <= self.bs.azimuth) & (self.bs.azimuth <= 180)), "BS azimuth angles should be in [-180, 180] range"
485488
for bs in bs_active:
486489
# select K UE's among the ones that are connected to BS
487490
random_number_gen.shuffle(self.link[bs])
@@ -494,9 +497,14 @@ def select_ue(self, random_number_gen: np.random.RandomState):
494497
# add beam to BS antennas
495498

496499
# limit beamforming angle
497-
bs_beam_phi = np.clip(
500+
beam_h_min, beam_h_max = wrap2_180(
501+
self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range + self.bs.azimuth[bs]
502+
)
503+
504+
bs_beam_phi = clip_angle(
498505
self.bs_to_ue_phi[bs, ue],
499-
*(self.parameters.imt.bs.antenna.array.horizontal_beamsteering_range + self.bs.azimuth[bs])
506+
beam_h_min,
507+
beam_h_max,
500508
)
501509

502510
bs_beam_theta = np.clip(

sharc/station_factory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from sharc.mask.spectral_mask_3gpp import SpectralMask3Gpp
6060
from sharc.mask.spectral_mask_mss import SpectralMaskMSS
6161
from sharc.support.sharc_geom import GeometryConverter
62+
from sharc.support.sharc_utils import wrap2_180
6263

6364

6465
class StationFactory(object):
@@ -119,7 +120,7 @@ def generate_imt_base_stations(
119120
else:
120121
imt_base_stations.height = param.bs.height * np.ones(num_bs)
121122

122-
imt_base_stations.azimuth = topology.azimuth
123+
imt_base_stations.azimuth = wrap2_180(topology.azimuth)
123124
imt_base_stations.active = random_number_gen.rand(
124125
num_bs,
125126
) < param.bs.load_probability

sharc/support/sharc_utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,49 @@ def is_float(s: str) -> bool:
2626
return False
2727

2828

29+
def wrap2_180(a):
30+
"""
31+
Wraps angles to the [-180, 180) range
32+
"""
33+
return (a + 180) % 360 - 180
34+
35+
36+
def angular_dist(a1, a2):
37+
"""
38+
Returns smallest angular distance between 2 angles
39+
in the [0, 180] range
40+
"""
41+
return np.abs(wrap2_180(a1 - a2))
42+
43+
44+
def clip_angle(a, a_min, a_max):
45+
"""
46+
Returns `a` if it is inside the angle range defined by [`a_min`, `a_max`]
47+
otherwise returns the closest bound, be it `a_min` or `a_max`
48+
It is assumed all angles passes are already in [-180, 180] range
49+
"""
50+
outside_rng = False
51+
# NOTE: angle limits such as -180 + [-60, 60]
52+
# should be passed as wrapped around [120, -120]
53+
# So it is important to check for this case
54+
if a_min > a_max:
55+
if a > a_max and a < a_min:
56+
outside_rng = True
57+
else:
58+
# Normal interval
59+
if a < a_min or a > a_max:
60+
outside_rng = True
61+
62+
# always clip to the closest bound
63+
if outside_rng:
64+
if angular_dist(a, a_min) < angular_dist(a, a_max):
65+
return a_min
66+
else:
67+
return a_max
68+
69+
return a
70+
71+
2972
def to_scalar(x):
3073
"""Convert a numpy scalar or array to a Python scalar if possible."""
3174
if isinstance(x, np.ndarray):

tests/parameters/test_parameters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ def test_parameters_imt(self):
6363
self.assertEqual(
6464
self.parameters.imt.ue.antenna.array.horizontal_beamsteering_range,
6565
(-180.,
66-
180.))
66+
179.9999))
6767
self.assertEqual(
68-
self.parameters.imt.ue.antenna.array.vertical_beamsteering_range, (0., 180.))
68+
self.parameters.imt.ue.antenna.array.vertical_beamsteering_range, (0., 179.9999))
6969
self.assertEqual(self.parameters.imt.ue.k, 3)
7070
self.assertEqual(self.parameters.imt.ue.k_m, 1)
7171
self.assertEqual(self.parameters.imt.ue.indoor_percent, 5.0)

tests/test_sharc_utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import unittest
2+
3+
from sharc.support.sharc_utils import clip_angle
4+
5+
6+
class StationTest(unittest.TestCase):
7+
"""Unit tests for some utilities."""
8+
9+
def setUp(self):
10+
pass
11+
12+
def test_clip_angle(self):
13+
"""
14+
Testing in range for non wrapping around angles
15+
"""
16+
for a in [90, 100, 180, -180, -135.01]:
17+
self.assertEqual(clip_angle(a, 0, 90), 90)
18+
for a in [-134.99, -90, -45, 0]:
19+
self.assertEqual(clip_angle(a, 0, 90), 0)
20+
for a in [0, 45, 90]:
21+
self.assertEqual(clip_angle(a, 0, 90), a)
22+
23+
for a in [-90, -80, 0, 44.99]:
24+
self.assertEqual(clip_angle(a, -180, -90), -90)
25+
for a in [45.01, 90, 135, 180, -180]:
26+
self.assertEqual(clip_angle(a, -180, -90), -180)
27+
for a in [-180, -135, -90]:
28+
self.assertEqual(clip_angle(a, -180, -90), a)
29+
30+
for a in [180, -180, -135, -90.01]:
31+
self.assertEqual(clip_angle(a, 0, 180), 180)
32+
for a in [-89.99, -45, 0]:
33+
self.assertEqual(clip_angle(a, 0, 180), 0)
34+
for a in [0, 45, 90, 135, 180]:
35+
self.assertEqual(clip_angle(a, 0, 180), a)
36+
37+
"""
38+
Testing in range for wrapping around angles
39+
"""
40+
for a in [-90, -80, 0, 44.99]:
41+
self.assertEqual(clip_angle(a, 180, -90), -90)
42+
for a in [45.01, 90, 135, 180, 180]:
43+
self.assertEqual(clip_angle(a, 180, -90), 180)
44+
for a in [-180, -135, -90]:
45+
self.assertEqual(clip_angle(a, 180, -90), a)
46+
47+
for a in [-180, -135, -90.01]:
48+
self.assertEqual(clip_angle(a, 0, -180), -180)
49+
for a in [-89.99, -45, 0]:
50+
self.assertEqual(clip_angle(a, 0, -180), 0)
51+
for a in [0, 45, 90, 135, -180]:
52+
self.assertEqual(clip_angle(a, 0, -180), a)
53+
54+
self.assertEqual(clip_angle(91, 180, 0), 180)
55+
self.assertEqual(clip_angle(89, 180, 0), 0)
56+
57+
self.assertEqual(clip_angle(91, -180, 0), -180)
58+
self.assertEqual(clip_angle(89, -180, 0), 0)
59+
60+
61+
if __name__ == '__main__':
62+
unittest.main()

tests/test_simulation_uplink.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ def test_beamforming_gains(self):
868868
# Physical pointing angles
869869
self.assertEqual(self.simulation.bs.antenna[0].azimuth, 0)
870870
self.assertEqual(self.simulation.bs.antenna[0].elevation, -10)
871-
self.assertEqual(self.simulation.bs.antenna[1].azimuth, 180)
871+
self.assertEqual(self.simulation.bs.antenna[1].azimuth, -180)
872872
self.assertEqual(self.simulation.bs.antenna[0].elevation, -10)
873873

874874
# Change UE pointing

0 commit comments

Comments
 (0)