Speckle simulation involves constructing a wrinkled wavefront and an aperture illumination function, then performing a Fourier transform using this amplitude/phase information to generate the far-field diffraction pattern arising from this input.
The most complicated part is generating a realistic wavefront. To do this, we use a Zernike basis, and a "power spectrum" of strength in each term. A Gaussian random variable for each Zernike term scales its influence in each instance. Each term is summed to produce a total wavefront. The series is terminated once a target precision is reached, where the amplitudes of the new additions become smaller than some threshold (default = 0.05 radians).
The coding was originally in IDL, recently ported to Python, with slight enhancements. First, I'll describe the common traits.
One must first decide on a D/r0 ratio to simulate. D is the aperture diameter, and r0 is the characteristic scale (the Fried parameter) of wavefront distortions, so that the long-exposure "seeing" measure is λ/r0 rather than λ/D.
The level to which the simulation is carried out will impact the fidelity (and speed) of the simulation. The default value is 0.05, meaning that higher-order terms contributing less than 0.05 radians to the sum will be truncated. This does not mean that the wavefront is solid to 0.05 radians, as smaller terms can add to something larger than this. But it probably does mean fidelity to about 34 times this level.
In principle, each simulation can be adjusted to the number of pixels covered (slightly easier in the Pyhton version); the default is 256.
The Zernike amplitude spectrum followed is as prescribed in Hardy (page 96) in both cases: the first 10 terms are from Table 3.3, and the remaining terms follow the power law set by Eq. 3.71.
Padding: In order to resolve the structure with adequate sampling, it is necessary to embed the aperture/phase function into a larger (blank) frame, perform the Fourier transform, then clip out the central region you care about. In the Python code, this is handled via the variable psf_scale. In IDL, it is somewhat more manually set (see code snippet below). It is easy to get confused about the pixel scale through these manipulations. If the scaling factor is α, then the final image spans an angle (in radians) of Npixλ/αD. So for example, dealing with the 3.5 meter scope, 256 pixels, 532 nm, and a padding factor α=3 yields a frame 13 microradioans, or 2.67 arcseconds across.
Two codes, zernike.pro and wavefront.pro get you most of the way there. The wavefront code calls the zernike code. In order to turn the wavefront into a PSF, use code like the following (for hard-coded α=6):npix = 256
The first block makes a circular aperture (unobstructed). The second block defines a larger complex array (this is where the α=6 implicitly enters) and pastes the center with the aperture-limited complex phase, then takes the FFT and shifts and chops accordingly. The im output may then be written as a fits file, if desired.
You are to use the code from either platform to understand something about wavefronts, seeing, etc. Whatever you do, you will need to describe your parameters like D, λ, r0, etc. (and context, in some cases). Report the peak-to-peak wavefront error, in microns (will need to assume some wavelength).
Pick some topic to explore that involves configuring and running the code to learn something of interest to you. Possible topics might look like: