给信号叠加相位噪声

本Python版本由Alex Bur-Guy(October 2005)发布的Matlab版本翻译而来。


代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
from scipy import fftpack
import matplotlib.pyplot as plt


def add_phase_noise(Sin, Fs, phase_noise_freq, phase_noise_power, VALIDATION_ON=False):
    """
    Oscillator Phase Noise Model
    
    INPUT:
        :param Sin: input COMPLEX signal
        :param Fs: sampling frequency (in Hz) of Sin
        :param phase_noise_freq: frequencies at which SSB Phase Noise is defined (offset from carrier in Hz)
        :param phase_noise_power: SSB Phase Noise power (in dBc/Hz)
        :param VALIDATION_ON: True - perform validation, False - don't perfrom validation
        
    OUTPUT:
        :return Sout: output COMPLEX phase noised signal
        
    NOTE:
        Input signal should be complex
        
    EXAMPLE:
        Assume SSB Phase Noise is specified as follows:
        -------------------------------------------------------
        |  Offset From Carrier      |        Phase Noise      |
        -------------------------------------------------------
        |        1   kHz            |        -84  dBc/Hz      |
        |        10  kHz            |        -100 dBc/Hz      |
        |        100 kHz            |        -96  dBc/Hz      |
        |        1   MHz            |        -109 dBc/Hz      |
        |        10  MHz            |        -122 dBc/Hz      |
        -------------------------------------------------------
        
        Assume that we have 10000 samples of complex sinusoid of frequency 3 KHz 
        sampled at frequency 40MHz:
        
        Fc = 3e3    # carrier frequency
        Fs = 40e6    # sampling frequency
        t = np.arange(0, 10000)
        S = np.exp(1j * 2 * np.pi * Fc/Fs * t)  # complex sinusoid
        
        Then, to produce phase noised signal S1 from the original signal S run follows:
        
        phase_noise_freq = np.array([1e3, 10e3, 100e3, 1e6, 10e6])  # Offset From Carrier
        phase_noise_power = np.array([-84, -100, -96, -109, -122])    # Phase Noise power
        S1 = add_phase_noise(S, Fs, phase_noise_freq, phase_noise_power)
        
    ----------
    Original Matlab Version 1.0
    Alex Bur-Guy, October 2005
    [email protected]
    
    Revisions:
        Version 1.5 - Comments. Validation.
        Version 1.0 - initial version
        
    NOTES:
    1) The presented model is a simple VCO phase noise model based on the following consideration:
    If the output of an oscillator is given as V(t) = V0 * cos(w0 * t + phi(t)), 
    then phi(t) is defined as the phase noise. In cases of small noise
    sources (a valid assumption in any usable system), a narrowband modulation approximation can
    be used to express the oscillator output as:
    
    V(t) = V0 * cos(w0 * t + phi(t))
         = V0 * [cos(w0 * t) * cos(phi(t)) - sin(w0 * t) * sin(phi(t))]
         ~ V0 * [cos(w0 * t) - sin(w0 * t) * phi(t)] 
    
    This shows that phase noise will be mixed with the carrier to produce sidebands around the carrier.
    
    2) In other words, exp(j * x) ~ (1 + j * x) for small x
    
    3) Phase noise = 0 dBc/Hz at freq. offset of 0 Hz
    
    4) The lowest phase noise level is defined by the input SSB phase noise power at the maximal 
    freq. offset from DC. (IT DOES NOT BECOME EQUAL TO ZERO)
    
    The generation process is as follows:
    First of all we interpolate (in log-scale) SSB phase noise power spectrum in M 
    equally spaced points (on the interval [0, Fs/2] including bounds).
    
    After that we calculate required frequency shape of the phase noise by X(m) = sqrt(P(m) * dF(m)) 
    and after that complement it by the symmetrical negative part of the spectrum.
    
    After that we generate AWGN of power 1 in the freq domain and multiply it sample-by-sample to 
    the calculated shape 
    
    Finally we perform 2 * M - 2 points IFFT to such generated noise
    ( See comments inside the code)
    
    0 dBc/Hz                                
    \                                                          /
     \                                                        / 
      \                                                      /  
       \P dBc/Hz                                            /   
       .\                                                  /    
       . \                                                /     
       .  \                                              /      
       .   \____________________________________________/  /_ This level is defined by the phase_noise_power at the    
       .                                                   \  maximal freq. offset from DC defined in phase_noise_freq
    |__| _|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__   (N points)
    0   dF                       Fs/2                          Fs
    DC
    """
    
    if not np.any(np.imag(Sin)):
        print("Input signal should be complex signal")
        
        return None
    
    if np.max(phase_noise_freq) >= Fs/2:
        print("Maximal frequency offset should be less than Fs/2")
        
        return None
    
    if phase_noise_freq.shape[0] != phase_noise_power.shape[0]:
        print("phase_noise_freq and phase_noise_power should be of the same length")
        
        return None
    
    realmin = 2.225073858507201e-308
    
    # Sort phase_noise_freq and phase_noise_power
    phase_noise_freq_sorted = np.array([phase_noise_freq[0]])
    phase_noise_power_sorted = np.array([phase_noise_power[0]])
    for i, phase_noise_freq_i in enumerate(phase_noise_freq):
        for j, phase_noise_freq_sorted_j in enumerate(phase_noise_freq_sorted):
            if j == 0 and phase_noise_freq_i < phase_noise_freq_sorted_j:
                phase_noise_freq_sorted = \
                    np.insert(phase_noise_freq_sorted, 0, phase_noise_freq_i)
                phase_noise_power_sorted = \
                    np.insert(phase_noise_power_sorted, 0, phase_noise_power[i])
                break
            elif i != 0 and \
                    j == phase_noise_freq_sorted.shape[0] - 1 and \
                    phase_noise_freq_i >= phase_noise_freq_sorted_j:
                phase_noise_freq_sorted = \
                    np.append(phase_noise_freq_sorted, phase_noise_freq_i)
                phase_noise_power_sorted = \
                    np.append(phase_noise_power_sorted, phase_noise_power[i])
                break
            else:
                if i != 0 and \
                        j != phase_noise_freq_sorted.shape[0] - 1 and \
                        phase_noise_freq_i >= phase_noise_freq_sorted_j and \
                        phase_noise_freq_i < phase_noise_freq_sorted[j + 1]:
                    phase_noise_freq_sorted = \
                        np.insert(phase_noise_freq_sorted, j + 1, phase_noise_freq_i)
                    phase_noise_power_sorted = \
                        np.insert(phase_noise_power_sorted, j + 1, phase_noise_power[i])
                    break
    phase_noise_freq = phase_noise_freq_sorted
    phase_noise_power = phase_noise_power_sorted
    
    # Add 0 dBc/Hz @ DC
    if phase_noise_freq[0] != 0:
        phase_noise_freq = np.insert(phase_noise_freq, 0, 0)
        phase_noise_power = np.insert(phase_noise_power, 0, 0)
        
    # Calculate input length
    N = Sin.shape[0]
    
    # Define M number of points (frequency resolution) in the positive spectrum 
    # (M equally spaced points on the interval [0, Fs/2] including bounds), 
    # then the number of points in the negative spectrum will be M - 2 
    # (interval (Fs/2, Fs) not including bounds)
    #
    # The total number of points in the frequency domain will be 2 * M - 2, and if we want 
    # to get the same length as the input signal, then
    # 2 * M - 2 = N
    # M - 1 = N/2
    # M = N/2 + 1
    #
    # So, if N is even then M = N/2 + 1, and if N is odd we will take M = (N + 1)/2 + 1
    if N % 2 == 1:
        M = (N + 1)/2 + 1
    else:
        M = N/2 + 1
    M = int(M)
    
    # Equally spaced partitioning of the half spectrum
    F = np.linspace(0, Fs/2, M) # % Freq. Grid
    dF = np.append(np.diff(F), F[-1] - F[-2])   # Delta F
    
    # Perform interpolation of phase_noise_power in log-scale
    intrvlNum = phase_noise_freq.shape[0]
    logP = np.zeros(M)
    for intrvlIndex in range(intrvlNum):
        leftBound = phase_noise_freq[intrvlIndex]
        t1 = phase_noise_power[intrvlIndex]
        if intrvlIndex == intrvlNum - 1:
            rightBound = Fs/2
            t2 = phase_noise_power[-1]
            inside = []
            for i in range(F.shape[0]):
                if F[i] >= leftBound and F[i] <= rightBound:
                    inside.append(i)
        else:
            rightBound = phase_noise_freq[intrvlIndex + 1]
            t2 = phase_noise_power[intrvlIndex + 1]
            inside = []
            for i in range(F.shape[0]):
                if F[i] >= leftBound and F[i] < rightBound:
                    inside.append(i)
        logP[inside] = t1 + \
            (np.log10(F[inside] + realmin) - np.log10(leftBound + realmin)) / \
            (np.log10(rightBound + realmin) - np.log10(leftBound + realmin)) * \
            (t2 - t1)
    P = 10**(logP.real/10)
    
    # Now we will generate AWGN of power 1 in frequency domain and shape it by the desired shape
    # as follows:
    #
    # At the frequency offset F(m) from DC we want to get power Ptag(m) such that P(m) = Ptag/dF(m),
    # that is we have to choose X(m) = sqrt(P(m) * dF(m));
    # 
    # Due to the normalization factors of FFT and IFFT defined as follows:
    # For length K input vector x, the DFT is a length K vector X,
    # with elements
    #                  K
    #    X(k) =       sum  x(n)*exp(-j*2*pi*(k-1)*(n-1)/K), 1 <= k <= K.
    #                 n=1
    # The inverse DFT (computed by IFFT) is given by
    #                  K
    #    x(n) = (1/K) sum  X(k)*exp(j*2*pi*(k-1)*(n-1)/K), 1 <= n <= K.
    #                 k=1
    #
    # we have to compensate normalization factor (1/K) multiplying X(k) by K.
    # In our case K = 2 * M - 2.
    
    # Generate AWGN of power 1
    if not VALIDATION_ON:
        awgn_P1 = np.sqrt(0.5) * (np.random.randn(M) + 1j * np.random.randn(M))
    else:
        awgn_P1 = np.sqrt(0.5) * (np.ones(M) + 1j * np.ones(M))
        
    # Shape the noise on the positive spectrum [0, Fs/2] including bounds (M points)
    X = (2 * M - 2) * np.sqrt(dF * P) * awgn_P1
    
    # Complete symmetrical negative spectrum (Fs/2, Fs) not including bounds (M - 2 points)
    X = np.append(X, np.fliplr(np.conj(X[1: -1]).reshape(1, -1)).reshape(-1))
    
    # Remove DC
    X[0] = 0
    
    # Perform IFFT
    x = fftpack.ifft(X)
    
    # Calculate phase noise 
    phase_noise = np.exp(1j * np.real(x[: N]))
    
    # Add phase noise
    if not VALIDATION_ON:
        Sout = Sin * phase_noise[: Sin.shape[0]]
    else:
        Sout = "VALIDATION IS ON"
        
    if VALIDATION_ON:
        plt.figure()
        plt.plot(phase_noise_freq, phase_noise_power, 'o-', \
                     label='Input SSB phase noise power')
        plt.grid()
        plt.plot(F, 10 * np.log10(P), 'r*-', \
                     label='Interpolated SSB phase noise power')
        X1 = fftpack.fft(phase_noise)
        plt.plot(F, 10 * np.log10(((np.abs(X1[: M])/np.max(np.abs(X1[: M])))**2)/dF[0]), 'ks-', \
                     label='Positive spectrum of the generated phase noise exp(j * x)')
        X2 = fftpack.fft(1 + 1j * np.real(x[: N]))
        plt.plot(F, 10 * np.log10(((np.abs(X2[: M])/np.max(np.abs(X2[: M])))**2)/dF[0]), 'm>-', \
                     label='Positive spectrum of the approximation (1 + j * x)')
        plt.xlabel('Frequency [Hz]')
        plt.ylabel('dBc/Hz')
        plt.legend()
        plt.show()
        
    return Sout
    

# %%
def test():
    Fc = 3e3    # carrier frequency
    Fs = 40e6    # sampling frequency
    t = np.arange(0, 10000)
    S = np.exp(1j * 2 * np.pi * Fc/Fs * t)  # complex sinusoid
    
    phase_noise_freq = np.array([1e3, 10e3, 100e3, 1e6, 10e6])  # Offset From Carrier
    phase_noise_power = np.array([-84, -100, -96, -109, -122])    # Phase Noise power
    S1 = add_phase_noise(S, Fs, phase_noise_freq, phase_noise_power, True)
    
    return None
    

if __name__ == "__main__":
    test()
    
    


Python版本运行结果分别如下:

给信号叠加相位噪声_第1张图片

Matlab版本运行结果如下:

给信号叠加相位噪声_第2张图片

你可能感兴趣的:(Python)