Resource Grid

The module grid.py implements the Grid class which encapsulates the functionality of a Resource Grid including:

  • Keeping the Resource Element (RE) values for a specified resource grid size.

  • Providing easy access to a specific type of data in the resource grid (e.g. DMRS values, CSI-RS values, PDSCH data, etc.)

  • Providing statistics and visualizing the resource grid map.

  • Applying the Precoding process which results in a new Precoded Grid object.

  • Applying OFDM modulation to the resource grid which results in a Waveform object.

  • Applying a Channel Model to the resource grid in frequency domain.

  • Applying Additive white Gaussian Noise (AWGN) to the resource grid in frequency domain.

  • Performing Synchronization based on the correlation between the configured reference signals and a received Waveform.

  • Performing Channel Estimation based on a received resource grid and the configured reference signals.

  • Performing Equalization using the estimated channel.

class neoradium.grid.Grid(bwp, numPlanes=1, contents='DATA', useReDesc=False, numSlots=1)

This class implements the functionality of a resource grid. It keeps the complex frequency-domain values of resource elements (REs) in a resource grid.

Parameters:
  • bwp (BandwidthPart) – The bandwidth part object based on which this resource grid is created.

  • numPlanes (int (default: 1)) – A resource grid can be considered as a 3-dimensional P x L x K complex tensor where L is the number of OFDM symbols, K is the number of subcarriers (based on bwp), and P is the number of planes. In different contexts, P can be equivalent to the number of layers, number of transmitter antenna, or number of receiver antenna. To avoid any confusion, the resource grid implementation in NeoRadium uses the term “Plane” for the first dimension of the resource gird.

  • contents (str (default: "DATA")) –

    The default content type of this resource grid. Each resource element (RE) in the resource grid has a an associated content type. When some data is assigned to some REs in this resource grid without a specified content type, this default value is used. The following content types are currently defined:

    DATA:

    A Generic content type used when the type of data in the resource grid is unknown or not specified.

    PDSCH:

    The content type used for the data carried in a Physical Downlink Shared Channel (PDSCH)

    PDCCH:

    The content type used for the data carried in a Physical Downlink Control Channel (PDCCH)

    PUSCH:

    The content type used for the data carried in a Physical Uplink Shared Channel (PUSCH)

    PUCCH:

    The content type used for the data carried in a Physical Uplink Control Channel (PUCCH)

    RX_DATA:

    The content type used for the received resource grid. (Created by the OFDM demodulation process)

  • useReDesc (Boolean (default: False)) – If True, the resource grid will also include additional fields that describe the content of each resource element (RE). This can be used during the debugging to make sure the resources are allocated correctly.

  • numSlots (int (default: 1)) – The number of time slots to include in the resource grid. The number of time symbols L (the second dimension of the resource grid tensor) is equal to numSlots*bwp.symbolsPerSlot.

Other Read-Only Properties:

Here is a list of additional properties:

shape:

Returns the shape of the 3-dimensional resource grid tensor.

numPlanes:

The number of antenna ports. (The same as numPlanes)

numPorts:

The number of antenna ports. (The same as numPlanes)

numLayers:

The number of layers. (The same as numPlanes)

numSubcarriers:

The number of subcarriers in this resource grid.

numRBs:

The number of resource blocks (RBs) in this resource grid. This is equal to numSubcarriers/12. The number of subcarriers in a resource grid is always a multiple of 12.

numSymbols:

The number of time symbols in this resource grid. This is equal to numSlots*bwp.symbolsPerSlot.

size:

The size of resource grid tensor.

noiseVar:

The variance of the AWGN noise present in this resource grid. This is usually initialized to zero. When an AWGN noise is applied to the grid using the addNoise() function, the variance of the noise stored in this property. Also, if a noisy Waveform is OFDM-demodulated using the ofdmDemodulate() method, then the amount of noise is transferred to the new Grid object created.

Additionally you can access (Read-Only) these BandwidthPart class properties directly: startRb, numRbs, nFFT, symbolsPerSlot, slotsPerSubFrame, slotsPerFrame, symbolsPerSubFrame.

Resource Grid Indexing:

a) Reading: You can directly access the contents of the resource grid using indexes. Here are a few examples of accessing the RE values in the resource grid:

myREs = myGrid[0,2:5,:]     # instead of using myGrid.grid[0,2:5,:]
print(myREs.shape)          # Assuming 612 subcarriers, this will print: "(3, 612)"

indexes = myGrid.getReIndexes("DMRS")   # Get the indexes of all DMRS values
dmrsValues = myGrid[indexes]            # Get all DMRS values as a 1-D array.

b) Writing: You can assign different values to different REs in the resource grid. Here are a few example:

# Set the RE at layer 1, symbol 2, and subcarrier 3 to the value
# 0.707 - 0.707j and RE type "DMRS".
myGrid[1,2,3] = (0.707 - 0.707j, "DMRS")

# Mark all REs in the time symbol 5 as "Reserved" for layer 1. The
# RE values are set to 0 in this case.
myGrid[1,5,:] = "RESERVED"

# Update the 3 RE values at layer 0, subcarrier 5, and symbols [1, 4, 7]
# and mark their RE content type to the grid's default content type.
myGrid[0,1:10:3,5] = [-0.948 - 0.948j, -0.316+0.316j, 0.316-0.948j]
print(indent=0, title=None, getStr=False)

Prints the properties of this resource grid object.

Parameters:
  • indent (int (default: 0)) – The number of indentation characters.

  • title (str or None (default: None)) – If specified, it is used as a title for the printed information.

  • getStr (Boolean (default: False)) – If True, it returns the information in a text string instead of printing the information.

Returns:

If the getStr parameter is True, then this function returns the information in a text string. Otherwise, nothing is returned.

Return type:

None or str

getStats()

Returns some statistics about the allocation of resources in the resource grid.

Returns:

A dictionary of items containing the number of resource elements allocated for different types of data in this resource grid.

Return type:

dict

reTypeAt(p, l, k)

Returns the content type (as a string) of the resource element at the position specified by p, l, and k.

Parameters:
  • p (int) – The plane number. It can be the layer or antenna index depending on the context.

  • l (int) – The time symbol index.

  • k (int) – The subcarrier index.

Returns:

The content type of the resource element specified by p, l, and k.

Return type:

str

getReIndexes(reTypeStr=None)

Returns the indexes of all resource elements in the resource grid with the content type specified by the reTypeStr. For example, the following code first gets the indexes of all DMRS values in the resource grid and uses the returned indexes to retrieve the DMRS values.

dmrsIdx = myGrid.getReIndexes("DMRS")   # Get the indexes of all DMRS values
dmrsValues = myGrid[indexes]            # Get all DMRS values as a 1-D array.
Parameters:

reTypeStr (str or None (default: None)) –

If reTypeStr is None, the default content type of this resource grid is used as the key. For example if this resource grid was created with contents="PDSCH", then the indexes of all resource elements with content type “PDSCH” are returned.

Otherwise, this function returns the indexes of all resource elements in the resource grid with the content type specified by reTypeStr. Here is a list of values that can be used:

”UNASSIGNED”:

The un-assigned resource elements.

”RESERVED”:

The reserved resource elements. This includes the REs reserved by reservedRbSets or reservedReMap parameters of this PDSCH.

”NO_DATA”:

The resource elements that should not contain any data. For example when the corresponding REs in a different layer is used for transmission of data for a differnt UE. (See otherCdmGroups parameter of DMRS class)

”DMRS”:

The resource elements used for DMRS.

”PTRS”:

The resource elements used for PTRS.

”CSIRS_NZP”:

The resource elements used for Zero-Power (ZP) CSI-RS (See csirs).

”CSIRS_ZP”:

The resource elements used for Non-Zero-Power (NZP) CSI-RS (See csirs).

”PDSCH”:

The resource elements used for user data in a Physical Downlink Shared Channel (PDSCH)

”PDCCH”:

The resource elements used for user data in a Physical Downlink Control Channel (PDCCH)

”PUSCH”:

The resource elements used for user data in a Physical Uplink Shared Channel (PUSCH)

”PUCCH”:

The resource elements used for user data in a Physical Uplink Control Channel (PUCCH)

Returns:

A tuple of three 1-D numpy arrays specifying a list of locations in the resource grid. This value can be used directly to access REs at the specified locations. (See the above example)

Return type:

3-tuple

getReValues(reTypeStr=None)

Returns the values of all resource elements in the resource grid with the content type specified by the reTypeStr. This is a short cut method that allows accessing all the values in one step. For example, the following two methods are equivalent.

dmrsValues1 = myGrid[ myGrid.getReIndexes("DMRS") ] # Get indexes, then access values
dmrsValues2 = myGrid.getReValues("DMRS")            # Using this method
assert np.all(dmrsValues1==dmrsValues2)             # The results are the same
Parameters:

reTypeStr (str or None (default: None)) –

If reTypeStr is None, the default content type of this resource grid is used as the key. For example, if this resource grid was created with contents="PDSCH", then the values of all resource elements with content type “PDSCH” are returned.

Otherwise, this function returns the values of all resource elements in the resource grid with the content type specified by reTypeStr. See getReIndexes() for a list of values that could be used for reTypeStr.

Returns:

A 1-D complex numpy array containing the values for all REs with the content type specified by reTypeStr.

Return type:

1-D numpy array

precode(f)

Applies the specified precoding matrix to this grid object and returns a new precoded grid. This function supports Precoding RB groups (PRGs) which means different precoding matrixes could be applied to different groups of subcarriers in the resource grid. See 3GPP TS 38.214, Section 5.1.2.3 for more details.

Parameters:

f (numpy array or list of tuples) –

This function supports two types of precoding:

Wideband:

f is an Nt x Nl, matrix where Nt is the number of transmitter antenna and Nl is the number of layers which must match the number of layers in the resource grid. In this case the same precoding is applied to all subcarriers of the resource grid.

Using PRGs:

f is a list of tuples of the form (groupRBs, groupF). For each entry in the list, the Nt x Nl precoding matrix groupF is applied to all subcarriers of the resource blocks listed in groupRBs.

Returns:

A new Grid object of shape Nt x L x K where Nt is the number of transmitter antenna, L is the number of OFDM symbols, and K is the number of subcarriers.

Return type:

Grid

ofdmModulate(f0=0, windowing='STD')

Applies OFDM Modulation to the resource grid which results in a Waveform object. This function is based on 3GPP TS 38.211, Section 5.3.1.

Parameters:
  • f0 (float (default: 0)) – The carrier frequency of the generated waveform. If it is 0 (default), then a baseband waveform is generated and the up-conversion process explained in 3GPP TS 38.211, Section 5.4 is not applied.

  • windowing (str (default: "STD")) – A text string indicating what type of windowing should be applied to the waveform after OFDM modulation. The default value "STD" means the windowing should be applied based on 3GPP TS 38.104, Sections B.5.2 and C.5.2. For more information see applyWindowing() method of the Waveform class.

Returns:

A Waveform object containing the OFDM-modulated waveform information.

Return type:

Waveform

estimateTimingOffset(rxWaveform)

Estimates the timing offset of a received waveform. This method first applies OFDM modulation to the resource grid and then calculates the correlation of this waveform with the given rxWaveform. The timing offset is the index of where the correlation is at its maximum. The output of this function can be used by the sync() method of the Waveform class to synchronize a received waveform.

Parameters:

rxWaveform (Waveform) – The Waveform object containing the received waveform.

Returns:

The timing offset in number of time-domain samples. This is the number of samples that should be ignored from the beginning of the rxWaveform.

Return type:

int

equalize(hf, noiseVar=None)

Equalizes a received resource grid using the estimated channel hf. The the estimated channel is assumed to include the effect of precoding matrix, therefore, its shape is L x K x Nr x Nl where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nl is the number of layers. The output of the equalization process is a new Grid object of shape Nl x L x K.

This function also outputs Log-Likelihood Ratios (LLR) scaling factors which are used by the demodulation process when extracting Log-Likelihood Ratios (LLRs) from the equalized resource grid.

This method uses the Minimum Mean Squared Error (MMSE) algorithm for the equalization.

Parameters:
  • hf (4-D complex numpy array) – This is an L x K x Nr x Nl numpy array representing the estimated channel matrix, where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nl is the number of layers.

  • noiseVar (float or None (default: None)) – The variance of the noise applied to the received resource grid. If this is not provided, this method tries to use the noise variance of the resource grid obtained by the OFDM demodulation process for the time-domain case or the variance of the noise applied to the received resource grid by the addNoise() method for the frequency domain case (See the noiseVar property of Grid class).

Returns:

  • eqGrid (Grid) – The equalized grid object of shape Nl x L x K where Nl is the number of layers, L is the number of OFDM symbols, and K is the number of subcarriers.

  • llrScales (3-D numpy array) – The Log-Likelihood Ratios (LLR) scaling factors which are used by demodulating process when extracting Log-Likelihood Ratios (LLRs) from the equalized resource grid. The shape of this array is Nl x L x K which is similar to eqGrid above.

estimateChannelLS(rsInfo, meanCdm=True, polarInt=False, kernel='linear')

Performs channel estimation based on this received grid and the reference signal information in the rsInfo. Here is a list of steps taken by this function to calculated the estimated channel and noise variance:

1) First the channel information is calculated at each pilot location using least squared method based on the following equations:

\[Y_p = h_p \odot P + n_p\]

where \(Y_p\) is a vector of received values at the pilot locations which are the values in this Grid object at the pilot locations indicated in rsInfo, \(h_p\) is the vector of channel values at pilot locations, \(P\) is the vector of pilot values extracted from rsInfo, and \(n_p\) is the noise at pilot locations. The least square estimate of the channel values at pilot locations \(h_p\) is then calculated by:

\[h_p = \frac {Y_p} P \qquad \qquad \qquad \text{(element-wise division)}\]

2) If meanCdm is True, the \(h_p\) values in each CDM group are averaged which results in a new smaller set of \(h_p\) values located at centers of CDM groups.

3) Frequency interpolation along subcarriers is applied to \(h_p\) values at all OFDM symbols containing pilots based on polarInt and kernel values.

4) A raise-cosine low-pass filter is applied to the Channel Impulse Response (CIR) values to get a de-noised version of CIRs. The noise variance is estimated using the difference between the noisy and de-noised versions of the CIRs.

5) Finally another interpolation is applied along OFDM symbols to estimate the channel information for the whole channel matrix.

Parameters:
  • rsInfo (CsiRsConfig or DMRS) –

    This object contain reference signal information for the channel estimation. If it is a CsiRsConfig object, the channel matrix is estimated based on the CSI-RS signals which does not include the precoding effect.

    If this is a DMRS object, the channel matrix is estimated based on the demodulation reference signals which includes the precoding effect.

  • meanCdm (Boolean (default: True)) – If True, the \(h_p\) values at pilot locations for each CDM group are averaged before applying subcarrier interpolation. Otherwise interpolation is applied directly on the \(h_p\) values.

  • polarInt (Boolean (default: False)) –

    If True, the interpolation along the subcarriers is applied in polar coordinates. This means all \(h_p\) values are converted to the polar coordinates and then the type of interpolation specified by kernel is applied to magnitudes and angles of these values. The results are then converted back to the cartesian coordinates. Otherwise (default), the interpolation is applied in the cartesian coordinates.

    Doing polar interpolation provides slightly better results at the cost of longer execution time.

  • kernel (str (default: 'linear')) –

    The type of interpolation used for channel estimation process. The same type of 1-D interpolations are applied along subcarriers and then OFDM symbols. Here is a list of supported values:

    linear:

    A linear interpolation is applied to the values using extrapolation at both ends of the arrays. This uses the function interp1d with kind set to linear.

    nearest:

    A nearest neighbor interpolation is applied to the values using extrapolation at both ends of the arrays. This uses the function interp1d with kind set to nearest.

    quadratic:

    A quadratic interpolation is applied to the values using extrapolation at both ends of the arrays. This uses the function interp1d with kind set to quadratic.

    thin_plate_spline:

    An RBF interpolation is applied with a thin_plate_spline kernel. This uses the RBFInterpolator class.

    multiquadric:

    An RBF interpolation is applied with a multiquadric kernel. This uses the RBFInterpolator class.

Returns:

  • hEst (a 4-D complex numpy array) – If rsInfo is a CsiRsConfig object, an L x K x Nr x Nt complex numpy array is returned where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nt is the number of transmitter antenna.

    If rsInfo is a DMRS object, an L x K x Nr x Nl complex numpy array is returned where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nl is the number of layers.

  • estNoiseVar (float) – The estimated noise variance.

applyChannel(channelMatrix)

Applies a channel to this grid in frequency domain which results in a new received Grid object. This function performs a matrix multiplication where this grid of shape Nt x L x K is multiplied by the channel matrix of shape L x K x Nr x Nt and results in the received grid of shape Nr x L x K, where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nt is the number of transmitter antenna.

This method can be used as a shortcut method to get the received resource grid faster compared to time domain process of doing OFDM modulation, applying the channel, performing synchronization, and doing OFDM demodulation.

Please note that the results are slightly different when a channel is applied in time domain vs frequency domain.

Parameters:

channelMatrix (4-D complex numpy array) – This is an L x K x Nr x Nt numpy array representing the estimated channel matrix, where L is the number of OFDM symbols, K is the number of subcarriers, Nr is the number of receiver antenna, and Nt is the number of transmitter antenna.

Returns:

The received grid of shape Nr x L x K, where Nr is the number of receiver antenna, L is the number of OFDM symbols, and K is the number of subcarriers.

Return type:

Grid

addNoise(**kwargs)

Adds Additive White Gaussian Noise (AWGN) to this resource grid based on the given noise properties. The noisy grid is then returned in a new Grid object. The noiseVar property of the returned grid contains the variance of the noise applied by this function.

Parameters:

kwargs (dict) –

One of the following parameters must be specified. They specify how the noise signal is generated.

noise:

A numpy array with the same shape as this Grid object containing the noise information. If the noise information is provided by noise it is added directly to the grid. In this case all other parameters are ignored.

noiseStd:

The standard deviation of the noise. An AWGN complex noise signal is generated with zero-mean and the specified standard deviation. If noiseStd is specified, noiseVar and snrDb values below are ignored.

noiseVar:

The variance of the noise. An AWGN complex noise signal is generated with zero-mean and the specified variance. If noiseVar is specified, the value of snrDb is ignored.

snrDb:

The signal to noise ratio in dB. First the noise variance is calculated using the given SNR value and then an AWGN complex noise signal is generated with zero-mean and the calculated variance. This function uses the following formula to calculate the noise variance \(\sigma^2_{AWGN}\) from \(snrDb\):

\[\sigma^2_{AWGN} = \frac 1 {N_r.10^{\frac {snrDb} {10}}}\]

where \(N_r\) is the number of receiver antenna.

ranGen:

If provided, it is used as the random generator for the AWGN generation. Otherwise, if this is not specified, NeoRadium’s global random generator is used.

Returns:

A new grid object containing the noisy version of this grid. The noiseVar property of the returned grid contains the variance of the noise applied by this function.

Return type:

Grid

drawMap(ports=[0], reRange=(0, 12), title=None)

Draws a color-coded map of this grid object. Each plain of the grid is drawn separately with subcarriers in horizontal direction and OFDM symbols in vertical direction.

Parameters:
  • ports (list (default: [0])) – Specifies the list of ports (or plains) to draw. Each port is drawn separately. By default this function draws only the first plain of the resource grid.

  • reRange (tuple (default: (0,12))) – Specifies the range of subcarriers (REs) to draw. By default this function only draws the first resource block of the grid (subcarriers 0 to 12). The tuple of (a, b) means the first RE drawn is the one at a and last one is at b-1.

  • title (str or None (default: None)) – If specified, it is used as the title for the drawn grid. Otherwise, this function automatically creates a title based on the given parameters.