Source code for pyphysim.reference_signals.root_sequence

#!/usr/bin/env python
"""Module with Sounding Reference Signal (SRS) related functions"""

from typing import List, Optional, Union, cast

import numpy as np

from .zadoffchu import calcBaseZC, get_extended_ZF

# Type representing something that can be used to index a numpy array
Indexes = Union[np.ndarray, List[int], slice]

__all__ = ['RootSequence']

# List of prime numbers lower than 282.
_SMALL_PRIME_LIST = np.array([
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
    73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
    157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233,
    239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317,
    331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
    421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503,
    509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607,
    613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701,
    709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811,
    821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911,
    919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009
])

# Table with root sequence for sequence size equal to 12 (number of
# subcarriers in one PRB)
ROOT_TABLE1 = {
    '0': np.array([-1, 1, 3, -3, 3, 3, 1, 1, 3, 1, -3, 3]),
    '1': np.array([1, 1, 3, 3, 3, -1, 1, -3, -3, 1, -3, 3]),
    '2': np.array([1, 1, -3, -3, -3, -1, -3, -3, 1, -3, 1, -1]),
    '3': np.array([-1, 1, 1, 1, 1, -1, -3, -3, 1, -3, 3, -1]),
    '4': np.array([-1, 3, 1, -1, 1, -1, -3, -1, 1, -1, 1, 3]),
    '5': np.array([1, -3, 3, -1, -1, 1, 1, -1, -1, 3, -3, 1]),
    '6': np.array([-1, 3, -3, -3, -3, 3, 1, -1, 3, 3, -3, 1]),
    '7': np.array([-3, -1, -1, -1, 1, -3, 3, -1, 1, -3, 3, 1]),
    '8': np.array([1, -3, 3, 1, -1, -1, -1, 1, 1, 3, -1, 1]),
    '9': np.array([1, -3, -1, 3, 3, -1, -3, 1, 1, 1, 1, 1]),
    '10': np.array([-1, 3, -1, 1, 1, -3, -3, -1, -3, -3, 3, -1]),
    '11': np.array([3, 1, -1, -1, 3, 3, -3, 1, 3, 1, 3, 3]),
    '12': np.array([1, -3, 1, 1, -3, 1, 1, 1, -3, -3, -3, 1]),
    '13': np.array([3, 3, -3, 3, -3, 1, 1, 3, -1, -3, 3, 3]),
    '14': np.array([-3, 1, -1, -3, -1, 3, 1, 3, 3, 3, -1, 1]),
    '15': np.array([3, -1, 1, -3, -1, -1, 1, 1, 3, 1, -1, -3]),
    '16': np.array([1, 3, 1, -1, 1, 3, 3, 3, -1, -1, 3, -1]),
    '17': np.array([-3, 1, 1, 3, -3, 3, -3, -3, 3, 1, 3, -1]),
    '18': np.array([-3, 3, 1, 1, -3, 1, -3, -3, -1, -1, 1, -3]),
    '19': np.array([-1, 3, 1, 3, 1, -1, -1, 3, -3, -1, -3, -1]),
    '20': np.array([-1, -3, 1, 1, 1, 1, 3, 1, -1, 1, -3, -1]),
    '21': np.array([-1, 3, -1, 1, -3, -3, -3, -3, -3, 1, -1, -3]),
    '22': np.array([1, 1, -3, -3, -3, -3, -1, 3, -3, 1, -3, 3]),
    '23': np.array([1, 1, -1, -3, -1, -3, 1, -1, 1, 3, -1, 1]),
    '24': np.array([1, 1, 3, 1, 3, 3, -1, 1, -1, -3, -3, 1]),
    '25': np.array([1, -3, 3, 3, 1, 3, 3, 1, -3, -1, -1, 3]),
    '26': np.array([1, 3, -3, -3, 3, -3, 1, -1, -1, 3, -1, -3]),
    '27': np.array([-3, -1, -3, -1, -3, 3, 1, -1, 1, 3, -3, -3]),
    '28': np.array([-1, 3, -3, 3, -1, 3, 3, -3, 3, 3, -1, -1]),
    '29': np.array([3, -3, -3, -1, -1, -3, -1, 3, -3, 3, 1, -1])
}

# Table with root sequence for sequence size equal to 24 (number of
# subcarriers in two PRBs)
ROOT_TABLE2 = {
    '0':
    np.array([
        -1, 3, 1, -3, 3, -1, 1, 3, -3, 3, 1, 3, -3, 3, 1, 1, -1, 1, 3, -3, 3,
        -3, -1, -3
    ]),
    '1':
    np.array([
        -3, 3, -3, -3, -3, 1, -3, -3, 3, -1, 1, 1, 1, 3, 1, -1, 3, -3, -3, 1,
        3, 1, 1, -3
    ]),
    '2':
    np.array([
        3, -1, 3, 3, 1, 1, -3, 3, 3, 3, 3, 1, -1, 3, -1, 1, 1, -1, -3, -1, -1,
        1, 3, 3
    ]),
    '3':
    np.array([
        -1, -3, 1, 1, 3, -3, 1, 1, -3, -1, -1, 1, 3, 1, 3, 1, -1, 3, 1, 1, -3,
        -1, -3, -1
    ]),
    '4':
    np.array([
        -1, -1, -1, -3, -3, -1, 1, 1, 3, 3, -1, 3, -1, 1, -1, -3, 1, -1, -3,
        -3, 1, -3, -1, -1
    ]),
    '5':
    np.array([
        -3, 1, 1, 3, -1, 1, 3, 1, -3, 1, -3, 1, 1, -1, -1, 3, -1, -3, 3, -3,
        -3, -3, 1, 1
    ]),
    '6':
    np.array([
        1, 1, -1, -1, 3, -3, -3, 3, -3, 1, -1, -1, 1, -1, 1, 1, -1, -3, -1, 1,
        -1, 3, -1, -3
    ]),
    '7':
    np.array([
        -3, 3, 3, -1, -1, -3, -1, 3, 1, 3, 1, 3, 1, 1, -1, 3, 1, -1, 1, 3, -3,
        -1, -1, 1
    ]),
    '8':
    np.array([
        -3, 1, 3, -3, 1, -1, -3, 3, -3, 3, -1, -1, -1, -1, 1, -3, -3, -3, 1,
        -3, -3, -3, 1, -3
    ]),
    '9':
    np.array([
        1, 1, -3, 3, 3, -1, -3, -1, 3, -3, 3, 3, 3, -1, 1, 1, -3, 1, -1, 1, 1,
        -3, 1, 1
    ]),
    '10':
    np.array([
        -1, 1, -3, -3, 3, -1, 3, -1, -1, -3, -3, -3, -1, -3, -3, 1, -1, 1, 3,
        3, -1, 1, -1, 3
    ]),
    '11':
    np.array([
        1, 3, 3, -3, -3, 1, 3, 1, -1, -3, -3, -3, 3, 3, -3, 3, 3, -1, -3, 3,
        -1, 1, -3, 1
    ]),
    '12':
    np.array([
        1, 3, 3, 1, 1, 1, -1, -1, 1, -3, 3, -1, 1, 1, -3, 3, 3, -1, -3, 3, -3,
        -1, -3, -1
    ]),
    '13':
    np.array([
        3, -1, -1, -1, -1, -3, -1, 3, 3, 1, -1, 1, 3, 3, 3, -1, 1, 1, -3, 1, 3,
        -1, -3, 3
    ]),
    '14':
    np.array([
        -3, -3, 3, 1, 3, 1, -3, 3, 1, 3, 1, 1, 3, 3, -1, -1, -3, 1, -3, -1, 3,
        1, 1, 3
    ]),
    '15':
    np.array([
        -1, -1, 1, -3, 1, 3, -3, 1, -1, -3, -1, 3, 1, 3, 1, -1, -3, -3, -1, -1,
        -3, -3, -3, -1
    ]),
    '16':
    np.array([
        -1, -3, 3, -1, -1, -1, -1, 1, 1, -3, 3, 1, 3, 3, 1, -1, 1, -3, 1, -3,
        1, 1, -3, -1
    ]),
    '17':
    np.array([
        1, 3, -1, 3, 3, -1, -3, 1, -1, -3, 3, 3, 3, -1, 1, 1, 3, -1, -3, -1, 3,
        -1, -1, -1
    ]),
    '18':
    np.array([
        1, 1, 1, 1, 1, -1, 3, -1, -3, 1, 1, 3, -3, 1, -3, -1, 1, 1, -3, -3, 3,
        1, 1, -3
    ]),
    '19':
    np.array([
        1, 3, 3, 1, -1, -3, 3, -1, 3, 3, 3, -3, 1, -1, 1, -1, -3, -1, 1, 3, -1,
        3, -3, -3
    ]),
    '20':
    np.array([
        -1, -3, 3, -3, -3, -3, -1, -1, -3, -1, -3, 3, 1, 3, -3, -1, 3, -1, 1,
        -1, 3, -3, 1, -1
    ]),
    '21':
    np.array([
        -3, -3, 1, 1, -1, 1, -1, 1, -1, 3, 1, -3, -1, 1, -1, 1, -1, -1, 3, 3,
        -3, -1, 1, -3
    ]),
    '22':
    np.array([
        -3, -1, -3, 3, 1, -1, -3, -1, -3, -3, 3, -3, 3, -3, -1, 1, 3, 1, -3, 1,
        3, 3, -1, -3
    ]),
    '23':
    np.array([
        -1, -1, -1, -1, 3, 3, 3, 1, 3, 3, -3, 1, 3, -1, 3, -1, 3, 3, -3, 3, 1,
        -1, 3, 3
    ]),
    '24':
    np.array([
        1, -1, 3, 3, -1, -3, 3, -3, -1, -1, 3, -1, 3, -1, -1, 1, 1, 1, 1, -1,
        -1, -3, -1, 3
    ]),
    '25':
    np.array([
        1, -1, 1, -1, 3, -1, 3, 1, 1, -1, -1, -3, 1, 1, -3, 1, 3, -3, 1, 1, -3,
        -3, -1, -1
    ]),
    '26':
    np.array([
        -3, -1, 1, 3, 1, 1, -3, -1, -1, -3, 3, -3, 3, 1, -3, 3, -3, 1, -1, 1,
        -3, 1, 1, 1
    ]),
    '27':
    np.array([
        -1, -3, 3, 3, 1, 1, 3, -1, -3, -1, -1, -1, 3, 1, -3, -3, -1, 3, -3, -1,
        -3, -1, -3, -1
    ]),
    '28':
    np.array([
        -1, -3, -1, -1, 1, -3, -1, -1, 1, -1, -3, 1, 1, -3, 1, -3, -3, 3, 1, 1,
        -1, 3, -1, -1
    ]),
    '29':
    np.array([
        1, 1, -1, -1, -3, -1, 3, -1, 3, -1, 1, 3, 1, -1, 3, 1, 3, -3, -3, 1,
        -1, -1, 1, 3
    ])
}


[docs]class RootSequence: """ Class representing the root sequence of the reference signals. The root sequence is generated using two possible formulas, one used for a sequence size smaller than :math:`3M_{sc}^{RS}` and another for sequence size equal to or greater than :math:`3M_{sc}^{RS}`, where :math:`3M_{sc}^{RS}` is the number of subcarriers in a PRB (12 subcarriers). Parameters ---------- root_index : int The SRS root sequence index. size : int The size of the extended Zadoff-Chu sequence. If None then the sequence will not be extended and will thus have a size equal to Nzc. Nzc : int The size of the Zadoff-Chu sequence (without any extension). If not provided then the largest prime number lower than or equal to `size` will be used. """ n_sc_PRB = 12 # Number of subcarriers in a PRB in LTE def __init__(self, root_index: int, size: Optional[int] = None, Nzc: Optional[int] = None) -> None: if size is None and Nzc is None: raise AttributeError("Either 'size' or 'Nzc' (or both) must " "be provided.") if size is None: size = Nzc assert (isinstance(size, int)) if Nzc is None: Nzc = self._get_largest_prime_lower_than_number(size) if size < Nzc: raise AttributeError("If 'size' and Nzc are provided, " "then size must be greater than Nzc") self._root_index = root_index self._seq_array: np.ndarray = None self._extended_seq_array: np.ndarray = None # Extended Zadoff-Chu sequence if size > 2 * self.n_sc_PRB: # If size is greater then 2 * n_sc_PRB, the root # sequence is an extended Zadoff-Chu sequence. First let's # compute the Zadoff-Chu sequence. self._seq_array = calcBaseZC(Nzc, root_index) # Zadoff-Chu seq # Now, if size is greater than Nzc, let's compute the cyclic # extension of the root sequence. if size > Nzc: self._extended_seq_array = get_extended_ZF( self._seq_array, size) else: # size must be either n_sc_PRB or 2*n_sc_PRB if size == self.n_sc_PRB: self._seq_array = np.exp(1j * (np.pi / 4.0) * ROOT_TABLE1['{0}'.format(root_index)]) elif size == 2 * self.n_sc_PRB: self._seq_array = np.exp(1j * (np.pi / 4.0) * ROOT_TABLE2['{0}'.format(root_index)]) else: raise AttributeError("Invalid root sequence size")
[docs] @staticmethod def _get_largest_prime_lower_than_number(seq_size: int) -> int: """ Get the largest prime number lower than `seq_size`. Parameters ---------- seq_size : int The sequence size. Returns ------- int The largest prime number lower than `seq_size`. """ p = _SMALL_PRIME_LIST[_SMALL_PRIME_LIST <= seq_size][-1] return int(p)
@property def Nzc(self) -> int: """ Get the size of the Zadoff-Chu sequence (without any extension). Returns ------- int The value of the Nzc property. """ return cast(int, self._seq_array.size) @property def size(self) -> int: """ Return the size (with extension) of the sequence. If the sequence is not extended than `size()` will return the same as `Nzc`. Returns ------- size : int The size of the extended Zadoff-Chu sequence. Examples -------- >>> seq1 = RootSequence(root_index=25, Nzc=139) >>> seq1.size 139 >>> seq1 = RootSequence(root_index=25, Nzc=139, size=150) >>> seq1.size 150 """ if self._extended_seq_array is None: return self.Nzc return cast(int, self._extended_seq_array.size) @property def index(self) -> int: """ Return the SRS root sequence index. Returns ------- int The root sequence index. """ return self._root_index
[docs] def seq_array(self) -> np.ndarray: """ Get the extended Zadoff-Chu root sequence as a numpy array. Returns ------- seq : np.ndarray The extended Zadoff-Chu sequence """ if self._extended_seq_array is None: return self._seq_array return self._extended_seq_array
# xxxxxxxxxx Define some basic methods xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # We can always just get the equivalent numpy array and perform the # operations on it, but having these operations defined here is # convenient # TODO: Make these operation methods (add, mul, etc) also work with # RootSequence objects returning a new RootSequence object. Change the # docstring type information when you do that. def __add__(self, other: np.ndarray) -> np.ndarray: # pragma: no cover """ Perform addition with `other`. Parameters ---------- other : np.ndarray Returns ------- np.ndrray """ return self.seq_array() + other def __radd__(self, other: np.ndarray) -> np.ndarray: # pragma: no cover """ Perform addition with `other`. Parameters ---------- other : np.ndarray Returns ------- np.ndrray """ return self.seq_array() + other def __mul__(self, other: np.ndarray) -> np.ndarray: # pragma: no cover """ Perform multiplication with `other`. Parameters ---------- other : np.ndarray Returns ------- np.ndrray """ return self.seq_array() * other def __rmul__(self, other: np.ndarray) -> np.ndarray: # pragma: no cover """ Perform multiplication with `other`. Parameters ---------- other : np.ndarray Returns ------- np.ndrray """ return self.seq_array() * other def __getitem__(self, val: Indexes) -> np.ndarray: """ Index the sequence. This will simply return the same indexing of the underlying numpy array. Parameters ---------- val : Indexes Anything accepted as indexing by numpy arrays. Returns ------- np.ndarray """ return self.seq_array()[val]
[docs] def conjugate(self) -> np.ndarray: # pragma: no cover """ Return the conjugate of the root sequence as a numpy array. Returns ------- np.ndarray The conjugate of the root sequence. """ return self.seq_array().conj()
[docs] def conj(self) -> np.ndarray: # pragma: no cover """ Return the conjugate of the root sequence as a numpy array. Returns ------- np.ndarray The conjugate of the root sequence. """ return self.seq_array().conj()
def __repr__(self) -> str: # pragma: no cover """ Get the representation of the object. Returns ------- str The representation of the object. """ if self._extended_seq_array is None: return ("<SrsRootSequence(" "root_index={0},Nzc={1})>").format(self._root_index, self._seq_array.size) return ("<SrsRootSequence(" "root_index={0},size={2},Nzc={1})>").format( self._root_index, self._seq_array.size, self._extended_seq_array.size)
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx