IQ modulator demo (supplementary material)#

  • IQ modulator is widely used in coherent optical communications.

  • We can use an IQ modulator to generate signals with advanced modulation formats such as QPSK, and m-QAMs.

What we are going to learn in this notebook#

  • Understand how I/Q modulator works

  • Generate I/Q signals, use numpy function ‘fft’ for Discrete Fourier Transform.

  • Apply I/Q signals to I/Q modulator model

  • Plot output signals

How I/Q modulator works#

from IPython.display import Image
from IPython.core.display import HTML 
Image(url="https://img.lightwaveonline.com/files/base/ebm/lw/image/2015/12/1503lwcovrstoryf2.png?auto=format&w=1500&h=843&fit=max")

[1] https://www.lightwaveonline.com/articles/print/volume-32/issue-2/features/optical-iq-modulators-for-coherent-100g-and-beyond.html

Generate I/Q signals#

  • pattern mapping

  • raised cosine filter for pulse shaping (roll-off factor 0.2)

%pylab inline

def makeQAMtarget(shift=0.,N = 2**31,BaudRate = 30, BD=2,beta=.1,half=False,pat_en=None,ModNumber=2,get_rid_DC=None,DEBUG = False):
    """
    In this particular example, we generate 16-QAM signals.
    We can also generate M-QAM signals.
    """
    if pat_en is None:
        Np = int((N)/BD)
        random.seed(103231)
        pattern = (random.randn(Np)>0) + 1j*(random.randn(Np)>0) # QPSK
        pattern -= .5+.5j
        p1 = (random.randn(Np)>0) + 1j*(random.randn(Np)>0)
        p1 -= .5+.5j
        pattern += p1*.5 # 16QAM
        p1 = (random.randn(Np)>0) + 1j*(random.randn(Np)>0)
        p1 -= .5+.5j
        #pattern += p1*.25 # 64QAM
    else:
        pattern1=io.loadmat(pat_en)
        pattern = pattern1['a'][:,0]
        pattern -= np.mean(pattern)
        pattern /=(mean(abs(pattern)**2))**0.5
        if ModNumber==1:
            pattern=roll(pattern,len(pattern)//2)
    
    SampleRate = 2.*BaudRate
    br = BD/SampleRate
    N = int(len(pattern)*BD)
    f = arange(-N/2,N/2)/N*SampleRate
    e = zeros(N,complex)
    e[::int(BD)] = pattern
    s = fft.fftshift(fft.fft(e))
    #plot(f,)
    shaping_filter = RCFilter(beta,f,br)
    if DEBUG:
        figure(figsize = (5,2))
        plot(f,shaping_filter)
    s *=shaping_filter
    if half:
        s*= f>0
    if get_rid_DC:
        s*= abs(f)>10
    #pre-emphasis for dac roll off
    et = fft.ifft(fft.fftshift(s))
    et /= mean(abs(et)**2)**.5
    return f,et,pattern 

def RCFilter(beta,f,T):
    """
    raised-cosine filter for pulse shaping
    """
    H = zeros(len(f),dtype = complex)
    finner = abs(f)<=((1-beta)/2./T)
    fmiddle = (abs(f)>=((1-beta)/2./T))&(abs(f)<=((1+beta)/2./T))
    H[finner] = 1
    H[fmiddle] = 1./2*(1+cos(pi*T/beta*(abs(f[fmiddle])-(1-beta)/2./T)))
    
    return H

## et is
f,et,pattern = makeQAMtarget(shift = 0.,
                             N = 2**15,
                             BaudRate = 30.,
                             BD = 2.,
                             beta = 0.2,
                             half = False,
                             DEBUG = False)

figure(1,figsize = (5,5))
plot(real(et[::2]),imag(et[::2]),'bo',label = 'sampled points')
plot(real(et),imag(et),'r,',label = 'full waveform')
title('constellation for I/Q signal',fontsize = 18)
xlabel('x-axis',fontsize = 15)
ylabel('y-axis',fontsize = 15)
legend()

figure(2,figsize = (5,5))
plot(real(pattern),imag(pattern),'bo',label = 'target pattern')
title('constellation for target pattern',fontsize = 18)
xlabel('x-axis',fontsize = 15)
ylabel('y-axis',fontsize = 15)
xlim(-2,2)
ylim(-2,2)
legend();
%pylab is deprecated, use %matplotlib inline and import the required libraries.
Populating the interactive namespace from numpy and matplotlib
_images/4bea65c9c683a592f51e1ab2b5e371ca1f643b5a22bc36cdcf925c1bf5b8e9a8.png _images/95408a7d46a45300ca8ab64817c646663c71fa752b4e99132338ad3bf798f849.png

Apply generated signals to I/Q modulator model#

  • practical model includes modulator bias, 90 degree phase shifter, Vpi, Driver amplitude and OSNR

  • practical model includes polarization modulator (here we assume time-invariant polarization shift).

Plot output signals#

def Pol_Emu(e_te,e_tm,theta,baudrate):
    """
    polarization emulator (only with time-invariant polarization shift)
    e_te: TE signal
    e_tm: TM signal
    theta: polarization rotating angle
    """
    N = len(e_te)
    dt = 1./2/baudrate
    t = arange(0,N)*dt
    c  = cos(theta)
    s = sin(theta)
    
    eH = e_te*c+e_tm*s
    eV = -e_te*s+e_tm*c
    
    return eH,eV

def TX_SIMU(efull,N = 2**15,Bias_I = 0.,Bias_Q = 0.,TX_90 = pi/2.,Driver_Amp = 2,Vpi = 4.,OSNR = 20.,theta = 0.):
    '''
    et is the full pattern
    N is the data length that we used for the pattern, and it started with a initial value
    Bias_I is the bias phase shifter of the I side
    Bias_Q is the bias phase shifter of the Q side
    TX_90 is the phase shifter of 90 degree phase shifter
    Driver_Amp is the driver output amplitude
    Vpi is the Voltage needed for pi phase shift of the MZM (I/Q)
    '''
    initial_index = mod(floor(random.randn()*2**15),2**15) 
    if mod(initial_index,2) == 1:
        initial_index -= 1
    
    initial_index = int(initial_index)
    e = efull[initial_index:initial_index+N]
    Ph_I = real(e)*Driver_Amp+Bias_I
    Ph_Q = imag(e)*Driver_Amp+Bias_Q
    
    eout = 0.5*(sin(0.5*Ph_I/Vpi*pi)+exp(1j*TX_90)*sin(0.5*Ph_Q/Vpi*pi))
    eout1 = roll(eout,380)
    
    ### need to add AWGN based on OSNR (default is 20 dB)
    GBB_temp = 10**((OSNR-10*log10(30.*2./12.5))/10.)
    power_noise = mean(abs(eout)**2.)**.5/(1+GBB_temp)
    print(power_noise)
    noise_add = (random.randn(N)+1j*random.randn(N))*power_noise
    noise_add1 = (random.randn(N)+1j*random.randn(N))*power_noise
    eout_awgn = eout + noise_add
    eout_awgn1 = eout1 + noise_add1
    
    eH,eV = Pol_Emu(eout_awgn,eout_awgn1,theta,30)

    return eH,eV
e_full = tile(et,2**6) ### repeat et for 2**6 times
Eout_H,Eout_V = TX_SIMU(efull = e_full,
                        N = int(2**15),
                        Bias_I = 0,
                        Bias_Q = 0,
                        TX_90 = pi/2.,
                        Driver_Amp = 1.,
                        Vpi = 2.,
                        OSNR = 20.,
                        theta = 0.)

figure(1,figsize = (8,4))
subplot(121)
plot(real(Eout_H[::2]),imag(Eout_H[::2]),'b,')
title('H pol')
xlabel('x-axis',fontsize = 15)
ylabel('y-axis',fontsize = 15)

subplot(122)
plot(real(Eout_V[::2]),imag(Eout_V[::2]),'r,')
title('V pol')
xlabel('x-axis',fontsize = 15);
0.016226785771489192
_images/af31b66b1424b06339be1e717f758289fd6d947c86bfaf79509bea784a589190.png