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
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
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
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
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
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
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