示例#1
0
    def test_scalar_integer_bad_var(self):
        """
        Fail on invalid variable type.

        Type: scalar integer
        """
        for v0 in [1.0, 1j, (1., ), [5, 5], 'si']:
            with self.assertRaises(TestCheckException):
                check.scalar_integer(v0, 'si', TestCheckException)
                pass
            pass
        pass
示例#2
0
    def test_scalar_integer_good(self):
        """
        Verify checker works correctly for valid input.

        Type: scalar integer
        """
        for j in [-2, -1, 0, 1, 2]:
            try:
                check.scalar_integer(j, 'si', TestCheckException)
            except check.CheckException:
                self.fail('scalar_integer failed on valid input')
            pass
        pass
示例#3
0
def relay(E_in, Nrelay, centering='pixel'):
    """
    Perform re-imaging of the input E-field through optical relays.
    
    Propagate a field through Nrelay optical relays, without any intermediate
    mask multiplications. This results in a 180-degree rotation of the array 
    for each optical relay. Correct centering of the array must be maintained.

    Parameters
    ----------
    E_in : array_like
        Input electric field
    Nrelay: int
        Number of times to relay (and rotate by 180 degrees)
    centering : string
        Whether the input field is pixel-centered or inter-pixel-centered. If
        the array is pixel-centered, the output is shifted by 1 pixel in both
        axes after an odd number of relays.

    Returns
    -------
    E_out : array_like
        The output E-field. Same as the input E-field but rotated by 180
        degrees times the number of optical relays.

    """
    if centering not in _VALID_CENTERING:
        raise ValueError(_CENTERING_ERR)
    check.twoD_array(E_in, 'E_in', TypeError)
    check.scalar_integer(Nrelay, 'Nrelay', TypeError)

    #--Only rotate if odd number of 180-degree rotations. If even, no change.
    if(np.mod(Nrelay,2)==1):
        # Reverse and scale input to account for propagation
        E_out = E_in[::-1, ::-1]  
        if centering == 'pixel':
            # Move the DC pixel back to the right place
            E_out = np.roll(E_out, (1, 1), axis=(0, 1))  
    else:
        E_out = E_in
        
    return E_out
示例#4
0
 def test_scalar_integer_bad_vexc(self):
     """Fail on input vexc not an Exception."""
     with self.assertRaises(check.CheckException):
         check.scalar_integer(1, 'si', 'TestCheckException')
         pass
     pass
示例#5
0
 def test_scalar_integer_bad_vname(self):
     """Fail on invalid input name for user output."""
     with self.assertRaises(check.CheckException):
         check.scalar_integer(1, (1, ), TestCheckException)
         pass
     pass
示例#6
0
def mft_p2v2p(pupilPre, charge, beamRadius, inVal, outVal):
    """
    Propagate from the pupil plane before a vortex FPM to pupil plane after it.
    
    Compute a radial Tukey window for propagating through a vortex coroangraph.

    Parameters
    ----------
    pupilPre : array_like
        2-D E-field at pupil plane before the vortex focal plane mask
    charge : int, float
        Charge of the vortex mask
    beamRadius : float
        Beam radius at pupil plane. Units of pixels.
    inVal : float
        Ask Gary
    outVal : float
        Ask Gary
        
    Returns
    -------
    pupilPost : array_like
        2-D E-field at pupil plane after the vortex focal plane mask

    """
    check.twoD_array(pupilPre, 'pupilPre', TypeError)
    check.scalar_integer(charge, 'charge', TypeError)
    check.real_positive_scalar(beamRadius, 'beamRadius', TypeError)
    check.real_positive_scalar(inVal, 'inVal', TypeError)
    check.real_positive_scalar(outVal, 'outVal', TypeError)
    
    # showPlots2debug = False 

    D = 2.0*beamRadius
    lambdaOverD = 4. # samples per lambda/D
    
    NA = pupilPre.shape[1]
    NB = util.ceil_even(lambdaOverD*D)
    
    # [X,Y] = np.meshgrid(np.arange(-NB/2., NB/2., dtype=float),np.arange(-NB/2., NB/2., dtype=float))
    # [RHO,THETA] = util.cart2pol(Y,X)
    RHO = util.radial_grid(np.arange(-NB/2., NB/2., dtype=float))
   
    windowKnee = 1.-inVal/outVal
    
    windowMask1 = gen_tukey_for_vortex(2*outVal*lambdaOverD, RHO, windowKnee)
    windowMask2 = gen_tukey_for_vortex(NB, RHO, windowKnee)

    # DFT vectors 
    x = np.arange(-NA/2,NA/2,dtype=float)/D   #(-NA/2:NA/2-1)/D
    u1 = np.arange(-NB/2,NB/2,dtype=float)/lambdaOverD #(-NB/2:NB/2-1)/lambdaOverD
    u2 = np.arange(-NB/2,NB/2,dtype=float)*2*outVal/NB # (-NB/2:NB/2-1)*2*outVal/N
    
    FPM = falco_gen_vortex_mask(charge, NB)

    #if showPlots2debug; figure;imagesc(abs(pupilPre));axis image;colorbar; title('pupil'); end;

    ## Low-sampled DFT of entire region

    FP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(u1,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u1))
    #if showPlots2debug; figure;imagesc(log10(abs(FP1).^2));axis image;colorbar; title('Large scale DFT'); end;

    LP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(x,u1)) @ (FP1*FPM*(1-windowMask1)) @ np.exp(-1j*2*np.pi*np.outer(u1,x))
    #if showPlots2debug; figure;imagesc(abs(FP1.*(1-windowMask1)));axis image;colorbar; title('Large scale DFT (windowed)'); end;
    
    ## Fine sampled DFT of innter region
    FP2 = 2*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(u2,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u2))
    #if showPlots2debug; figure;imagesc(log10(abs(FP2).^2));axis image;colorbar; title('Fine sampled DFT'); end;
    FPM = falco_gen_vortex_mask(charge, NB)
    LP2 = 2.0*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(x,u2)) @ (FP2*FPM*windowMask2) @ np.exp(-1j*2*np.pi*np.outer(u2,x))       
    #if showPlots2debug; figure;imagesc(abs(FP2.*windowMask2));axis image;colorbar; title('Fine sampled DFT (windowed)'); end;
    pupilPost = LP1 + LP2;
    #if showPlots2debug; figure;imagesc(abs(pupilPost));axis image;colorbar; title('Lyot plane'); end;

    return pupilPost
示例#7
0
def calc_complex_occulter(lam, aoi, t_Ti, t_Ni_vec, t_PMGI_vec,
                                 d0, pol, flagOPD=False, SUBSTRATE='FS'):
    """
    Calculate the thin-film complex transmission and reflectance.
    
    Calculates the thin-film complex transmission and reflectance for the
    provided combinations of metal and dielectric thicknesses and list of
    wavelengths.

    Parameters
    ----------
    lam : float
        Wavelength in meters.
    aoi : flaot
        Angle of incidence in degrees.
    t_Ti : float
        Titanium thickness in meters. Titanium goes only between
        fused silica and nickel layers.
    t_Ni_vec : array_like
        1-D array of nickel thicknesses in meters. Nickel goes between
        titanium and PMGI layers.
    t_PMGI_vec : array_like
        1-D array of PMGI thicknesses in meters.
    d0 : float
        Reference height for all phase offsets. Must be larger than the stack
        of materials, not including the substrate. Units of meters.
    pol : {0, 1, 2}
        Polarization state to compute values for.
        0 for TE(s) polarization,
        1 for TM(p) polarization,
        2 for mean of s and p polarizations
    flagOPD : bool, optional
        Flag to use the OPD convention. The default is False.
    SUBSTRATE : str, optional
        Material to use as the substrate. The default is 'FS'.

    Returns
    -------
    tCoef : numpy ndarray
        2-D array of complex transmission amplitude values.
    rCoef : numpy ndarray
        2-D array of complex reflection amplitude values.
    """
    real_positive_scalar(lam, 'lam', TypeError)
    real_nonnegative_scalar(aoi, 'theta', TypeError)
    real_nonnegative_scalar(t_Ti, 't_Ti', TypeError)
    oneD_array(t_Ni_vec, 't_Ni_vec', ValueError)
    oneD_array(t_PMGI_vec, 't_PMGI_vec', ValueError)
    # if len(t_Ti) != len(t_Ni_vec) or len(t_Ni_vec) != len(t_PMGI_vec):
    #     raise ValueError('Ti, Ni, and PMGI thickness vectors must all ' +
    #                      'have same length.')
    scalar_integer(pol, 'pol', TypeError)
    
    lam_nm = lam * 1.0e9  # m --> nm
    lam_um = lam * 1.0e6  # m --> microns
    lam_um2 = lam_um * lam_um
    theta = aoi * (np.pi/180.)  # deg --> rad
    
    # Define Material Properties
    # ---------------------------------------------
    # Substrate properties
    if SUBSTRATE.upper() in ('FS', 'FUSEDSILICA'):
        A1 = 0.68374049400
        A2 = 0.42032361300
        A3 = 0.58502748000
        B1 = 0.00460352869
        B2 = 0.01339688560
        B3 = 64.49327320000
        n_substrate = np.sqrt(1 + A1*lam_um2/(lam_um2 - B1) +
                           A2*lam_um2/(lam_um2 - B2) +
                           A3*lam_um2/(lam_um2 - B3))

    elif SUBSTRATE.upper() in ('N-BK7', 'NBK7', 'BK7'):
        B1 = 1.03961212
        B2 = 0.231792344
        B3 = 1.01046945
        C1 = 0.00600069867
        C2 = 0.0200179144
        C3 = 103.560653
        n_substrate = np.sqrt(1 + (B1*lam_um2/(lam_um2 - C1)) +
                              (B2*lam_um2/(lam_um2 - C2)) +
                              (B3*lam_um2/(lam_um2 - C3)))
    
    # Dielectric properties
    npmgi = 1.524 + 5.176e-03/lam_um**2 + 2.105e-4/lam_um**4
    Ndiel = len(t_PMGI_vec)
    
    # Metal layer properties
    # Titanium base layer under the nickel
    Nmetal = len(t_Ni_vec)
    t_Ti_vec = t_Ti * np.ones(Nmetal)
    t_Ti_vec[np.asarray(t_Ni_vec) < 1e-10] = 0  # no Ti where no Ni
    # from D Moody
    titanium = np.array([
                        [397, 2.08, 2.95],
                        [413, 2.14, 2.98],
                        [431, 2.21, 3.01],
                        [451, 2.27, 3.04],
                        [471, 2.3, 3.1],
                        [496, 2.36, 3.19],
                        [521, 2.44, 3.2],
                        [549, 2.54, 3.43],
                        [582, 2.6, 3.58],
                        [617, 2.67, 3.74],
                        [659, 2.76, 3.84],
                        [704, 2.86, 3.96],
                        [756, 3.00, 4.01],
                        [821, 3.21, 4.01],
                        [892, 3.29, 3.96],
                        [984, 3.35, 3.97],
                        [1088, 3.5, 4.02],
                        [1216, 3.62, 4.15]
                        ])
    lam_ti = titanium[:, 0]  # nm
    n_ti = titanium[:, 1]
    k_ti = titanium[:, 2]
    nti = np.interp(lam_nm, lam_ti, n_ti)
    kti = np.interp(lam_nm, lam_ti, k_ti)
    
    # Nickel
    localpath = os.path.dirname(os.path.abspath(__file__))
    fnNickel = os.path.join(localpath, 'data',
                            'nickel_data_from_Palik_via_Bala_wvlNM_n_k.txt')
    vnickel = np.loadtxt(fnNickel, delimiter="\t", unpack=False, comments="#")
    lam_nickel = vnickel[:, 0]  # nm
    n_nickel = vnickel[:, 1]
    k_nickel = vnickel[:, 2]
    nnickel = np.interp(lam_nm, lam_nickel, n_nickel)
    knickel = np.interp(lam_nm, lam_nickel, k_nickel)
    
    # Compute the complex transmission
    # tCoef = np.zeros((Nmetal, ), dtype=complex)  # initialize
    # rCoef = np.zeros((Nmetal, ), dtype=complex)  # initialize

    # for ii in range(Nmetal):
    #     dni = t_Ni_vec[ii]
    #     dti = t_Ti_vec[ii]
    #     dpm = t_PMGI_vec[ii]
        
    #     nvec = np.array([1, 1, npmgi, nnickel-1j*knickel, nti-1j*kti,
    #                      n_substrate], dtype=complex)
    #     dvec = np.array([d0-dpm-dni-dti, dpm, dni, dti])
        
    #     # Choose polarization
    #     if(pol == 2):  # Mean of the two
    #         [dummy1, dummy2, rr0, tt0] = solver(nvec, dvec, theta,
    #                                             lam, False)
    #         [dummy1, dummy2, rr1, tt1] = solver(nvec, dvec, theta,
    #                                             lam, True)
    #         rr = (rr0+rr1)/2.
    #         tt = (tt0+tt1)/2.
    #     elif(pol == 0 or pol == 1):
    #         [dumm1, dummy2, rr, tt] = solver(nvec, dvec, theta, lam,
    #                                          bool(pol))
    #     else:
    #         raise ValueError('Wrong input value for polarization.')

    #     # Choose phase convention
    #     if not flagOPD:
    #         tCoef[ii] = np.conj(tt)  # Complex field transmission coef
    #         rCoef[ii] = np.conj(rr)  # Complex field reflection coef
    #     else:  # OPD phase convention
    #         tCoef[ii] = tt  # Complex field transmission coeffient
    #         rCoef[ii] = rr  # Complex field reflection coeffient
                
    # Compute the complex transmission
    tCoef = np.zeros((Ndiel, Nmetal), dtype=complex)  # initialize
    rCoef = np.zeros((Ndiel, Nmetal), dtype=complex)  # initialize
    for jj in range(Ndiel):
        dpm = t_PMGI_vec[jj]
        
        for ii in range(Nmetal):
            dni = t_Ni_vec[ii]
            dti = t_Ti_vec[ii]
            
            nvec = np.array([1, 1, npmgi, nnickel-1j*knickel, nti-1j*kti,
                              n_substrate], dtype=complex)
            dvec = np.array([d0-dpm-dni-dti, dpm, dni, dti])
            
            # Choose polarization
            if(pol == 2):  # Mean of the two
                [dummy1, dummy2, rr0, tt0] = solver(nvec, dvec, theta,
                                                    lam, False)
                [dummy1, dummy2, rr1, tt1] = solver(nvec, dvec, theta,
                                                    lam, True)
                rr = (rr0+rr1)/2.
                tt = (tt0+tt1)/2.
            elif(pol == 0 or pol == 1):
                [dumm1, dummy2, rr, tt] = solver(nvec, dvec, theta, lam,
                                                  bool(pol))
            else:
                raise ValueError('Wrong input value for polarization.')

            # Choose phase convention
            if not flagOPD:
                tCoef[jj, ii] = np.conj(tt)  # Complex field transmission coef
                rCoef[jj, ii] = np.conj(rr)  # Complex field reflection coef
            else:  # OPD phase convention
                tCoef[jj, ii] = tt  # Complex field transmission coeffient
                rCoef[jj, ii] = rr  # Complex field reflection coeffient
    
    return tCoef, rCoef