Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
def fresnel(x):
    return tuple(reversed(_fresnel(x)))
Esempio n. 5
0
def fresnel(t: np.ndarray) -> np.ndarray:
    y, x = _fresnel(t)
    return np.stack([x, y], axis=-1)