def _field_Fresnel(z, field, dx, lam): """ Separated the "math" logic out so that only standard and numpy types are used. Parameters ---------- z : float Propagation distance. field : ndarray 2d complex numpy array (NxN) of the field. dx : float In units of sim (usually [m]), spacing of grid points in field. lam : float Wavelength lambda in sim units (usually [m]). Returns ------- ndarray (2d, NxN, complex) The propagated field. """ """ ************************************************************* Major differences to Cpp based LP version: - dx =siz/N instead of dx=siz/(N-1), more consistent with physics and rest of LP package - fftw DLL uses no normalization, numpy uses 1/N on ifft -> omitted factor of 1/(2*N)**2 in final calc before return - bug in Cpp version: did not touch top row/col, now we extract one more row/col to fill entire field. No errors noticed with the new method so far ************************************************************* """ tictoc.tic() N = field.shape[0] #assert square legacy = True #switch on to numerically compare oldLP/new results if legacy: kz = 2. * 3.141592654 / lam * z siz = N * dx dx = siz / (N - 1) #like old Cpp code, even though unlogical else: kz = 2 * _np.pi / lam * z cokz = _np.cos(kz) sikz = _np.sin(kz) No2 = int(N / 2) #"N over 2" """The following section contains a lot of uses which boil down to 2*No2. For even N, this is N. For odd N, this is NOT redundant: 2*No2 is N-1 for odd N, therefore sampling an even subset of the field instead of the whole field. Necessary for symmetry of first step involving Fresnel integral calc. """ if _using_pyfftw: in_outF = _pyfftw.zeros_aligned((2 * N, 2 * N), dtype=complex) in_outK = _pyfftw.zeros_aligned((2 * N, 2 * N), dtype=complex) else: in_outF = _np.zeros((2 * N, 2 * N), dtype=complex) in_outK = _np.zeros((2 * N, 2 * N), dtype=complex) """Our grid is zero-centered, i.e. the 0 coordiante (beam axis) is not at field[0,0], but field[No2, No2]. The FFT however is implemented such that the frequency 0 will be the first element of the output array, and it also expects the input to have the 0 in the corner. For the correct handling, an fftshift is necessary before *and* after the FFT/IFFT: X = fftshift(fft(ifftshift(x))) # correct magnitude and phase x = fftshift(ifft(ifftshift(X))) # correct magnitude and phase X = fftshift(fft(x)) # correct magnitude but wrong phase ! x = fftshift(ifft(X)) # correct magnitude but wrong phase ! A numerically faster way to achieve the same result is by multiplying with an alternating phase factor as done below. Speed for N=2000 was ~0.4s for a double fftshift and ~0.1s for a double phase multiplication -> use the phase factor approach (iiij). """ # Create the sign-flip pattern for largest use case and # reference smaller grids with a view to the same data for # memory saving. ii2N = _np.ones((2 * N), dtype=float) ii2N[1::2] = -1 #alternating pattern +,-,+,-,+,-,... iiij2N = _np.outer(ii2N, ii2N) iiij2No2 = iiij2N[:2 * No2, :2 * No2] #slice to size used below iiijN = iiij2N[:N, :N] RR = _np.sqrt(1 / (2 * lam * z)) * dx * 2 io = _np.arange(0, (2 * No2) + 1) #add one extra to stride fresnel integrals R1 = RR * (io - No2) fs, fc = _fresnel(R1) fss = _np.outer(fs, fs) # out[i, j] = a[i] * b[j] fsc = _np.outer(fs, fc) fcs = _np.outer(fc, fs) fcc = _np.outer(fc, fc) """Old notation (0.26-0.33s): temp_re = (a + b + c - d + ...) # numpy func add takes 2 operands A, B only # -> each operation needs to create a new temporary array, i.e. # ((((a+b)+c)+d)+...) # since python does not optimize to += here (at least is seems) New notation (0.14-0.16s): temp_re = (a + b) #operation with 2 operands temp_re += c temp_re -= d ... Wrong notation: temp_re = a #copy reference to array a temp_re += b ... # changing `a` in-place, re-using `a` will give corrupted # result """ temp_re = ( fsc[1:, 1:] #s[i+1]c[j+1] + fcs[1:, 1:]) #c[+1]s[+1] temp_re -= fsc[:-1, 1:] #-scp [p=+1, without letter =+0] temp_re -= fcs[:-1, 1:] #-csp temp_re -= fsc[1:, :-1] #-spc temp_re -= fcs[1:, :-1] #-cps temp_re += fsc[:-1, :-1] #sc temp_re += fcs[:-1, :-1] #cs temp_im = ( -fcc[1:, 1:] #-cpcp + fss[1:, 1:]) # +spsp temp_im += fcc[:-1, 1:] # +ccp temp_im -= fss[:-1, 1:] # -ssp temp_im += fcc[1:, :-1] # +cpc temp_im -= fss[1:, :-1] # -sps temp_im -= fcc[:-1, :-1] # -cc temp_im += fss[:-1, :-1] # +ss temp_K = 1j * temp_im # a * b creates copy and casts to complex temp_K += temp_re temp_K *= iiij2No2 temp_K *= 0.5 in_outK[(N - No2):(N + No2), (N - No2):(N + No2)] = temp_K in_outF[(N-No2):(N+No2), (N-No2):(N+No2)] \ = field[(N-2*No2):N,(N-2*No2):N] #cutting off field if N odd (!) in_outF[(N - No2):(N + No2), (N - No2):(N + No2)] *= iiij2No2 tictoc.tic() in_outK = _fft2(in_outK, **_fftargs) in_outF = _fft2(in_outF, **_fftargs) t_fft1 = tictoc.toc() in_outF *= in_outK in_outF *= iiij2N tictoc.tic() in_outF = _ifft2(in_outF, **_fftargs) t_fft2 = tictoc.toc() #TODO check normalization if USE_PYFFTW Ftemp = (in_outF[No2:N + No2, No2:N + No2] - in_outF[No2 - 1:N + No2 - 1, No2:N + No2]) Ftemp += in_outF[No2 - 1:N + No2 - 1, No2 - 1:N + No2 - 1] Ftemp -= in_outF[No2:N + No2, No2 - 1:N + No2 - 1] comp = complex(cokz, sikz) Ftemp *= 0.25 * comp Ftemp *= iiijN field = Ftemp #reassign without data copy ttotal = tictoc.toc() t_fft = t_fft1 + t_fft2 t_outside = ttotal - t_fft debug_time = False if debug_time: print('Time total = fft + rest: {:.2f}={:.2f}+{:.2f}'.format( ttotal, t_fft, t_outside)) return field
def Forward(z, sizenew, Nnew, Fin): """ Fout = Forward(z, sizenew, Nnew, Fin) :ref:`Propagates the field using direct integration. <Forward>` Args:: z: propagation distance Fin: input field Returns:: Fout: output field (N x N square array of complex numbers). Example: :ref:`Diffraction from a circular aperture <Diffraction>` """ if z <= 0: raise ValueError('Forward does not support z<=0') Fout = Field.begin(sizenew, Fin.lam, Nnew) field_in = Fin.field field_out = Fout.field field_out[:, :] = 0.0 #default is ones, clear old_size = Fin.siz old_n = Fin.N new_size = sizenew #renaming to match cpp code new_n = Nnew on2 = int(old_n / 2) nn2 = int(new_n / 2) #read "new n over 2" dx_new = new_size / (new_n - 1) dx_old = old_size / (old_n - 1) #TODO again, dx seems better defined without -1, check this R22 = _np.sqrt(1 / (2 * Fin.lam * z)) X_new = _np.arange(-nn2, new_n - nn2) * dx_new Y_new = X_new #same X_old = _np.arange(-on2, old_n - on2) * dx_old Y_old = X_old #same for i_new in range(new_n): x_new = X_new[i_new] P1 = R22 * (2 * (X_old - x_new) + dx_old) P3 = R22 * (2 * (X_old - x_new) - dx_old) Fs1, Fc1 = _fresnel(P1) Fs3, Fc3 = _fresnel(P3) for j_new in range(new_n): y_new = Y_new[j_new] P2 = R22 * (2 * (Y_old - y_new) - dx_old) P4 = R22 * (2 * (Y_old - y_new) + dx_old) Fs2, Fc2 = _fresnel(P2) Fs4, Fc4 = _fresnel(P4) C4C1 = _np.outer(Fc4, Fc1) #out[i, j] = a[i] * b[j] C2S3 = _np.outer(Fc2, Fs3) #-> out[j,i] = a[j]*b[i] here C4S1 = _np.outer(Fc4, Fs1) S4C1 = _np.outer(Fs4, Fc1) S2C3 = _np.outer(Fs2, Fc3) C2S1 = _np.outer(Fc2, Fs1) S4C3 = _np.outer(Fs4, Fc3) S2C1 = _np.outer(Fs2, Fc1) C4S3 = _np.outer(Fc4, Fs3) S2S3 = _np.outer(Fs2, Fs3) S2S1 = _np.outer(Fs2, Fs1) C2C3 = _np.outer(Fc2, Fc3) S4S1 = _np.outer(Fs4, Fs1) C4C3 = _np.outer(Fc4, Fc3) C4C1 = _np.outer(Fc4, Fc1) S4S3 = _np.outer(Fs4, Fs3) C2C1 = _np.outer(Fc2, Fc1) Fr = 0.5 * field_in.real Fi = 0.5 * field_in.imag Temp_c = ( Fr * (C2S3 + C4S1 + S4C1 + S2C3 - C2S1 - S4C3 - S2C1 - C4S3) + Fi * (-S2S3 + S2S1 + C2C3 - S4S1 - C4C3 + C4C1 + S4S3 - C2C1) + 1j * Fr * (-C4C1 + S2S3 + C4C3 - S4S3 + C2C1 - S2S1 + S4S1 - C2C3) + 1j * Fi * (C2S3 + S2C3 + C4S1 + S4C1 - C4S3 - S4C3 - C2S1 - S2C1)) field_out[j_new, i_new] = Temp_c.sum() #complex elementwise sum return Fout
def Forward(Fin, z, sizenew, Nnew): """ *Propagates the field using direct integration.* :param Fin: input field :type Fin: Field :param z: propagation distance :type z: int, float :return: output field (N x N square array of complex numbers). :rtype: `LightPipes.field.Field` :Example: >>> F = Forward(F, 20*cm, 10*mm, 20) # propagates the field 20 cm, for a new grid size of 10 mm and a new grid dimension 20 .. seealso:: * :ref:`Manual: Direct integration. <Direct integration.>` * :ref:`Manual: Splitting and mixing beams.<Splitting and mixing beams.>` (figure 10.) """ if z <= 0: raise ValueError('Forward does not support z<=0') Fout = Field.begin(sizenew, Fin.lam, Nnew) field_in = Fin.field field_out = Fout.field field_out[:, :] = 0.0 #default is ones, clear old_size = Fin.siz old_n = Fin.N new_size = sizenew #renaming to match cpp code new_n = Nnew on2 = int(old_n / 2) nn2 = int(new_n / 2) #read "new n over 2" dx_new = new_size / (new_n - 1) dx_old = old_size / (old_n - 1) #TODO again, dx seems better defined without -1, check this R22 = _np.sqrt(1 / (2 * Fin.lam * z)) X_new = _np.arange(-nn2, new_n - nn2) * dx_new Y_new = X_new #same X_old = _np.arange(-on2, old_n - on2) * dx_old Y_old = X_old #same for i_new in range(new_n): x_new = X_new[i_new] P1 = R22 * (2 * (X_old - x_new) + dx_old) P3 = R22 * (2 * (X_old - x_new) - dx_old) Fs1, Fc1 = _fresnel(P1) Fs3, Fc3 = _fresnel(P3) for j_new in range(new_n): y_new = Y_new[j_new] P2 = R22 * (2 * (Y_old - y_new) - dx_old) P4 = R22 * (2 * (Y_old - y_new) + dx_old) Fs2, Fc2 = _fresnel(P2) Fs4, Fc4 = _fresnel(P4) C4C1 = _np.outer(Fc4, Fc1) #out[i, j] = a[i] * b[j] C2S3 = _np.outer(Fc2, Fs3) #-> out[j,i] = a[j]*b[i] here C4S1 = _np.outer(Fc4, Fs1) S4C1 = _np.outer(Fs4, Fc1) S2C3 = _np.outer(Fs2, Fc3) C2S1 = _np.outer(Fc2, Fs1) S4C3 = _np.outer(Fs4, Fc3) S2C1 = _np.outer(Fs2, Fc1) C4S3 = _np.outer(Fc4, Fs3) S2S3 = _np.outer(Fs2, Fs3) S2S1 = _np.outer(Fs2, Fs1) C2C3 = _np.outer(Fc2, Fc3) S4S1 = _np.outer(Fs4, Fs1) C4C3 = _np.outer(Fc4, Fc3) C4C1 = _np.outer(Fc4, Fc1) S4S3 = _np.outer(Fs4, Fs3) C2C1 = _np.outer(Fc2, Fc1) Fr = 0.5 * field_in.real Fi = 0.5 * field_in.imag Temp_c = ( Fr * (C2S3 + C4S1 + S4C1 + S2C3 - C2S1 - S4C3 - S2C1 - C4S3) + Fi * (-S2S3 + S2S1 + C2C3 - S4S1 - C4C3 + C4C1 + S4S3 - C2C1) + 1j * Fr * (-C4C1 + S2S3 + C4C3 - S4S3 + C2C1 - S2S1 + S4S1 - C2C3) + 1j * Fi * (C2S3 + S2C3 + C4S1 + S4C1 - C4S3 - S4C3 - C2S1 - S2C1)) field_out[j_new, i_new] = Temp_c.sum() #complex elementwise sum return Fout
def fresnel(x): return tuple(reversed(_fresnel(x)))
def fresnel(t: np.ndarray) -> np.ndarray: y, x = _fresnel(t) return np.stack([x, y], axis=-1)