In fact the problem lays in the way you run your fft. It is well explained in Fourier Optics and Computational Imaging by Khedar Kare, Wiley 2015:
The appropriate sequence for 2D FFT in most programming platforms for the result to be meaningful from Physical
standpoint (e.g. in describing phenomena like diffraction) is thus
given by: fftshift(fft2(ifftshift(...))).
In your code you should:O = fftshift(fft2(ifftshift(U0)));
Should you be interested in software developed in Python there is a rapidly developing Python toolbox for optics including diffraction: PyOptica. In PyOptica, a wavefront can be propagated using:
import astropy.units as u
import numpy as np
import pyoptica as po
wavelength = 500 * u.nm
pixel_scale = 22 * u.um
npix = 1024
w = 6 * u.mm
h = 3 * u.mm
axis_unit = u.mm
wf = po.Wavefront(wavelength, pixel_scale, npix)
ap = po.RectangularAperture(w, h)
wf = wf * ap
fig_1 = po.plotting.plot_wavefront(wf, 'intensity', axis_unit=axis_unit)
The second step is to propagate:
f = 50 * u.cm
fs_f = po.FreeSpace(f)
wf_forward = wf * fs_f
fig_2 = po.plotting.plot_wavefront(wf_forward, 'intensity', axis_unit=axis_unit)
It is important to remember sampling conditions for Fresnel propagation that is:
( z <= N (dx)^2 / lambda) where:
- N - number of pixels (in one direction);
- dx - pixel size;
- lambda - wavelength.
That condition is based on Computational Fourier Optics: A MATLAB Tutorial by David Voelz, SPIE 2011
You should implement that condition in your code. In PyOptica it is always checked before propagation; if the requested distance violates the condition, the propagation distance is broken up into substeps.