1

I want to simulate how a Gaussian beam would look like at the receiver plane when propagated through an atmospheric turbulence. For this I am using AO package. Since I didn't see any function for multipropagation i.e. choosing multiple phase screens between the distance and propagating partially. Taking reference from the book "Numerical Simulation of optical wave propagation with examples in Matlab", i modified the source code for angularSpectrum().matlab code

My code:

# -*- coding: utf-8 -*-
"""

@author: """ import numpy as np import matplotlib.pyplot as plt from aotools import phasescreen, opticalpropagation, atmos_conversions, fouriertransform

class beam: def init(self, sigma, Eo, z, x, lambda_, r0, N, delta, L0, l0, num): self.sigma = sigma self.Eo = Eo self.z = z self.x = x self.y = x self.X,self.Y = np.meshgrid(self.x, self.y) self.lambda_ = lambda_ self.r0 = r0 self.N = N self.delta = delta self.L0 = L0 self.l0 =l0 self.num = num

def electric_field(self):  #generate a Gaussian beam 
    return self.Eo * np.exp(-0.5*(self.X **2 + self.Y**2)/self.sigma**2)


def PS(self):    #generate phase screens
    phase_arr = []
    for i in range(self.num):
        phase_arr.append(phasescreen.ft_sh_phase_screen(self.r0, self.N, self.delta, self.L0, self.l0, seed = None))
    return np.array(phase_arr)

def angularSpectrum(self, inputSpacing, outputSpacing, t):   #modified this from source code
    """
    Propogates light complex amplitude using an angular spectrum algorithm

    Parameters:
        inputComplexAmp (ndarray): Complex array of input complex amplitude
        wvl (float): Wavelength of light to propagate
        inputSpacing (float): The spacing between points on the input array in metres
        outputSpacing (float): The desired spacing between points on the output array in metres
        z (float): Distance to propagate in metres

    Returns:
        ndarray: propagated complex amplitude
    """
    wvl = self.lambda_
    z = self.z
    inputComplexAmp = self.electric_field()
    # If propagation distance is 0, don't bother 
    # if self.z==0:
    #     return inputComplexAmp
    mag = float(outputSpacing)/inputSpacing
    N = self.N
    k = 2*np.pi/wvl     #optical wavevector

    (x1,y1) = np.meshgrid(inputSpacing*np.arange(-N/2,N/2),   #receiver plane meshgrid
                             inputSpacing*np.arange(-N/2,N/2))
    r1sq = (x1**2 + y1**2) + 1e-10
    Q1 = np.exp( 1j * k/2. * (1-mag)/delta * r1sq)  #not changed
    Uin = inputComplexAmp *Q1 * t[0]
    w = N * 0.47
    sg = np.exp(-(self.X**2 + self.Y**2)**8/(w**16))
    for idx in range(self.num):

    #Spatial Frequencies (of source plane)
        df1 = 1. / (N*inputSpacing)
        fX,fY = np.meshgrid(df1*np.arange(-N/2,N/2),
                               df1*np.arange(-N/2,N/2))
        fsq = fX**2 + fY**2

        #Scaling Param


        Q2 = np.exp(-1j * np.pi**2 * 2 * z/mag/k*fsq)
        Uin = sg * t[idx]*fouriertransform.ift2(
                    Q2 * fouriertransform.ft2(Uin/mag,inputSpacing), df1)
        #Observation Plane Co-ords
    x2,y2 = np.meshgrid( outputSpacing*np.arange(-N/2,N/2),
                            outputSpacing*np.arange(-N/2,N/2) )
    r2sq = x2**2 + y2**2

    #Quadratic phase factors

    Q3 = np.exp(1j * k/2. * (mag-1)/(mag*z) * r2sq)

    #Compute propagated field
    outputComplexAmp = Q3 * Uin*Q1
    return outputComplexAmp


def Fresnelpropagation(self):  #optical propagation
    phase_arr = self.PS()
    Uout = self.angularSpectrum(self.delta, self.delta, phase_arr)
    return np.array(Uout)


def avg_real(self):  #take average of few iterations

    av_m = 100
    Uout = self.Fresnelpropagation()
    Uav = np.zeros((Uout.shape))*1j
    for i in range(av_m):
        Uav += Uout

    return Uav/av_m



def plot_(Uin,G): # Assuming you have defined m, X, Y, and G appropriately for each propagating distance

m = G.shape[0]
fig, ax = plt.subplots(2, m+1)  # Create n subplots in a single row
ax[0, 0].imshow(np.abs(Uin), cmap = 'viridis')
ax[1, 0].imshow(np.imag(Uin), cmap = 'viridis')  # Display the absolute values of G[i] in each su
for i in range(1,m+1):
    ax[0, i].imshow(np.abs(G[i-1]), cmap = 'viridis')
    ax[1, i].imshow(np.imag(G[i-1]), cmap = 'viridis')  # Display the absolute values of G[i] in each subplot

plt.show()  # Display the plot

def func(cn2_,N): #try for a few cn2 values count = 1 for cn2 in cn2_:

    x = np.linspace(-D/2,D/2,N)
    print(N)
    r0 = atmos_conversions.cn2_to_r0(cn2, lambda_)
    print(D/r0)
    z = 1e2   #distance between two phase screens
    #convert cn2 to ro 
    # sigma, Eo, z, x, lambda_, r0, N, delta, L0, l0, num
    g = beam(sigma, Eo, z, x, lambda_, r0, N, delta, L0, l0, num)
    Uin = g.electric_field()
    Uav.append(g.avg_real(count))
    count += 1
print(np.array(Uav).shape[0])
plot_(Uin, np.array(Uav))

plt.imshow(gaussian.electric_field())

D = 0.3 #receiver size N = 100 #phase screen grid size num = 10 #numner of phase screen Eo = 1 delta = D/N #size of each pixel in a phase screen grid L0 = 1 #the outer scale l0 = 0.05 #the inner scale lambda_ = 106410(-9) #1064nm sigma = 0.01 #for beam width #different values of cn2[![output][2]][2] cn2_ = [110(-19), 1*10(-13), 110*(-10)] Uav = []
func(cn2_,N)

I don't know if i did it correctly but the output I see is weird. I'd appreciate some help with understanding the output, if the code is correct. P.S the first plot is the source and the rest are on the basis of increasing atmospheric turbulence.

when running code for multiple times, i get different results. I do understand that the turbulence is random but shouldn't the output be somewhat similar based on the cn2 values? Output:

fig 1 fig2 fig 3 fig 4

Rima
  • 111
  • 2

0 Answers0