Processors#

class Upsampler(L: int, phase: int = 0, scale: float = 1.0, is_mimo: bool = True, axis: int = -1, use_filter: bool = False, name: str = 'upsampler')#

Upsampler class for increasing the sampling rate of a signal along a specified axis.

This class increases the sample rate of the incoming signal by inserting \(L – 1\) zeros between samples along the specified axis.

Signal Model#

\[\begin{split}y[n] = \begin{cases} \alpha x[(n-\tau)/ L] & \text{if } (n-\tau)\% L = 0 \\\\ 0 & \text{otherwise} \end{cases}\end{split}\]

Attributes#

Lint

The upsampling factor.

phaseint, optional

The number of samples \(\tau \in \mathbb{N}\) by which to offset the upsampled sequence (default: 0).

scalefloat, optional

The scaling factor \(\alpha\) applied to the upsampled signal (default: 1.0).

axisint, optional

The axis along which to perform the upsampling operation (default: -1).

use_filter: bool, optional

Apply a lowpass filter at the output (default: false)

namestr, optional

The name of the processor (default: “upsampler”).

Example 1#

>>> import numpy as np
>>> X = np.array([1, 2, 3])
>>> upsampler = Upsampler(L=2)
>>> Y = upsampler(X)
>>> print(Y)
[1. 0. 2. 0. 3. 0.]

Example 2#

>>> X = np.array([1, 2])
>>> upsampler = Upsampler(L=3, phase=1)
>>> Y = upsampler(X)
>>> print(Y)
[0. 1. 0. 0. 2. 0.]

Example 3#

>>> X = np.array([[1, 2], [3, 4]])
>>> print(X[0, :])
[1 2]
>>> upsampler = Upsampler(L=2, axis=-1)
>>> Y = upsampler(X)
>>> print(Y)
[[1. 0. 2. 0.]
[3. 0. 4. 0.]]
class Downsampler(L: int, phase: int = 0, scale: float = 1.0, is_mimo: bool = True, axis: int = -1, use_filter: bool = False, name: str = 'downsampler')#

Downsampler class for decreasing the sampling rate of a signal along a specified axis.

This class decreases the sample rate of the input signal by keeping the first sample and then every Lth sample after the first along the specified axis.

Signal Model#

The decimation process can be described mathematically as follows:

\[y[n] = x[n \cdot L]\]

where \(L\) is the downsampling factor, \(x\) is the input signal, and \(y\) is the output signal.

Attributes#

Lint

The downsampling factor, which determines how many samples are skipped between each retained sample.

phaseint, optional

The number of samples by which to offset the downsampled sequence (default: 0).

scalefloat, optional

The scaling factor applied to the downsampled signal (default: 1.0).

axisint, optional

The axis along which to perform the downsampling operation (default: -1).

use_filter: bool, optional

Apply a lowpass filter at the output (default: false)

namestr, optional

The name of the processor (default: “downsampler”).

Examples#

>>> import numpy as np
>>> X = np.array([1, 2, 3, 4, 5, 6])
>>> downsampler = Downsampler(L=2)
>>> Y = downsampler(X)
>>> print(Y)
[1. 3. 5.]
class Serial2Parallel(N_sub: int, order: str = 'F', method: Literal['zero-padding', 'truncate'] = 'zero-padding', name: str = 'S2P')#

A class for converting a serial data stream into parallel data streams.

This class reshapes a multi-dimensional array (serial stream) into another multi-dimensional array (parallel data streams) using a specified number of subcarriers and an ordering scheme. If necessary, the input data is either padded with zeros to fit the specified structure or is truncated.

Attributes#

N_subint

The number of subcarriers, which defines the size of the last dimension in the reshaped array. Must be a positive integer.

orderstr, optional

The order in which to reshape the array. ‘F’ means to reshape in column-major (Fortran-style) order, and ‘C’ means to reshape in row-major (C-style) order. Default is ‘F’.

methodLiteral[“zero-padding”, “truncate”], optional

The method to handle data that does not fit perfectly into the reshaped structure. Options are ‘zero-padding’ to pad with zeros or ‘truncate’ to remove excess data. Default is ‘zero-padding’.

namestr, optional

Name of the instance. Default is “S2P”.

Example 1#

>>> processor = Serial2Parallel(3)
>>> X = np.arange(5)
>>> print(X)
[0 1 2 3 4]
>>> print(X.shape)
(5,)
>>> Y = processor(X)
>>> print(Y.shape)
(3, 2)
>>> print(Y[:, 0])
[0 1 2]
>>> print(Y[:, 1])
[3 4 0]

Example 2#

>>> processor = Serial2Parallel(3)
>>> X = np.arange(10).reshape(2, 5)
>>> print(X)
[[0 1 2 3 4]
[5 6 7 8 9]]
>>> print(X.shape)
(2, 5)
>>> print(X[0, :])
[0 1 2 3 4]
>>> Y = processor(X)
>>> print(Y.shape)
(2, 3, 2)
>>> print(Y[0, :, 0])
[0 1 2]
>>> print(Y[0, :, 1])
[3 4 0]
class Parallel2Serial(order: str = 'F', name: str = 'P2S')#

A class for converting parallel data streams into a serial data stream.

This class reshapes a multi-dimensional array (parallel data streams) into another multi-dimensional array where the last dimension is flattened into a serial data stream, using a specified ordering scheme.

Attributes#

orderstr, optional

The order in which to reshape the array. ‘F’ means to reshape in column-major (Fortran-style) order, and ‘C’ means to reshape in row-major (C-style) order. Default is ‘F’.

namestr, optional

Name of the instance. Default is “P2S”.

Example 1#

>>> processor = Parallel2Serial()
>>> X = np.array([[0, 3], [1, 4], [2, 0]])
>>> print(X.shape)
(3, 2)
>>> Y = processor(X)
>>> print(Y.shape)
(6,)
>>> print(Y)
[0 1 2 3 4 0]

Example 2#

>>> processor = Parallel2Serial()
>>> X = np.array([[[0, 3],[1, 4], [2, 0]], [[5, 8], [6, 9], [7, 0]]])
>>> print(X.shape)
(2, 3, 2)
>>> Y =processor(X)
>>> print(Y.shape)
(2, 6)
>>> print(Y[0, :])
[0 1 2 3 4 0]
class Amplifier(gain: float = 1.0, axis: int | None = None, name: str = 'signal_amplifier')#

A class for amplifying or attenuating a signal along a specified axis.

This class multiplies an input signal by a specified gain factor, effectively amplifying or attenuating the signal based on the gain value. The gain can be applied to all elements or selectively along a specified axis.

Attributes#

gainfloat

The amplification factor by which the signal will be multiplied.

axisint or None, optional

The axis along which to apply the gain. If None, the gain is applied to the entire array. Default is None.

namestr

Name of the signal amplifier instance.

Example 1#

>>> amplifier = Amplifier(gain=2)
>>> X = np.array([[1, 2], [3, 4]])
>>> print(X)
[[1 2]
[3 4]]
>>> Y = amplifier(X)
>>> print(Y)
[[2 4]
[6 8]]

Example 2#

>>> amplifier = Amplifier(gain=3, axis=-1)
>>> X = np.array([[1, 2], [3, 4]])
>>> print(X)
[[1 2]
[3 4]]
>>> Y = amplifier(X)
>>> print(Y)
[[ 1.  6.]
[ 3. 12.]]
class WeightAmplifier(weight: ndarray | None = None, axis: int = -1, name: str = 'parallel_signal_weight')#

Applies weights to a MIMO (Multiple Input Multiple Output) parallel signal along a specified axis.

This class multiplies each parallel stream of the input signal by a corresponding weight along a specified axis. The weights are applied selectively to the elements along the specified axis.

Signal Model#

\[y_l[n] = w_l x_l[n]\]

where the coefficient \(w_l\) specifies the weight.

Attributes#

weightnp.ndarray

Weights to be applied to each parallel stream of the input signal (1D array).

axisint, optional

The axis along which to apply the weights. Default is -1 (the last axis).

namestr

Name of the weight amplifier instance.

Example 1#

>>> weight_amplifier = WeightAmplifier(weight=np.array([2, 3]), axis=0)
>>> X = np.array([[1, 2], [3, 4]])
>>> print(X)
[[1 2]
 [3 4]]
>>> Y = weight_amplifier.forward(X)
>>> print(Y)
[[2 4]
 [9 12]]

Example 2#

>>> weight_amplifier = WeightAmplifier(weight=np.array([2, 3]), axis=-1)
>>> X = np.array([[1, 2], [3, 4]])
>>> print(X)
[[1 2]
 [3 4]]
>>> Y = weight_amplifier(X)
>>> print(Y)
[[2 6]
 [6 12]]
class Complex2Real(part: Literal['real', 'imag'] = 'real', validate_input: bool = False)#

A processor class to extract the real or imaginary part of a complex array.

Attributes#

partLiteral[“real”, “imag”]

Specifies which part of the complex number to extract. Can be either “real” or “imag”. Default is “real”.

validate_inputbool

If True, validates that the input array is purely real or imaginary based on the specified part. Default is False.

Example 1#

>>> processor = Complex2Real(part="real")
>>> X = np.array([1+2j, 3+4j, 5+0j])
>>> processor(X)
array([1., 3., 5.])

Example 2#

>>> processor_imag = Complex2Real(part="imag")
>>> X = np.array([1+2j, 3+4j, 5+0j])
>>> processor_imag(X)
array([2., 4., 0.])

Example 3#

>>> processor_real2 = Complex2Real(part="imag", validate_input=True)
>>> X = np.array([1+2j, 3+4j, 5+0j])
>>> processor(X)  # Raises ValueError
ValueError: The input data is not real since the imaginary part is non-zero.
class AutoConcatenator(input_copy_mask: ndarray | None = None, output_original_mask: ndarray | None = None, output_copy_mask: ndarray | None = None, axis: int = 0, name: str = 'auto concatenator')#

A class to automatically concatenate data along a specified axis using masks.

This class facilitates the extraction and concatenation of data from an input array based on predefined masks. It ensures that the output array is constructed correctly by validating the shapes and contents of the masks.

Attributes#

input_copy_maskOptional[np.ndarray]

A boolean mask used to extract a portion of the input data to be copied.

output_original_maskOptional[np.ndarray]

A boolean mask indicating where the original data should be placed in the output array.

output_copy_maskOptional[np.ndarray]

A boolean mask indicating where the copied data should be placed in the output array.

axisint

The axis along which to perform the concatenation. Default is 0.

namestr

The name of the processor. Default is “auto concatenator”.

Example 1#

>>> input_copy_mask = np.array([True, False, True])
>>> output_original_mask = np.array([True, True, True, False, False])
>>> output_copy_mask = np.array([False, False, False, True, True])
>>> X = np.array([1, 2, 3])
[1 2 3]
>>> concatenator = AutoConcatenator(input_copy_mask, output_original_mask, output_copy_mask)
>>> Y = concatenator(X)
[1 2 3 1 3]

Example 2#

>>> input_copy_mask = np.array([True, False])
>>> output_original_mask = np.array([True, True, False, False, False])
>>> output_copy_mask = np.array([False, False, False, True, False])
>>> X = np.array([[1, 2, 3], [4, 5, 6]])
[[1 2 3]
[4 5 6]]
>>> concatenator = AutoConcatenator(input_copy_mask, output_original_mask, output_copy_mask)
>>> Y = concatenator(X)
[[1 2 3]
[4 5 6]
[0 0 0]
[1 2 3]
[0 0 0]]

Example 3#

>>> input_copy_mask = np.array([False, True, True])
>>> output_original_mask = np.array([False, True, True, True, False])
>>> output_copy_mask = np.array([True, False, False, False, True])
>>> X = np.array([[1, 2, 3], [4, 5, 6]])
[[1 2 3]
[4 5 6]]
>>> concatenator = AutoConcatenator(input_copy_mask, output_original_mask, output_copy_mask, axis=-1)
>>> result = concatenator(X)
[[2 1 2 3 3]
[5 4 5 6 6]]
extract_copy(X: ndarray)#

Extract a copy from the input signal on the specified axis

class SampleRemover(N_start: int = 0, length: int = 0, name: str = 'symbol remover')#

Deletes samples from a signal.

This class removes a specified number of samples starting from a given index in the signal.

Attributes#

N_startint

Index of the first sample to delete.

lengthint

Number of samples to delete.

namestr

Name of the symbol remover instance. Default is “SymbolRemover”.

class Delay_Remover(delay: int, is_mimo: bool = True, axis: int = -1, name: str = 'delay remover')#

Removes an initial delay from a signal.

This class removes a specified number of initial samples (delay) from the signal.

Attributes#

delayint

Number of initial samples to remove.

namestr

Name of the delay remover instance. Default is “DelayRemover”.

class DataAdder(symbol: ndarray, N_start: int = 0, name: str = 'Data Adder')#

Inserts symbol samples into a signal.

This class inserts a specified symbol into a signal at a given index.

Attributes#

symbolnp.ndarray

Symbol to be inserted into the signal.

N_startint

Index at which to insert the symbol.

namestr

Name of the data adder instance. Default is “DataAdder”.

class Signal_Extractor(N_start: int = 0, N: int | None = None, name: str = 'Signal Extractor')#

A class for extracting a segment from a signal.

This class is used to extract a specific portion of a signal, starting from a specified index. It can extract either until the end of the signal or for a specified number of samples.

Attributes#

N_startint

The starting index from where the extraction begins.

NOptional[int]

The number of samples to extract. If None, extracts until the end of the signal.

namestr

Name of the extractor instance. Default is “DataExtractor”.

class Resampler(up: int, down: int, name: str = 'Resampler')#

A class for resampling a signal.

This class changes the sampling rate of a signal by a rational factor. It performs upsampling by the specified ‘up’ factor, followed by downsampling by the specified ‘down’ factor, effectively changing the sampling rate by a factor of up/down.

Attributes#

upint

The upsampling factor. Must be a positive integer.

downint

The downsampling factor. Must be a positive integer.

namestr

Name of the resampler instance. Default is “Resampler”.

class Clipper(threshold: float, name: str = 'Clipper')#

Clipper class for clipping signal values to a specified threshold.

Signal Model#

\[y[n] = \frac{x[n]}{|x[n]|} \cdot \min{(|x[n]|, \tau)}\]

Attributes#

thresholdfloat

The threshold value \(\tau\) for clipping.

namestr

Name of the clipper instance.

class Blind_Phase_Tracker#

A class implementing a blind phase tracking algorithm using a grid search approach.

This algorithm estimates and compensates for unknown phase rotations in a received signal by minimizing the local Error Vector Magnitude (EVM) around each sample. It assumes the signal belongs to a known modulation alphabet (e.g., QAM or PSK).

Parameters#

Lint

The number of neighboring symbols on each side used to compute the local EVM cost.

alphabetnp.ndarray

The set of complex symbols representing the modulation constellation.

phase_stepsint, optional

The number of discrete phase candidates evaluated within the range [-π/4, π/4). Default is 10.

Methods#

hard_projector(z)

Projects input samples to the nearest symbols from the constellation.

evm_cost(x, n, phi)

Computes the local EVM cost at index n for a candidate phase shift phi.

forward(x)

Applies blind phase correction to the input signal x and plots the estimated phase evolution over time.