#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import (
absolute_import, unicode_literals, division, print_function
)
# from functools import partial, lru_cache
from astropy import units as apu
import numpy as np
from . import cyimt
from .helper import _Qinv
from .. import conversions as cnv
from .. import utils
# import ipdb
__all__ = [
'imt_rural_macro_losses', 'imt_urban_macro_losses',
'imt_urban_micro_losses', 'P_pusch', 'UE_MIN_MAX_DISTANCES',
'clutter_imt',
]
UE_MIN_MAX_DISTANCES = {
'rural_macro': (10, 10000),
'urban_macro': (10, 5000),
'urban_micro': (10, 5000),
}
# UE_MIN_MAX_DISTANCES.__doc__ = '''
# Minimum and maximum distances that UEs have from base stations in each of
# the IMT propagation models. Below and beyond these, the path losses will
# be NaN.
# '''
# Note, we have to curry the quantities here, because Cython produces
# "built-in" functions that don't provide a signature (such that
# ranged_quantity_input fails)
def _clutter_imt(
freq,
dist,
location_percent,
num_end_points=1,
):
assert num_end_points in [1, 2]
L_l = 23.5 + 9.6 * np.log10(freq)
L_s = 32.98 + 23.9 * np.log10(dist) + 3.0 * np.log10(freq)
L_clutter = -5 * np.log10(
np.power(10, -0.2 * L_l) + np.power(10, -0.2 * L_s)
) - 6 * _Qinv(location_percent / 100.)
if num_end_points == 2:
L_clutter *= 2
return L_clutter
[docs]
@utils.ranged_quantity_input(
freq=(2, 67, apu.GHz),
dist=(0.25, None, apu.km),
location_percent=(0, 100, apu.percent),
strip_input_units=True, output_unit=cnv.dB
)
def clutter_imt(
freq,
dist,
location_percent,
num_end_points=1,
):
'''
Calculate the Clutter loss according to IMT.CLUTTER document (method 2).
Parameters
----------
freq : `~astropy.units.Quantity`
Frequency of radiation [GHz]
dist : `~astropy.units.Quantity`
Distance between Tx/Rx antennas [km]
Minimal distance must be 0.25 km (single endpoint clutter) or 1 km
(if both endpoints are to be corrected for clutter)
location_percent : `~astropy.units.Quantity`
Percentage of locations for which the clutter loss `L_clutter`
(calculated with this function) will not be exceeded [%]
num_end_points : int, optional
number of endpoints affected by clutter, allowed values: 1, 2
Returns
-------
L_clutter : `~astropy.units.Quantity`
Clutter loss [dB]
Notes
-----
- The algorithm is independent of effective antenna height (w.r.t.
clutter height), i.e., it doesn't distinguish between terminals which
are close to the ground and those closer to the top of the building.
However, the model is only appropriate if the terminal is "in the
clutter", below the rooftops.
- The result of this function is to be understood as a cumulative
value. For example, if `location_percent = 2%`, it means that for
2% of all possible locations, the clutter loss will not exceed the
returned `L_clutter` value, for the remaining 98% of locations it
will therefore be lower than `L_clutter`. The smaller `location_percent`,
the smaller the returned `L_clutter`, i.e., low clutter attenuations
are more unlikely.
- This model was proposed by ITU study group SG 3 to replace
`~pycraf.pathprof.clutter_correction` for IMT 5G studies (especially,
at higher frequencies, where multipath effects play a role in
urban and suburban areas).
'''
return _clutter_imt(
freq,
dist,
location_percent,
num_end_points=num_end_points,
)
[docs]
@utils.ranged_quantity_input(
freq=(0.5, 30, apu.GHz),
dist_2d=(0, 100000, apu.m),
h_bs=(10, 150, apu.m),
h_ue=(1, 10, apu.m),
W=(5, 50, apu.m),
h=(5, 50, apu.m),
strip_input_units=True, allow_none=False,
output_unit=(cnv.dB, cnv.dB, cnv.dimless)
)
def imt_rural_macro_losses(
freq,
dist_2d,
h_bs=35 * apu.m, h_ue=1.5 * apu.m,
W=20 * apu.m, h=5 * apu.m,
):
'''
Calculate los/non-los propagation losses Rural-Macro IMT scenario.
The computation is in accordance with `3GPP TR 38.901 Table 7.4.1-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Parameters
----------
freq : `~astropy.units.Quantity`
Frequency of radiation [GHz]
dist_2d : `~astropy.units.Quantity`
Distance on the ground between BS and UE device [m]
Note: Well-defined only for distances between 10 m and 10 km.
h_bs : `~astropy.units.Quantity`, optional
Basestation height [m] (Default: 35 m)
h_ue : `~astropy.units.Quantity`, optional
User equipment height [m] (Default: 1.5 m)
W : `~astropy.units.Quantity`, optional
Average street width [m] (Default: 20 m)
h : `~astropy.units.Quantity`, optional
Average building height [m] (Default: 5 m)
Returns
-------
PL_los : `~astropy.units.Quantity`
Path loss for line-of-sight cases [dB]
PL_nlos : `~astropy.units.Quantity`
Path loss for non-line-of-sight cases [dB]
los_prob : `~astropy.units.Quantity`
Probability for a path to be line-of-sight. [dimless]
(see Notes and Examples)
Notes
-----
- In statistical simulations, the LoS and Non-LoS cases occur with
certain probabilities. For sampling of path losses the return
parameter `los_prob` can be used, which accounts for the likelihoods
according to `3GPP TR 38.901 Table 7.4.2-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Examples
--------
.. testsetup::
>>> import numpy as np
>>> np.set_printoptions(3)
A typical usage, which also accounts for the line-of-sight probabilities,
would be::
>>> import numpy as np
>>> from pycraf import conversions as cnv
>>> from pycraf import pathprof
>>> from astropy import units as u
>>> from astropy.utils.misc import NumpyRNGContext
>>> freq = 1 * u.GHz
>>> h_bs, h_ue = 35 * u.m, 1.5 * u.m
>>> distances = [5, 20, 1000, 20000] * u.m # 2D distances
>>> # Note: too small or large distances will lead to NaN values
>>> PL_los, PL_nlos, los_prob = pathprof.imt_rural_macro_losses(
... freq, distances, h_bs=h_bs, h_ue=h_ue
... )
>>> PL_los # doctest: +FLOAT_CMP
<Decibel [ nan, 64.381, 94.578, nan] dB>
>>> PL_nlos # doctest: +FLOAT_CMP
<Decibel [ nan, 65.108, 119.543, nan] dB>
>>> los_prob # doctest: +FLOAT_CMP
<Quantity [1.000e+00, 9.900e-01, 3.716e-01, 2.082e-09]>
>>> # randomly assign LOS or Non-LOS type to UE (according to above prob.)
>>> with NumpyRNGContext(0):
... los_type = np.random.uniform(0, 1, distances.size) < los_prob
>>> # note: los_type == True : LOS
>>> # los_type == False: Non-LOS
>>> los_type
array([ True, True, False, False])
>>> PL = np.where(
... los_type, PL_los.to_value(cnv.dB), PL_nlos.to_value(cnv.dB)
... ) * cnv.dB
>>> PL # doctest: +FLOAT_CMP
<Decibel [ nan, 64.381, 119.543, nan] dB>
.. testcleanup::
>>> np.set_printoptions()
'''
pl_los, pl_nlos = cyimt.rural_macro_losses_cython(
freq, dist_2d, h_bs, h_ue, W, h,
)
# probability to have a LOS path;
# see 3GPP TR 38.901, Table 7.4.2
los_prob = np.exp(-(dist_2d - 10) / 1000)
los_prob[dist_2d < 10] = 1.
los_prob = np.broadcast_to(los_prob, pl_los.shape)
return pl_los, pl_nlos, los_prob
[docs]
@utils.ranged_quantity_input(
freq=(0.5, 30, apu.GHz),
dist_2d=(0, 100000, apu.m),
h_bs=(10, 150, apu.m), # according to 3GPP TR 38.901 only 25 m allowed!?
h_ue=(1, 13, apu.m), # for larger h_ue, need to implement "C" function
h_e=(0, 20, apu.m),
strip_input_units=True, allow_none=False,
output_unit=(cnv.dB, cnv.dB, cnv.dimless)
)
def imt_urban_macro_losses(
freq,
dist_2d,
h_bs=25 * apu.m, h_ue=1.5 * apu.m,
# h_e=1 * apu.m,
):
'''
Calculate los/non-los propagation losses Rural-Macro IMT scenario.
The computation is in accordance with `3GPP TR 38.901 Table 7.4.1-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Parameters
----------
freq : `~astropy.units.Quantity`
Frequency of radiation [GHz]
dist_2d : `~astropy.units.Quantity`
Distance on the ground between BS and UE device [m]
Note: Well-defined only for distances between 10 m and 5 km.
h_bs : `~astropy.units.Quantity`, optional
Basestation height [m] (Default: 35 m)
h_ue : `~astropy.units.Quantity`, optional
User equipment height [m] (Default: 1.5 m)
Note: in the `pycraf` implementation this is restricted to
`h_ue < 13 m`. 3GPP TR 38.901 also has a model for heights up
to 22.5 m, but this involves a `h_e` different from 1 m and is
probabilistic, which would make the interface much more complicated.
Returns
-------
PL_los : `~astropy.units.Quantity`
Path loss for line-of-sight cases [dB]
PL_nlos : `~astropy.units.Quantity`
Path loss for non-line-of-sight cases [dB]
los_prob : `~astropy.units.Quantity`
Probability for a path to be line-of-sight. [dimless]
(see Notes and Examples)
Notes
-----
- In statistical simulations, the LoS and Non-LoS cases occur with
certain probabilities. For sampling of path losses the return
parameter `los_prob` can be used, which accounts for the likelihoods
according to `3GPP TR 38.901 Table 7.4.2-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Examples
--------
.. testsetup::
>>> import numpy as np
>>> np.set_printoptions(3)
A typical usage, which also accounts for the line-of-sight probabilities,
would be::
>>> import numpy as np
>>> from pycraf import conversions as cnv
>>> from pycraf import pathprof
>>> from astropy import units as u
>>> from astropy.utils.misc import NumpyRNGContext
>>> freq = 1 * u.GHz
>>> h_bs, h_ue = 25 * u.m, 1.5 * u.m
>>> distances = [5, 20, 1000, 20000] * u.m # 2D distances
>>> # Note: too small or large distances will lead to NaN values
>>> PL_los, PL_nlos, los_prob = pathprof.imt_urban_macro_losses(
... freq, distances, h_bs=h_bs, h_ue=h_ue
... )
>>> PL_los # doctest: +FLOAT_CMP
<Decibel [ nan, 60.766, 108.247, nan] dB>
>>> PL_nlos # doctest: +FLOAT_CMP
<Decibel [ nan, 71.745, 130.785, nan] dB>
>>> los_prob # doctest: +FLOAT_CMP
<Quantity [1.000e+00, 9.728e-01, 1.800e-02, 9.000e-04]>
>>> # randomly assign LOS or Non-LOS type to UE (according to above prob.)
>>> with NumpyRNGContext(0):
... los_type = np.random.uniform(0, 1, distances.size) < los_prob
>>> # note: los_type == True : LOS
>>> # los_type == False: Non-LOS
>>> los_type
array([ True, True, False, False])
>>> PL = np.where(
... los_type, PL_los.to_value(cnv.dB), PL_nlos.to_value(cnv.dB)
... ) * cnv.dB
>>> PL # doctest: +FLOAT_CMP
<Decibel [ nan, 60.766, 130.785, nan] dB>
.. testcleanup::
>>> np.set_printoptions()
'''
# for h_ue > 13 m, need to implement "C" function; then "h_e" is needed!
# h_e : `~astropy.units.Quantity`, optional
# Effective environment height [m] (Default: 1 m)
# Important: for `h_ue > 13 m`, this is subject to random sampling;
# see `3GPP TR 38.901 Table 7.4.1-1 Note 1
# <https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
h_e = 1.
pl_los, pl_nlos = cyimt.urban_macro_losses_cython(
freq, dist_2d, h_bs, h_ue, h_e,
)
# probability to have a LOS path;
# see 3GPP TR 38.901, Table 7.4.2
_x = 18 / dist_2d
los_prob = (
# for h_e > 13 m, a correction factor would be necessary!!!
_x + np.exp(-18 / 63 / _x) * (1 - _x)
)
los_prob[dist_2d < 18] = 1.
los_prob = np.broadcast_to(los_prob, pl_los.shape)
return pl_los, pl_nlos, los_prob
[docs]
@utils.ranged_quantity_input(
freq=(0.5, 100, apu.GHz),
dist_2d=(0, 100000, apu.m),
h_bs=(10, 150, apu.m), # according to 3GPP TR 38.901 only 10 m allowed!?
h_ue=(1, 22.5, apu.m),
strip_input_units=True, allow_none=False,
output_unit=(cnv.dB, cnv.dB, cnv.dimless)
)
def imt_urban_micro_losses(
freq,
dist_2d,
h_bs=10 * apu.m, h_ue=1.5 * apu.m,
):
'''
Calculate los/non-los propagation losses Rural-Macro IMT scenario.
The computation is in accordance with `3GPP TR 38.901 Table 7.4.1-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Parameters
----------
freq : `~astropy.units.Quantity`
Frequency of radiation [GHz]
dist_2d : `~astropy.units.Quantity`
Distance on the ground between BS and UE device [m]
Note: Well-defined only for distances between 10 m and 5 km.
h_bs : `~astropy.units.Quantity`, optional
Basestation height [m] (Default: 35 m)
h_ue : `~astropy.units.Quantity`, optional
User equipment height [m] (Default: 1.5 m)
Returns
-------
PL_los : `~astropy.units.Quantity`
Path loss for line-of-sight cases [dB]
PL_nlos : `~astropy.units.Quantity`
Path loss for non-line-of-sight cases [dB]
los_prob : `~astropy.units.Quantity`
Probability for a path to be line-of-sight. [dimless]
(see Notes and Examples)
Notes
-----
- In statistical simulations, the LoS and Non-LoS cases occur with
certain probabilities. For sampling of path losses the return
parameter `los_prob` can be used, which accounts for the likelihoods
according to `3GPP TR 38.901 Table 7.4.2-1
<https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=3173>`_
Examples
--------
.. testsetup::
>>> import numpy as np
>>> np.set_printoptions(3)
A typical usage, which also accounts for the line-of-sight probabilities,
would be::
>>> import numpy as np
>>> from pycraf import conversions as cnv
>>> from pycraf import pathprof
>>> from astropy import units as u
>>> from astropy.utils.misc import NumpyRNGContext
>>> freq = 1 * u.GHz
>>> h_bs, h_ue = 10 * u.m, 1.5 * u.m
>>> distances = [5, 20, 1000, 20000] * u.m # 2D distances
>>> # Note: too small or large distances will lead to NaN values
>>> PL_los, PL_nlos, los_prob = pathprof.imt_urban_micro_losses(
... freq, distances, h_bs=h_bs, h_ue=h_ue
... )
>>> PL_los # doctest: +FLOAT_CMP
<Decibel [ nan, 60.479, 118.534, nan] dB>
>>> PL_nlos # doctest: +FLOAT_CMP
<Decibel [ nan, 69.599, 128.301, nan] dB>
>>> los_prob # doctest: +FLOAT_CMP
<Quantity [1.000e+00, 9.574e-01, 1.800e-02, 9.000e-04]>
>>> # randomly assign LOS or Non-LOS type to UE (according to above prob.)
>>> with NumpyRNGContext(0):
... los_type = np.random.uniform(0, 1, distances.size) < los_prob
>>> # note: los_type == True : LOS
>>> # los_type == False: Non-LOS
>>> los_type
array([ True, True, False, False])
>>> PL = np.where(
... los_type, PL_los.to_value(cnv.dB), PL_nlos.to_value(cnv.dB)
... ) * cnv.dB
>>> PL # doctest: +FLOAT_CMP
<Decibel [ nan, 60.479, 128.301, nan] dB>
.. testcleanup::
>>> np.set_printoptions()
'''
pl_los, pl_nlos = cyimt.urban_micro_losses_cython(
freq, dist_2d, h_bs, h_ue,
)
# probability to have a LOS path;
# see 3GPP TR 38.901, Table 7.4.2
_x = 18 / dist_2d
los_prob = (
_x + np.exp(-0.5 / _x) * (1 - _x)
)
los_prob[dist_2d < 18] = 1.
los_prob = np.broadcast_to(los_prob, pl_los.shape)
return pl_los, pl_nlos, los_prob
[docs]
@utils.ranged_quantity_input(
P_cmax=(-20, 50, cnv.dBm), # range pretty large, but what is realistic?
P_0_pusch=(-200, 100, cnv.dBm), # dito
alpha=(0, 1, cnv.dimless),
PL=(-100, 300, cnv.dB),
strip_input_units=True, allow_none=False,
output_unit=(cnv.dBm)
)
def P_pusch(P_cmax, M_pusch, P_0_pusch, alpha, PL):
'''
Calculate power output level after UE power control.
See `ITU-R Rec. M.2101-0 <https://www.itu.int/rec/R-REC-M.2101/en>`_
Section 4.1.
Parameters
----------
P_cmax : `~astropy.units.Quantity`
Maximum transmit power [dBm]
M_pusch : `numpy.ndarray`, int
Number of allocated resource blocks (RBs)
Is this the bandwidth per carrier divided by the RB bandwidth (
typically 180 kHz) and number of UE devices associated to each
carrier?
P_0_pusch : `numpy.ndarray`
Initial receive target UE power level per RB [dBm]
alpha : `~astropy.units.Quantity`
Balancing factor for UEs with bad channel and UEs with good channel
PL : `~astropy.units.Quantity`
Path loss between UE and its associated BS [dB]
One should use one of the functions
`~pathprof.imt_rural_macro_losses`,
`~pathprof.imt_urban_macro_losses`, or
`~pathprof.imt_urban_micro_losses` to determine PL for the required
scenario.
Note: Antenna gains should be included, so this is rather the
coupling loss than the path loss, unlike what is stated in
`ITU-R Rec. M.2101-0 <https://www.itu.int/rec/R-REC-M.2101/en>`_.
Returns
-------
P_pusch : `~astropy.units.Quantity`
Transmit power of the terminal [dBm]
Examples
--------
.. testsetup::
>>> import numpy as np
>>> np.set_printoptions(3)
A typical usage would be::
>>> import numpy as np
>>> from pycraf import conversions as cnv
>>> from pycraf import pathprof
>>> from astropy import units as u
>>> from astropy.utils.misc import NumpyRNGContext
>>> freq = 6.65 * u.GHz
>>> h_bs, h_ue = 10 * u.m, 1.5 * u.m
>>> distances = [20, 100, 500, 1000] * u.m # 2D distances
>>> PL_los, PL_nlos, los_prob = pathprof.imt_urban_micro_losses(
... freq, distances, h_bs=h_bs, h_ue=h_ue
... )
>>> # randomly assign LOS or Non-LOS type to UE (according to above prob.)
>>> with NumpyRNGContext(0):
... los_type = np.random.uniform(0, 1, distances.size) < los_prob
>>> # note: los_type == True : LOS
>>> # los_type == False: Non-LOS
>>> PL = np.where(
... los_type, PL_los.to_value(cnv.dB), PL_nlos.to_value(cnv.dB)
... ) * cnv.dB
>>> PL # doctest: +FLOAT_CMP
<Decibel [ 76.935, 110.581, 135.202, 145.827] dB>
>>> # Assume some antenna gains:
>>> G_bs, G_ue = 20 * cnv.dBi, 5 * cnv.dBi
>>> CL = (
... PL.to_value(cnv.dB) -
... G_bs.to_value(cnv.dBi) -
... G_ue.to_value(cnv.dBi)
... ) * cnv.dB
>>> bw_carrier = 100 * u.MHz
>>> bw_rb = 180 * u.kHz # resource block bandwidth
>>> num_ue = 3 # 3 UEs per BS sector and carrier
>>> M_pusch = int(bw_carrier / bw_rb / num_ue)
>>> M_pusch
185
>>> P_cmax = 23 * cnv.dBm
>>> P_0_pusch = -95.5 * cnv.dBm
>>> alpha = 0.8 * cnv.dimless
>>> P_pusch = pathprof.P_pusch(
... P_cmax, M_pusch, P_0_pusch, alpha, CL
... )
>>> P_pusch.to(cnv.dBm) # doctest: +FLOAT_CMP
<Decibel [-31.28 , -4.363, 15.333, 23. ] dB(mW)>
.. testcleanup::
>>> np.set_printoptions()
'''
P_pusch = 10 * np.log10(M_pusch) + P_0_pusch + alpha * PL
P_pusch[P_pusch > P_cmax] = P_cmax
return P_pusch
if __name__ == '__main__':
print('This not a standalone python program! Use as module.')