opticspy / lightpipes

LightPipes for Python, "Pure Python version"
https://opticspy.github.io/lightpipes/
BSD 3-Clause "New" or "Revised" License
227 stars 52 forks source link

interference pattern with a focused x-ray beam (spherical coordinates propagation & Steps) #79

Open nimloth-a opened 10 months ago

nimloth-a commented 10 months ago

Hello,

In the first place I would like to thank you very much for the LightPipes package. It is very useful!

I am currently using it to simulate the propagation of a soft X-ray gaussian beam (1.42 nm). We focus the beam with a pair KB mirrors at f=3m. I want to simulate the intensity distribution around the focus with and without a spatial mask and I have a few questions.

I use the spherical coordinates. Without spatial mask and propagating with LensFresnel or lensForvard I get a similar result, a very tight focus, as expected.

This is my code:

from LightPipes import *
import matplotlib.pyplot as plt
from matplotlib import cm as colormap
%matplotlib widget
import numpy as np
from tqdm.notebook import tqdm  #(to have progress bars)

grid_dimension = 2000
wavelength = 1.42*nm;
grid_size = 20*mm;
beamsize = 2.2/1.18*mm; 
focal_length = 3*m;

F = Begin(grid_size, wavelength, grid_dimension)
F = GaussBeam(F, beamsize, n=0, m=0, x_shift=0, y_shift=0, tx=0, ty=0, doughnut=False, LG=False)

I = Intensity(0,F);
P = Phase(F);

x=[]
for i in range(grid_dimension):
    x.append((-grid_size/2+i*grid_size/grid_dimension)/mm)
x = np.asarray(x)
X, Y = np.meshgrid(x, x)

fig = plt.figure(figsize=(12,4))
plt.suptitle('Incoming gaussian beam')
ax1 =  fig.add_subplot(131, projection='3d')
ax1.set_xlabel('x (mm)');
ax1.set_ylabel('y (mm)');
ax1.set_zlabel('Intensity (a.u.)');
# ax1.imshow(I_aperture, cmap='rainbow'); 
ax1.plot_surface(X, Y, I, cmap=colormap.coolwarm, linewidth=0, antialiased=False)
ax2 = fig.add_subplot(132)
ax2.plot(x, I[int(grid_dimension/2)], marker = '.'); 
ax2.set_xlabel('x (mm)');
ax2.set_ylabel('Intensity (a.u.)')
ax2.grid('on')
ax3 = fig.add_subplot(133)
ax3.plot(x,P[int(grid_dimension/2)]); 
plt.tight_layout()
plt.show()

image

F_m = F
prop_distance = focal_length
f1 = 3000*m
f2 = f1*focal_length/(f1-focal_length)
frac = focal_length/f1
newsize = frac*grid_size

print('focal_length=',focal_length,'f1=',f1,'f2=',f2,'; grid narrowing=',1/frac)
print('new pixel size = ', newsize/grid_dimension, 'm')
print('expected focus diameter = ', (4*wavelength*focal_length/(np.pi*2*beamsize)), 'm') 

### Single-step propagation
F_m = Lens(F_m, f1, 0, 0);
F_m = LensFresnel(F_m, f2, prop_distance);
#F_m = LensForvard(F_m, f2, prop_distance);
F_m = Convert(F_m);
I_m = Intensity(0, F_m)

x2 = []
for i in range(grid_dimension):
    x2.append((-newsize/2+i*newsize/grid_dimension)/mm)
x2 = np.asarray(x2)
X2, Y2 = np.meshgrid(x2, x2)

### Plotting
fig = plt.figure(figsize=(9,4))
ax1 =  fig.add_subplot(121)
ax1.pcolormesh(X2, Y2, I_m[:,:], cmap=colormap.coolwarm, linewidth=0, antialiased=False)
ax2 = fig.add_subplot(122)
ax2.plot(x2, I_m[int(grid_dimension/2),:], marker = '.',label = 'lensForvard')#, color = color);
ax2.set_xlabel('x [mm]');
ax2.set_ylabel('Intensity [a.u.]')
plt.tight_layout()
plt.show()

The only difference in the results using LensFresnel or LensForvard comes in the beam size (calculated as FWHM).

sx_m, sy_m = D4sigma(F_m)
FWHMx_m = (sx_m/4)*2.35
FWHMy_m = (sy_m/4)*2.35
print('FWHM = ',FWHMx_m, 'm', FWHMy_m, 'm')

Fresnel gives systematically larger values than Forvard. I tested it with optical beams and non-spherical coordinates, and it seems to happen always. However, their intensities at the focus overlap (see figure below). I don’t really understand why this happens, any idea? It seems that Forvard gives the correct value, according to the intensity distribution plot. In my case the beamsize in the focus (FWHM) is 1.15e-06 m according to lensFresnel and 8.55e-07 m according to lensForvard.

image

For the spatial mask I just add these lines to the code before the propagation:

F_m = F
R=0.5*mm; # radius of the holes
d=2*mm; # distance between apertures
F1 = CircAperture(R,-d/2,d/2,F_m);
F2 = CircAperture(R,-d/2,-d/2,F_m);
F3 = CircAperture(R,d/2,d/2,F_m);
F4 = CircAperture(R,d/2,-d/2,F_m);
F_m = BeamMix(BeamMix(BeamMix(F1,F2),F3),F4);

image

Single-step propagation to the focus gives also similar results with both methods:

image

Then I try to explore what happens around the focus, so I propagate to different points close to the focus. To do so, I change the propagation section by the following:

steps_focus = np.arange(2.9*m,3.001*m,0.001*m) 
I_m_focus = np.zeros((len(steps_focus),I_m.shape[0], I_m.shape[1]))

F_ml = Lens(F_m, f1, 0, 0);
for k in tqdm(range(len(steps_focus))):
    F_m_temp = LensFresnel(F_ml, f2, steps_focus[k]);
    # F_m_temp = LensForvard(F_ml, f2, steps_focus[k]);
    F_m_temp = Convert(F_m_temp);
    I_m_temp = Intensity(1, F_m_temp)
    I_m_focus[k,:,:] = I_m_temp

Here already the issue is that I don’t see the focusing of the beam for most of the steps, only when getting really close to the focus.

With No mask, LensFresnel or No mask, lensForvard I get similar results:

image

The beam focusing is only visible when we are very close to the focus. Further away from it, we are not really simulating the focusing, looks like a collimated beam. Any idea why? Is the idea to simulate the approach to the focus incorrect? I don’t really understand why it does not work… The final grid size is fixed and the same in all cases since it is given by the choice of f1 and f. The intensity, if not normalized, does change, but the beam diameter does not.

With mask and lensFresnel or lendForvard I get:

image

Which seems to be rather diverging than focusing. For Fresnel I cannot go after the focus, so I use lensForvard to simulate the beam across the focus (steps_focus = np.arange(2.95m,3.05m,0.001*m) ):

No mask, with lensForvard:

image

With mask:

image

I understand the weird behavior in the focus happens because of what is mentioned in the manual “ As the coordinates follow the geometrical section of the light beam, operator LensForvard(10,10,Field) will produce floating exception because the calculations can not be conducted in a grid with zero size (that is so in the geometrical approximation of a focal point).”

But then my question is until which point the simulation is correct? Until the propagation distance is equal to f? without the mask the beam is converging, but with the mask it starts to diverge already when approaching the focus. And from where is it correct? From where we do observe focusing of the beam? because far from the focus, no focusing is observed.

Then to propagate across the focus, I thought I could do something like propagate close to the focus with spherical coordinates and then around the focus, go back to normal coordinates and use Steps. So I tried the following:

prop_distance = 2.990*m
F_m = Lens(F_m, f1, 0, 0);
F_m = LensFresnel(F_m, f2, prop_distance);
# F_m = LensForvard(F_m, f2, prop_distance);
F_m = Convert(F_m);
I_m = Intensity(0, F_m)

F_m_steps = F_m
prop_distance_steps = 0.020*m
dz = 0.001*m; 
n=(1+0.00000000001*1j)*np.ones((grid_dimension,grid_dimension)) 
steps = prop_distance_steps/dz;

print('prop_distance = ', prop_distance, 'm; Step = ', dz, 'm; #steps = ', steps )

Icross = np.zeros((int(steps), grid_dimension))

for i in tqdm(range(0, int(steps))):
    F_m_steps = Steps(dz, 1, n, F_m_steps); # Steps(Fin, z, nstep=1, refr=1.0, save_ram=False, use_scipy=False)
    I_m_steps = Intensity(1, F_m_steps);
    if i ==10:
        I_m_steps_d = I_m_steps
    Icross[i][:grid_dimension] = I_m_steps[int(grid_dimension/2)][:grid_dimension]

fig = plt.figure(figsize=(9,4))
ax1 =  fig.add_subplot(121)
ax1.pcolormesh(X2, Y2, I_m_steps_d[:,:], cmap=colormap.coolwarm, linewidth=0, antialiased=False)
ax2 = fig.add_subplot(122)
ax2.pcolormesh(Icross, cmap=colormap.coolwarm, linewidth=0, antialiased=False)
ax2.set_ylabel('steps')
plt.tight_layout()
plt.show()

image

The propagation across the focus looks reasonable and the interference pattern in the focus is actually similar as before. However, the size of the features is significantly smaller (approx. 100 nm vs. 1 micron) than with mask and lensFresnel or lendForvard (see above). I am a bit puzzled why. Any idea? I would assume this last case is more correct but why the other fails and is so different in the simulated size?

Is this propagation scheme consistent?

Sorry for the very long question, and thanks a lot in advance for any help!!!

kicklop commented 9 months ago

Hello, I think the best approach for you would be to propagate the beam into the focus of the KB by using LensFarField and then use Forvard to propagate back and forth around the focus. The result of LensFarField is a Field in the focal plane of a lens. The LightPipes LensFarField is not yet deployed, but you can input this function into your script and use it (taken from https://github.com/opticspy/lightpipes/commit/0e837359d4945084f93878f28311feaf769aa42a). There is a question about correct amplitude of the field but that doesn't have to concern you since you normalize the beams. The dimension of grid scaling should be correct.:

def TestLensFarField(Fin, f):
"""
    Use a direct FFT approach to calculate the far field of the input field.
    Given the focal length f, the correct scaling is applied and the
    output field will have it's values for size and dx correctly set.
    Also applies correct prefactors to field as calculated from Fresnel
    approximation and lens as square phase.
    Parameters
    ----------
    f : double
        Focal length in meters/ global units
    Fin : lp.Field
        The input field.
    Returns
    -------
    The output field.
    """
    dx = Fin.dx
    lam = Fin.lam
    k = 2*_np.pi/lam
    L_prime = lam * f / dx
    Fout = PipFFT(Fin, index=1)
    Fout.siz = L_prime
    Fout.field *= Fin.dx**2 # hope this is correct, should be as long as numpy normalization = backward (none for forward)
    Fout.field *= 1/(1j*lam*f) * _np.exp(1j*k*f)
    Fout.field *= np.exp(1j*k/(2*f)*(Fout.mgrid_Rsquared))
    Fout._IsGauss=False
    return Fout

Getting a nice image in the Lens far field requires playing with number of grid points and grid size (bigger beam in the image at the start => smaller beam in the image in the lens far field)

As to the propagation from KB somewhere closer to focus with LensForvard/LensFresnel. The thing is that the real dimension [m] of your grid is getting smaller while the image is not changing that much. I think that this is your problem for the steps vs. x[mm] plots, where you don't see the focusing. You probably plot steps vs. pixels, but the scaling of pixels to mm changes and you don't reflect this in your plots and can't see the focusing.

I gained this result of beam dimension against propagation distance (it is distance from the focus at 3m from the KB, so 0 um is at 3 m from KB, -1000um is at 2.999m from KB). Green dotted lines show positions where propagation for (something like) LensForvard was changed to propagation around focus by LensFarField and Forvard, red dotted line is change from LensFarField to "LensForvard". image_2023-11-18_125847274

I something like LensForvard, but not quite that, so I can't tell you the parameters to set, however getting a nice image should requite playing with f2, grid_size, number of grid points.

And lastly to the physics. I have some experience with X-ray beams focused by KB optics and the beam in the focus is quite different from Gaussian. There is always astigmatism from KB, the beam itself has wavefront distortions that show in the close to the focal plane. You can use this simulation as a best case scenario but you should count with having a different beam in the focus. A reconstruction of a beam profile in the focus can be found for example here: https://doi.org/10.1364/OE.21.026363. And if your experiment is highly dependent of the intensity distribution in the focus, you should consider measuring the real intensity distribution there, for example by the method in the article or by different methods.