def test_real_positive_scalar_good(self): """ Verify checker works correctly for valid input. Type: real positive scalar """ try: check.real_positive_scalar(1, 'rps', TestCheckException) except check.CheckException: self.fail('real_positive_scalar failed on valid input') pass
def test_real_positive_scalar_bad_var(self): """ Fail on invalid variable type. Type: real positive scalar """ for v0 in [-1, 1j, (1., ), [5, 5], 'v0']: with self.assertRaises(TestCheckException): check.real_positive_scalar(v0, 'rps', TestCheckException) pass pass pass
def mft_f2p(E_foc, fl, wavelength, dxi, deta, dx, N, centering='pixel'): """ Propagate a field from a focal plane to a pupil plane, using a matrix-multiply DFT. Parameters ---------- E_foc : array_like Electric field array in focal plane fl : float Focal length of Fourier transforming lens wavelength : float Propagation wavelength dxi : float Step size along horizontal axis of focal plane deta : float Step size along vertical axis of focal plane dx : float Step size along either axis of focal plane. The vertical and horizontal step sizes are assumed to be equal. N : int Number of datapoints along each side of the pupil-plane (output) array centering : string Whether the input and output arrays are pixel-centered or inter-pixel-centered. Possible values: 'pixel', 'interpixel' Returns ------- array_like Field in pupil plane, after propagating through Fourier transforming lens """ if centering not in _VALID_CENTERING: raise ValueError(_CENTERING_ERR) check.twoD_array(E_foc, 'E_foc', TypeError) check.real_scalar(fl, 'fl', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_positive_scalar(dxi, 'dxi', TypeError) check.real_positive_scalar(deta, 'deta', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.positive_scalar_integer(N, 'N', TypeError) Neta, Nxi = E_foc.shape dy = dx # Assume equal sample spacing along both directions # Focal-plane coordinates xi = util.create_axis(Nxi, dxi, centering=centering)[:, None] # Broadcast to column vector eta = util.create_axis(Neta, dxi, centering=centering)[None, :] # Broadcast to row vector # Pupil-plane coordinates x = util.create_axis(N, dx, centering=centering)[None, :] # Broadcast to row vector y = x.T # Column vector # Fourier transform matrices pre = np.exp(-2 * np.pi * 1j * (y * eta) / (wavelength * fl)) post = np.exp(-2 * np.pi * 1j * (xi * x) / (wavelength * fl)) # Constant scaling factor in front of Fourier transform scaling = np.sqrt(dx * dy * dxi * deta) / (1 * wavelength * fl) return scaling * np.linalg.multi_dot([pre, E_foc, post])
def ptp(E_in, full_width, wavelength, dz): """ Propagate an electric field array using the angular spectrum technique. Parameters ---------- E_in : array_like Square (i.e. NxN) input array. full_width : float The width along each side of the array [meters] wavelength : float Propagation wavelength [meters] dz : float Axial propagation distance [meters] Returns ------- array_like Field after propagating over distance dz. """ check.twoD_array(E_in, 'E_in', TypeError) check.real_positive_scalar(full_width, 'full_width', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_scalar(dz, 'dz', TypeError) M, N = E_in.shape dx = full_width / N N_critical = int(np.floor(wavelength * np.abs(dz) / (dx ** 2))) # Critical sampling if M != N: # Input array is not square raise ValueError('Input array is not square') elif N < N_critical: log.warning( ''' Input array is undersampled. Minimum required samples: {} Actual: {} '''.format(N_critical, N)) fx = np.arange(-N // 2, N // 2) / full_width rho = util.radial_grid(fx) # Spatial frequency coordinate grid kernel = np.fft.fftshift(np.exp(-1j * np.pi * wavelength * dz * (rho ** 2))) intermediate = np.fft.fftn(np.fft.fftshift(E_in)) return np.fft.ifftshift(np.fft.ifftn(kernel * intermediate))
def test_real_positive_scalar_bad_vexc(self): """Fail on input vexc not an Exception.""" with self.assertRaises(check.CheckException): check.real_positive_scalar(1, 'rps', 'TestCheckException') pass pass
def test_real_positive_scalar_bad_vname(self): """Fail on invalid input name for user output.""" with self.assertRaises(check.CheckException): check.real_positive_scalar(1, (1, ), TestCheckException) pass pass
def derotate_resize_surface(surfaceToFit, dx, Nact, dm_xc, dm_yc, spacing, **kwargs): """ Derotate and resize a DM surface to size and alignment of actuator grid. Does the order of operations in the reverse order of PROPER's prop_dm. Parameters ---------- surfaceToFit : numpy ndarray 2-D DM surface map to be fitted dx : float width of a pixel in meters Nact : int number of actuators across the DM array dm_xc, dm_yc : list or numpy ndarray The location of the optical axis (center of the wavefront) on the DM in actuator units (0 ro num_actuator-1). The center of the first actuator is (0.0, 0.0) spacing : float Spacing in meters between actuator centers (aka the pitch). Returns ------- gridDerotAtActRes : numpy ndarray Returns DM surface at same alignment and resolution as the DM actuator array. Other Parameters ---------------- XTILT, YTILT, ZTILT : float Specify the rotation of the DM surface with respect to the wavefront plane in degrees about the X, Y, Z axes, respectively, with the origin at the center of the wavefront. The DM surface is interpolated and orthographically projected onto the wavefront grid. The coordinate system assumes that the wavefront and initial DM surface are in the X,Y plane with a lower left origin with Z towards the observer. The rotations are left handed. The default rotation order is X, Y, then Z unless the /ZYX switch is set. XYZ or ZYX : bool Specifies the rotation order if two or more of XTILT, YTILT, or ZTILT are specified. The default is /XYZ for X, Y, then Z rotations. inf_fn : string specify a new influence function as a FITS file with the same header keywords as PROPER's default influence function. Needs these values in info.PrimaryData.Keywords: 'P2PDX_M' % pixel width x (m) 'P2PDY_M' % pixel width y (m) 'C2CDX_M' % actuator pitch x (m) 'C2CDY_M' % actuator pitch y (m) inf_sign : {+,-} specifies the sign (+/-) of the influence function. Given as an option because the default influence function file is positive, but positive DM actuator commands make a negative deformation for Xinetics and BMC DMs. Raises ------ ValueError: User cannot specify both ZYX and XYZ rotations. """ check.twoD_array(surfaceToFit, 'surfaceToFit', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.real_scalar(dm_xc, 'dm_xc', TypeError) check.real_scalar(dm_yc, 'dm_yc', TypeError) check.real_positive_scalar(spacing, 'spacing', TypeError) if "ZYX" in kwargs and "XYZ" in kwargs: raise ValueError('Error: Cannot specify both XYZ and ZYX rotation' + ' orders. Stopping') elif "ZYX" not in kwargs and 'XYZ' not in kwargs: XYZ = 1 # default is rotation around X, then Y, then Z # ZYX = 0 elif "ZYX" in kwargs: # ZYX = 1 XYZ = 0 elif "XYZ" in kwargs: XYZ = 1 # ZYX = 0 if "XTILT" in kwargs: xtilt = kwargs["XTILT"] else: xtilt = 0. if "YTILT" in kwargs: ytilt = kwargs["YTILT"] else: ytilt = 0. if "ZTILT" in kwargs: ztilt = kwargs["ZTILT"] else: ztilt = 0. dm_z = np.eye(Nact) if "inf_fn" in kwargs: inf_fn = kwargs["inf_fn"] else: inf_fn = "influence_dm5v2.fits" if "inf_sign" in kwargs: if(kwargs["inf_sign"] == '+'): sign_factor = 1. elif(kwargs["inf_sign"] == '-'): sign_factor = -1. else: sign_factor = 1. n = surfaceToFit.shape[0] dx_surf = dx # sampling of current surface in meters # Default influence function sampling is 0.1 mm, peak at (x,y)=(45,45) # Default influence function has shape = 1x91x91. Saving it as a 2D array # before continuing with processing dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") inf = proper.prop_fits_read(os.path.join(dir_path, inf_fn)) inf = sign_factor*np.squeeze(inf) s = inf.shape nx_inf = s[1] ny_inf = s[0] xc_inf = nx_inf // 2 yc_inf = ny_inf // 2 dx_inf = 0.1e-3 # influence function spacing in meters dx_dm_inf = 1.e-3 # spacing between DM actuators in meters assumed by influence function inf_mag = 10 dx_dm = spacing dx_inf = dx_inf * dx_dm / dx_dm_inf # Influence function sampling scaled # to specified DM actuator spacing dm_z_commanded = dm_z s = dm_z.shape nx_dm = s[1] ny_dm = s[0] # Create subsampled DM grid margin = 9 * inf_mag nx_grid = nx_dm * inf_mag + 2 * margin ny_grid = ny_dm * inf_mag + 2 * margin xoff_grid = margin + inf_mag/2 # pixel location of 1st actuator center in subsampled grid yoff_grid = xoff_grid dm_grid = np.zeros([ny_grid, nx_grid], dtype = np.float64) x = np.arange(nx_dm, dtype=int) * int(inf_mag) + int(xoff_grid) y = np.arange(ny_dm, dtype=int) * int(inf_mag) + int(yoff_grid) dm_grid[np.tile(np.vstack(y), (nx_dm,)), np.tile(x, (ny_dm, 1))] = dm_z_commanded dm_grid = ss.fftconvolve(dm_grid, inf, mode='same') # 3D rotate DM grid and project orthogonally onto wavefront xdim = int(np.round(np.sqrt(2) * nx_grid * dx_inf / dx_surf)) # grid dimensions (pix) projected onto wavefront ydim = int(np.round(np.sqrt(2) * ny_grid * dx_inf / dx_surf)) if xdim > n: xdim = n if ydim > n: ydim = n x = np.ones((ydim,1), dtype=int) * ((np.arange(xdim) - xdim // 2) * dx_surf) y = (np.ones((xdim,1), dtype=int) * ((np.arange(ydim) - ydim // 2) * dx_surf)).T a = xtilt * np.pi / 180 b = ytilt * np.pi / 180 g = ztilt * np.pi / 180 if XYZ: m = np.array([ [cos(b)*cos(g), -cos(b)*sin(g), sin(b), 0], [cos(a)*sin(g) + sin(a)*sin(b)*cos(g), cos(a)*cos(g)-sin(a)*sin(b)*sin(g), -sin(a)*cos(b), 0], [sin(a)*sin(g)-cos(a)*sin(b)*cos(g), sin(a)*cos(g)+cos(a)*sin(b)*sin(g), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) else: m = np.array([ [cos(b)*cos(g), cos(g)*sin(a)*sin(b)-cos(a)*sin(g), cos(a)*cos(g)*sin(b)+sin(a)*sin(g), 0], [cos(b)*sin(g), cos(a)*cos(g)+sin(a)*sin(b)*sin(g), -cos(g)*sin(a)+cos(a)*sin(b)*sin(g), 0], [-sin(b), cos(b)*sin(a), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) # Compute xdm0 and ydm0 for use in de-rotating the DM surface edge = np.array([[-1.0,-1.0,0.0,0.0], [1.0,-1.0,0.0,0.0], [1.0,1.0,0.0,0.0], [-1.0,1.0,0.0,0.0]]) new_xyz = edge #np.dot(edge, m) # determine backward projection for screen-raster-to-DM-surce computation dx_dxs = (new_xyz[0, 0] - new_xyz[1, 0]) / (edge[0, 0] - edge[1, 0]) dx_dys = (new_xyz[1, 0] - new_xyz[2, 0]) / (edge[1, 1] - edge[2, 1]) dy_dxs = (new_xyz[0, 1] - new_xyz[1, 1]) / (edge[0, 0] - edge[1, 0]) dy_dys = (new_xyz[1, 1] - new_xyz[2, 1]) / (edge[1, 1] - edge[2, 1]) xs = (x/dx_dxs - y*dx_dys/(dx_dxs*dy_dys) ) / ( 1 - dy_dxs*dx_dys/(dx_dxs*dy_dys)) ys = (y/dy_dys - x*dy_dxs/(dx_dxs*dy_dys) ) / ( 1 - dx_dys*dy_dxs/(dx_dxs*dy_dys)) xdm0 = (xs + dm_xc * dx_dm) / dx_inf + xoff_grid ydm0 = (ys + dm_yc * dx_dm) / dx_inf + yoff_grid ###### # Forward project a square # edge = np.array([[-1.0,-1.0,0.0,0.0], [1.0,-1.0,0.0,0.0], # [1.0,1.0,0.0,0.0], [-1.0,1.0,0.0,0.0]]) new_xyz = np.dot(edge, m) # determine backward projection for screen-raster-to-DM-surce computation dx_dxs = (new_xyz[0, 0] - new_xyz[1, 0]) / (edge[0, 0] - edge[1, 0]) dx_dys = (new_xyz[1, 0] - new_xyz[2, 0]) / (edge[1, 1] - edge[2, 1]) dy_dxs = (new_xyz[0, 1] - new_xyz[1, 1]) / (edge[0, 0] - edge[1, 0]) dy_dys = (new_xyz[1, 1] - new_xyz[2, 1]) / (edge[1, 1] - edge[2, 1]) xs = (x/dx_dxs - y*dx_dys/(dx_dxs*dy_dys)) / \ (1 - dy_dxs*dx_dys/(dx_dxs*dy_dys)) ys = (y/dy_dys - x*dy_dxs/(dx_dxs*dy_dys)) / \ (1 - dx_dys*dy_dxs/(dx_dxs*dy_dys)) xdm = (xs + dm_xc * dx_dm) / dx_inf + xoff_grid ydm = (ys + dm_yc * dx_dm) / dx_inf + yoff_grid # if proper.use_cubic_conv: # grid = proper.prop_cubic_conv(dm_grid.T, xdm, ydm, GRID = False) # grid = grid.reshape([xdm.shape[1], xdm.shape[0]]) # else: # grid = map_coordinates(dm_grid.T, [xdm, ydm], order=3, # mode="nearest", prefilter = True) # dm_grid = falco.util.pad_crop(surfaceToFit, xdm.shape[0]) # Derotate the DM surface dm_grid = falco.util.pad_crop(surfaceToFit, xdm.shape[0]) gridDerot = griddata((xdm.flatten(), ydm.flatten()), dm_grid.flatten(), (xdm0, ydm0), method='cubic', fill_value=0.) # gridDerot(isnan(gridDerot)) = 0 # Resize and decimate the DM surface to get it at the same size as the DM # actuator command array. # The result will be fed to fit_surf_to_act() for deconvolution with the # influence function. xOffsetInAct = ((Nact/2 - 1/2) - dm_xc) yOffsetInAct = ((Nact/2 - 1/2) - dm_yc) multipleOfCommandGrid = int(falco.util.ceil_odd(spacing/dx)) N1 = Nact*multipleOfCommandGrid N2 = dm_grid.shape[0] xs1 = np.linspace(-(N1-1)/2, (N1-1)/2, N1)/N1 # interpixel centered if(N2 % 2 == 0): xs2 = np.linspace(-N2/2, (N2/2)-1, N2)/N2*(N2*dx/(Nact*spacing)) else: xs2 = np.linspace(-(N2-1)/2, (N2-1)/2, N2)/N2*(N2*dx/(Nact*spacing)) interp_spline = RectBivariateSpline(xs2, xs2, gridDerot) gridDerotResize = interp_spline(xs1-xOffsetInAct/Nact, xs1-yOffsetInAct/Nact) xyOffset = int(np.floor(multipleOfCommandGrid/2.)) gridDerotAtActRes = gridDerotResize[xyOffset::multipleOfCommandGrid, xyOffset::multipleOfCommandGrid] # # plt.figure(11); plt.imshow(dm_grid); plt.colorbar(); plt.pause(0.1) # plt.figure(12); plt.imshow(gridDerot); plt.colorbar(); plt.pause(0.1) # plt.figure(13); plt.imshow(gridDerotResize); plt.colorbar(); plt.pause(0.1) # plt.figure(14); plt.imshow(gridDerotAtActRes); plt.colorbar(); plt.pause(0.1) # plt.figure(15); plt.imshow(gridDerot-gridDerot[::-1,::-1]); plt.colorbar(); plt.pause(0.1) # plt.figure(16); plt.imshow(gridDerotResize-gridDerotResize[::-1,::-1]); plt.colorbar(); plt.pause(0.1) return gridDerotAtActRes
def gen_poke_cube(dm, mp, dx_dm, **kwargs): """ Compute the datacube of each influence function. Influence functions are cropped down or padded up to the best size for angular spectrum propagation. Parameters ---------- dm : ModelParameters Structure containing parameter values for the DM mp: falco.config.ModelParameters Structure of model parameters dx_dm : float Pixel width [meters] at the DM plane Other Parameters ---------------- NOCUBE : bool Switch that tells function not to compute the datacube of influence functions. Returns ------- None modifies structure "dm" by reference """ if type(mp) is not falco.config.ModelParameters: raise TypeError('Input "mp" must be of type ModelParameters') check.real_positive_scalar(dx_dm, 'dx_dm', TypeError) if "NOCUBE" in kwargs and kwargs["NOCUBE"]: flagGenCube = False else: flagGenCube = True # Define this flag if it doesn't exist in the older code for square actuator arrays only if not hasattr(dm, 'flag_hex_array'): dm.flag_hex_array = False # Set the order of operations XYZ = True if(hasattr(dm, 'flagZYX')): if(dm.flagZYX): XYZ = False # Compute sampling of the pupil. Assume that it is square. dm.dx_dm = dx_dm dm.dx = dx_dm # Default to being centered on a pixel if not specified if not(hasattr(dm, 'centering')): dm.centering = 'pixel' # Compute coordinates of original influence function Ninf0 = dm.inf0.shape[0] # Number of points across inf func at native res x_inf0 = np.linspace(-(Ninf0-1)/2., (Ninf0-1)/2., Ninf0)*dm.dx_inf0 # True for even- or odd-sized influence function maps as long as they are # centered on the array. [Xinf0, Yinf0] = np.meshgrid(x_inf0, x_inf0) # Number of points across the DM surface at native inf func resolution Ndm0 = falco.util.ceil_even(Ninf0 + (dm.Nact - 1)*(dm.dm_spacing/dm.dx_inf0)) # Number of points across the (un-rotated) DM surface at new, desired res. dm.NdmMin = falco.util.ceil_even(Ndm0*(dm.dx_inf0/dm.dx))+2. # Number of points across the array to fully contain the DM surface at new # desired resolution and z-rotation angle. dm.Ndm = int(falco.util.ceil_even((abs(np.array([np.sqrt(2.)*cos(radians(45.-dm.zrot)), np.sqrt(2.)*sin(radians(45.-dm.zrot))])).max())*Ndm0*(dm.dx_inf0/dm.dx))+2) # Compute list of initial actuator center coordinates (in actutor widths). if(dm.flag_hex_array): # Hexagonal, hex-packed grid raise ValueError('flag_hex_array option not implemented yet.') # Nrings = dm.Nrings; # x_vec = []; # y_vec = []; # % row number (rowNum) is 1 for the center row and 2 is above it, etc. # % Nacross is the total number of segments across that row # for rowNum = 1:Nrings # Nacross = 2*Nrings - rowNum; % Number of actuators across at that row (for hex tiling in a hex shape) # yval = sqrt(3)/2*(rowNum-1); # bx = Nrings - (rowNum+1)/2; % x offset from origin # # xs = (0:Nacross-1).' - bx; % x values are 1 apart # ys = yval*ones(Nacross,1); % same y-value for the entire row # # if(rowNum==1) # x_vec = [x_vec;xs]; # y_vec = [y_vec;ys]; # else # x_vec = [x_vec;xs;xs]; # y_vec = [y_vec;ys;-ys]; % rows +/-n have +/- y coordinates # end # end else: # Square grid [actuator widths] [dm.Xact, dm.Yact] = np.meshgrid(np.arange(dm.Nact) - dm.xc, np.arange(dm.Nact)-dm.yc) # # Use order='F' to compare the final datacube to Matlab's output. # # Otherwise, use C ordering for Python FALCO. # x_vec = dm.Xact.reshape(dm.Nact*dm.Nact,order='F') # y_vec = dm.Yact.reshape(dm.Nact*dm.Nact,order='F') x_vec = dm.Xact.reshape(dm.Nact*dm.Nact) y_vec = dm.Yact.reshape(dm.Nact*dm.Nact) dm.NactTotal = x_vec.shape[0] # Total number of actuators in the 2-D array dm.xy_cent_act = np.zeros((2, dm.NactTotal)) # Initialize # Compute the rotation matrix to apply to the influence function and # actuator center locations tlt = np.zeros(3) tlt[0] = radians(dm.xtilt) tlt[1] = radians(dm.ytilt) tlt[2] = radians(-dm.zrot) sa = sin(tlt[0]) ca = cos(tlt[0]) sb = sin(tlt[1]) cb = cos(tlt[1]) sg = sin(tlt[2]) cg = cos(tlt[2]) if XYZ: Mrot = np.array( [[cb * cg, sa * sb * cg - ca * sg, ca * sb * cg + sa * sg, 0.0], [cb * sg, sa * sb * sg + ca * cg, ca * sb * sg - sa * cg, 0.0], [-sb, sa * cb, ca * cb, 0.0], [0.0, 0.0, 0.0, 1.0]]) else: Mrot = np.array( [[cb * cg, -cb * sg, sb, 0.0], [ca * sg + sa * sb * cg, ca * cg - sa * sb * sg, -sa * cb, 0.0], [sa * sg - ca * sb * cg, sa * cg + ca * sb * sg, ca * cb, 0.0], [0.0, 0.0, 0.0, 1.0]]) # # Compute the actuator center coordinates in units of actuator spacings # for iact in range(dm.NactTotal): # xyzVals = np.array([x_vec[iact], y_vec[iact], 0., 1.]) # xyzValsRot = Mrot @ xyzVals # dm.xy_cent_act[0, iact] = xyzValsRot[0].copy() # dm.xy_cent_act[1, iact] = xyzValsRot[1].copy() actIndMat = np.arange(dm.Nact**2, dtype=int).reshape((dm.Nact, dm.Nact)) if hasattr(dm, 'orientation'): if dm.orientation.lower() == 'rot0': pass # no change elif dm.orientation.lower() == 'rot90': actIndMat = np.rot90(actIndMat, 1) elif dm.orientation.lower() == 'rot180': actIndMat = np.rot90(actIndMat, 2) elif dm.orientation.lower() == 'rot270': actIndMat = np.rot90(actIndMat, 3) elif dm.orientation.lower() == 'flipxrot0': actIndMat = np.flipx(actIndMat) elif dm.orientation.lower() == 'flipxrot90': actIndMat = np.rot90(np.flipx(actIndMat), 1) elif dm.orientation.lower() == 'flipxrot180': actIndMat = np.rot90(np.flipx(actIndMat), 2) elif dm.orientation.lower() == 'flipxrot270': actIndMat = np.rot90(np.flipx(actIndMat), 3) else: raise ValueError('invalid value of dm.orientation') # Compute the actuator center coordinates in units of actuator spacings for iact, iIndex in enumerate(actIndMat.flatten()): xyzVals = np.array([x_vec[iIndex], y_vec[iIndex], 0., 1.]) xyzValsRot = Mrot @ xyzVals dm.xy_cent_act[0, iact] = xyzValsRot[0].copy() dm.xy_cent_act[1, iact] = xyzValsRot[1].copy() N0 = dm.inf0.shape[0] Npad = falco.util.ceil_odd(np.sqrt(2.)*N0) inf0pad = np.zeros((Npad, Npad)) inf0pad[int(np.ceil(Npad/2.)-np.floor(N0/2.)-1):int(np.ceil(Npad/2.)+np.floor(N0/2.)), int(np.ceil(Npad/2.)-np.floor(N0/2.)-1):int(np.ceil(Npad/2.)+np.floor(N0/2.))] = dm.inf0 ydim = inf0pad.shape[0] xdim = inf0pad.shape[1] xd2 = np.fix(xdim / 2.) + 1 yd2 = np.fix(ydim / 2.) + 1 cx = np.arange(xdim) + 1. - xd2 cy = np.arange(ydim) + 1. - yd2 [Xs0, Ys0] = np.meshgrid(cx, cy) xsNewVec = np.zeros(xdim*xdim) ysNewVec = np.zeros(ydim*ydim) Xs0Vec = Xs0.reshape(xdim*xdim) Ys0Vec = Ys0.reshape(ydim*ydim) for ii in range(Xs0.size): xyzVals = np.array([Xs0Vec[ii], Ys0Vec[ii], 0., 1.]) xyzValsRot = Mrot @ xyzVals xsNewVec[ii] = xyzValsRot[0] ysNewVec[ii] = xyzValsRot[1] # Calculate the interpolated DM grid at the new resolution # (set extrapolated values to 0.0) dm.infMaster = griddata((xsNewVec, ysNewVec), inf0pad.reshape(Npad*Npad), (Xs0, Ys0), method='cubic', fill_value=0.) # Crop down the influence function until it has no zero padding left infSum = np.sum(dm.infMaster) infDiff = 0. counter = 0 while(abs(infDiff) <= 1e-7): counter = counter + 2 infDiff = infSum - np.sum(abs(dm.infMaster[int(counter/2):int(-counter/2), int(counter/2):int(-counter/2)])) # Subtract an extra 2 to negate the extra step that overshoots. counter = counter - 2 Ninf0pad = dm.infMaster.shape[0]-counter if counter == 0: infMaster2 = dm.infMaster.copy() else: # The cropped-down influence function infMaster2 = dm.infMaster[int(counter/2):int(-counter/2), int(counter/2):int(-counter/2)].copy() dm.infMaster = infMaster2 Npad = Ninf0pad # True for even- or odd-sized influence function maps as long as they are # centered on the array. x_inf0 = np.linspace(-(Npad-1)/2, (Npad-1)/2., Npad)*dm.dx_inf0 [Xinf0, Yinf0] = np.meshgrid(x_inf0, x_inf0) # Translate and resample the master influence function to be at each # actuator's location in the pixel grid # Compute the size of the postage stamps. # Number of points across the influence function array at the DM plane's # resolution. Want as even Nbox = falco.util.ceil_even(Ninf0pad*dm.dx_inf0/dx_dm) dm.Nbox = Nbox # Also compute their padded sizes for the angular spectrum (AS) propagation # between P2 and DM1 or between DM1 and DM2 # Minimum number of points across for accurate angular spectrum propagation Nmin = falco.util.ceil_even(np.max(mp.sbp_centers)*np.max(np.abs(np.array( [mp.d_P2_dm1, mp.d_dm1_dm2, (mp.d_P2_dm1+mp.d_dm1_dm2)])))/dx_dm**2) # Use a larger array if the max sampling criterion for angular spectrum # propagation is violated dm.NboxAS = np.max(np.array([Nbox, Nmin])) # Pad the pupil to at least the size of the DM(s) surface(s) to allow all # actuators to be located outside the pupil. # (Same for both DMs) # Find actuator farthest from center: dm.r_cent_act = np.sqrt(dm.xy_cent_act[0, :]**2 + dm.xy_cent_act[1, :]**2) dm.rmax = np.max(np.abs(dm.r_cent_act)) NpixPerAct = dm.dm_spacing/dx_dm if(dm.flag_hex_array): # padded 2 actuators past the last actuator center to avoid trying to # index outside the array dm.NdmPad = falco.util.ceil_even((2.*(dm.rmax+2))*NpixPerAct + 1) else: # DM surface array padded by the width of the padded influence function # to prevent indexing outside the array. # The 1/2 term is because the farthest actuator center is still half an # actuator away from the nominal array edge. dm.NdmPad = falco.util.ceil_even((dm.NboxAS + 2.0*(1 + (np.max( np.abs(dm.xy_cent_act.reshape(2*dm.NactTotal)))+0.5)*NpixPerAct))) # Compute coordinates (in meters) of the full DM array if(dm.centering == 'pixel'): # meters, coords for the full DM arrays. Origin is centered on a pixel dm.x_pupPad = np.linspace(-dm.NdmPad/2., (dm.NdmPad/2. - 1), dm.NdmPad)*dx_dm else: # meters, coords for the full DM arrays. Origin is interpixel centered dm.x_pupPad = np.linspace(-(dm.NdmPad-1)/2., (dm.NdmPad-1)/2., dm.NdmPad)*dx_dm dm.y_pupPad = dm.x_pupPad dm.act_ele = np.arange(dm.NactTotal) # Include all actuators # Make NboxPad-sized postage stamps for each actuator's influence function if(flagGenCube): if not dm.flag_hex_array: print(" Influence function padded from %d to %d points for A.S. propagation." % (Nbox, dm.NboxAS)) print('Computing datacube of DM influence functions... ', end='') # Find the locations of the postage stamps arrays in the larger pupilPad array dm.xy_cent_act_inPix = dm.xy_cent_act*(dm.dm_spacing/dx_dm) # Convert units to pupil-plane pixels dm.xy_cent_act_inPix = dm.xy_cent_act_inPix + 0.5 # For the half-pixel offset if pixel centered. dm.xy_cent_act_box = np.round(dm.xy_cent_act_inPix) # Center locations of the postage stamps (in between pixels), in actuator widths dm.xy_cent_act_box_inM = dm.xy_cent_act_box*dx_dm # now in meters dm.xy_box_lowerLeft = dm.xy_cent_act_box + (dm.NdmPad-Nbox)/2 - 0 # index of pixel in lower left of the postage stamp within the whole pupilPad array. +0 for Python, +1 for Matlab # Starting coordinates (in actuator widths) for updated master influence function. # This is interpixel centered, so do not translate! dm.x_box0 = np.linspace(-(Nbox-1)/2., (Nbox-1)/2., Nbox)*dx_dm [dm.Xbox0, dm.Ybox0] = np.meshgrid(dm.x_box0, dm.x_box0) # meters, interpixel-centered coordinates for the master influence function # (Allow for later) Limit the actuators used to those within 1 actuator width of the pupil # r_cent_act_box_inM = np.sqrt(dm.xy_cent_act_box_inM[0, :]**2 + dm.xy_cent_act_box_inM[1, :]**2) # Compute and store all the influence functions: dm.inf_datacube = np.zeros((Nbox, Nbox, dm.NactTotal)) # initialize array of influence function "postage stamps" inf_datacube = np.zeros((dm.NactTotal, Nbox, Nbox)) interp_spline = RectBivariateSpline(x_inf0, x_inf0, dm.infMaster) # RectBivariateSpline is faster in 2-D than interp2d # Refer to https://scipython.com/book/chapter-8-scipy/examples/two-dimensional-interpolation-with-scipyinterpolaterectbivariatespline/ for iact in range(dm.NactTotal): xbox = dm.x_box0 - (dm.xy_cent_act_inPix[0, iact]-dm.xy_cent_act_box[0, iact])*dx_dm # X = X0 -(x_true_center-x_box_center) ybox = dm.x_box0 - (dm.xy_cent_act_inPix[1, iact]-dm.xy_cent_act_box[1, iact])*dx_dm # Y = Y0 -(y_true_center-y_box_center) dm.inf_datacube[:, :, iact] = interp_spline(ybox, xbox) inf_datacube[iact, :, :] = interp_spline(ybox, xbox) print('done.') else: dm.act_ele = np.arange(dm.NactTotal)
def gen_surf_from_act(dm, dx, N): """ Function to compute the surface shape of a deformable mirror. Uses PROPER. Parameters ---------- dm : ModelParameters Structure containing parameter values for the DM dx : float Pixel width [meters] at the DM plane N : int Number of points across the array to return at the DM plane Returns ------- DMsurf : array_like 2-D surface map of the DM """ check.real_positive_scalar(dx, 'dx', TypeError) check.positive_scalar_integer(N, 'N', TypeError) # if type(dm) is not falco.config.Object: # raise TypeError('Input "dm" must be of type falco.config.Object') # Set the order of operations flagXYZ = True if(hasattr(dm, 'flagZYX')): if(dm.flagZYX): flagXYZ = False # Adjust the centering of the output DM surface. The shift needs to be in # units of actuators, not meters, for prop_dm.m. Darray = dm.NdmPad*dm.dx Narray = dm.NdmPad if dm.centering == 'interpixel': cshift = -Darray/2./Narray/dm.dm_spacing elif dm.centering == 'pixel': cshift = 0 pupil_ratio = 1 # beam diameter fraction wl_dummy = 1e-6 # dummy value needed to initialize PROPER (meters) bm = proper.prop_begin(N*dx, wl_dummy, N, pupil_ratio) # Apply various constraints to DM commands dm = enforce_constraints(dm) # Quantization of DM actuation steps based on least significant bit of the # DAC (digital-analog converter). In height, so called HminStep # If HminStep (minimum step in H) is defined, then quantize the DM voltages if(hasattr(dm, 'HminStep')): if not(hasattr(dm, 'HminStepMethod')): dm.HminStepMethod = 'round' # Discretize/Quantize the DM voltages (creates dm.Vquantized) dm = discretize_surf(dm, dm.HminStepMethod) heightMap = dm.VtoH*dm.Vquantized else: # Quantization not desired; send raw, continuous voltages heightMap = dm.VtoH*dm.V if hasattr(dm, 'orientation'): if dm.orientation.lower() == 'rot0': pass # no change elif dm.orientation.lower() == 'rot90': heightMap = np.rot90(heightMap, 1) elif dm.orientation.lower() == 'rot180': heightMap = np.rot90(heightMap, 2) elif dm.orientation.lower() == 'rot270': heightMap = np.rot90(heightMap, 3) elif dm.orientation.lower() == 'flipxrot0': heightMap = np.flipx(heightMap) elif dm.orientation.lower() == 'flipxrot90': heightMap = np.rot90(np.flipx(heightMap), 1) elif dm.orientation.lower() == 'flipxrot180': heightMap = np.rot90(np.flipx(heightMap), 2) elif dm.orientation.lower() == 'flipxrot270': heightMap = np.rot90(np.flipx(heightMap), 3) else: raise ValueError('invalid value of dm.orientation') # Generate the DM surface DMsurf = falco.dm.propcustom_dm(bm, heightMap, dm.xc-cshift, dm.yc-cshift, dm.dm_spacing, XTILT=dm.xtilt, YTILT=dm.ytilt, ZTILT=dm.zrot, XYZ=flagXYZ, inf_sign=dm.inf_sign, inf_fn=dm.inf_fn) return DMsurf
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 mft_p2f(E_pup, fl, wavelength, dx, dxi, Nxi, deta, Neta, centering='pixel'): """ Propagate a pupil to a focus using a matrix-multiply DFT. Parameters ---------- E_pup : array_like Electric field array in pupil plane fl : float Focal length of Fourier transforming lens wavelength : float Propagation wavelength dx : float Step size along either axis of focal plane. The vertical and horizontal step sizes are assumed to be equal. dxi : float Step size along horizontal axis of focal plane Nxi : int Number of samples along horizontal axis of focal plane. deta : float Step size along vertical axis of focal plane Neta : int Number of samples along vertical axis of focal plane. centering : string Whether the input and output arrays are pixel-centered or inter-pixel-centered. Possible values: 'pixel', 'interpixel' Returns ------- array_like Field in pupil plane, after propagating through Fourier transforming lens """ if centering not in _VALID_CENTERING: raise ValueError(_CENTERING_ERR) check.twoD_array(E_pup, 'E_pup', TypeError) check.real_scalar(fl, 'fl', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.real_positive_scalar(dxi, 'dxi', TypeError) check.positive_scalar_integer(Nxi, 'Nxi', TypeError) check.real_positive_scalar(deta, 'deta', TypeError) check.positive_scalar_integer(Neta, 'Neta', TypeError) dy = dx M, N = E_pup.shape if M != N: raise ValueError('Input array is not square') # Pupil-plane coordinates x = util.create_axis(N, dx, centering=centering)[:, None] # Broadcast to column vector y = x.T # Row vector # Focal-plane coordinates xi = util.create_axis(Nxi, dxi, centering=centering)[None, :] # Broadcast to row vector eta = util.create_axis(Neta, deta, centering=centering)[:, None] # Broadcast to column vector # Fourier transform matrices pre = np.exp(-2 * np.pi * 1j * (eta * y) / (wavelength * fl)) post = np.exp(-2 * np.pi * 1j * (x * xi) / (wavelength * fl)) # Constant scaling factor in front of Fourier transform scaling = np.sqrt(dx * dy * dxi * deta) / (1 * wavelength * fl) return scaling * np.linalg.multi_dot([pre, E_pup, post])
def solver(n, d0, theta, lam, tetm=False): """ Solve the thin film equations for the given materials. Parameters ---------- n : array_like index of refraction for each layer. n(1) = index of incident medium n(N) = index of transmission medium then length(n) must be >= 2 d0 : array_like thickness of each layer, not counting incident medium or transmission medium. length(d) = length(n)-2. theta : float angle of incidence [radians]. lam : float wavelength. units of lam must be same as d0. tetm : bool, optional False => TE, True => TM. The default is False. Returns ------- R : numpy ndarray normalized reflected intensity coefficient T : numpy ndarray normalized transmitted intensity coefficient rr : numpy ndarray complex field reflection coefficient tt : numpy ndarray complex field transmission coefficient """ oneD_array(n, 'n', ValueError) oneD_array(d0, 'd0', ValueError) N = len(n) if not (len(d0) == N-2): raise ValueError('n and d size mismatch') pass real_nonnegative_scalar(theta, 'theta', TypeError) real_positive_scalar(lam, 'lam', TypeError) if not type(tetm) == bool: raise TypeError('tetm must be a boolean.') # np.hstac d = np.hstack((0, d0.reshape(len(d0, )), 0)) kx = 2*np.pi*n[0]*np.sin(theta)/lam # sign agrees with measurement convention: kz = -np.sqrt((2*np.pi*n/lam)**2 - kx*kx) if tetm: kzz = kz/(n*n) else: kzz = kz eep = np.exp(-1j*kz*d) eem = np.exp(1j*kz*d) i1 = np.arange(N-1) i2 = np.arange(1, N) tin = 0.5*(kzz[i1] + kzz[i2])/kzz[i1] ri = (kzz[i1] - kzz[i2])/(kzz[i1] + kzz[i2]) A = np.eye(2, dtype=complex) for i in range(N-1): A = A @ np.array(tin[i]*np.array([[eep[i], ri[i]*eep[i]], [ri[i]*eem[i], eem[i]]])) rr = A[1, 0] / A[0, 0] tt = 1 / A[0, 0] # transmitted power flux (Poynting vector . surface) depends on index of # the substrate and angle R = np.abs(rr)**2 if tetm: Pn = np.real((kz[-1]/(n[-1]**2)) / (kz[0]/(n[0]**2))) else: Pn = np.real((kz[-1]/kz[0])) pass T = Pn*np.abs(tt)**2 tt = np.sqrt(Pn)*tt return [R, T, rr, tt]
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